Skip to content

Commit 7e977f5

Browse files
author
Amineta Lo
committed
feat(pfCard): Add a loading state to the pfCards
1 parent 26acd65 commit 7e977f5

File tree

8 files changed

+191
-24
lines changed

8 files changed

+191
-24
lines changed

src/card/aggregate-status/aggregate-status-card.component.js

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
* </ul>
3131
* </ul>
3232
* @param {boolean=} show-top-border Show/hide the top border, true shows top border, false (default) hides top border
33+
* @param {boolean=} showSpinner Show/Hide the spinner for loading state. True shows the spinner, false (default) hides the spinner
34+
* @param {string=} spinnerText Text for the card spinner
35+
* @param {string=} loadingCardHeight Height to set for the card
3336
* @param {string=} layout Various alternative layouts the aggregate status card may have:<br/>
3437
* <ul style='list-style-type: none'>
3538
* <li>'mini' displays a mini aggregate status card. Note: when using 'mini' layout, only one notification can be specified in the status object
@@ -63,13 +66,19 @@
6366
<i>(depreciated, use layout = 'tall' instead)</i>
6467
</br></br>
6568
<pf-aggregate-status-card status="aggStatusAlt" show-top-border="true" alt-layout="true"></pf-aggregate-status-card>
69+
<br/>
70+
<label>Loading State</label>
71+
<pf-aggregate-status-card status="aggStatusAlt2" loading-card-height="500px" show-top-border="true" show-spinner="dataLoading" spinner-text="Loading" layout="tall"></pf-aggregate-status-card>
6672
</div>
6773
</div>
6874
</file>
6975
7076
<file name="script.js">
71-
angular.module( 'patternfly.card' ).controller( 'CardDemoCtrl', function( $scope, $window ) {
77+
angular.module( 'patternfly.card' ).controller( 'CardDemoCtrl', function( $scope, $window, $timeout ) {
7278
var imagePath = $window.IMAGE_PATH || "img";
79+
80+
$scope.dataLoading = true;
81+
7382
$scope.status = {
7483
"title":"Nodes",
7584
"count":793,
@@ -103,28 +112,49 @@
103112
"href":"#"
104113
}
105114
]
106-
};
115+
};
116+
117+
$timeout(function () {
118+
$scope.dataLoading = false;
119+
120+
$scope.aggStatusAlt2 = {
121+
"title":"Providers",
122+
"count":3,
123+
"notifications":[
124+
{
125+
"iconImage": imagePath + "/kubernetes.svg",
126+
"count":1,
127+
"href":"#"
128+
},
129+
{
130+
"iconImage": imagePath + "/OpenShift-logo.svg",
131+
"count":2,
132+
"href":"#"
133+
}
134+
]
135+
};
136+
}, 3000 );
107137
108-
$scope.miniAggStatus = {
138+
$scope.miniAggStatus = {
109139
"iconClass":"pficon pficon-container-node",
110140
"title":"Nodes",
111141
"count":52,
112142
"href":"#",
113143
"notification": {
114-
"iconClass":"pficon pficon-error-circle-o",
115-
"count":3
116-
}
117-
};
144+
"iconClass":"pficon pficon-error-circle-o",
145+
"count":3
146+
}
147+
};
118148
119-
$scope.miniAggStatus2 = {
149+
$scope.miniAggStatus2 = {
120150
"iconClass":"pficon pficon-cluster",
121151
"title":"Adipiscing",
122152
"count":9,
123153
"href":"#",
124154
"notification":{
125-
"iconClass":"pficon pficon-ok"
126-
}
127-
};
155+
"iconClass":"pficon pficon-ok"
156+
}
157+
};
128158
});
129159
</file>
130160
@@ -135,6 +165,9 @@ angular.module( 'patternfly.card' ).component('pfAggregateStatusCard', {
135165
bindings: {
136166
status: '=',
137167
showTopBorder: '@?',
168+
showSpinner: '<?',
169+
spinnerText: '@?',
170+
loadingCardHeight: '@',
138171
altLayout: '@?',
139172
layout: '@?'
140173
},
@@ -146,6 +179,11 @@ angular.module( 'patternfly.card' ).component('pfAggregateStatusCard', {
146179
ctrl.shouldShowTopBorder = (ctrl.showTopBorder === 'true');
147180
ctrl.isAltLayout = (ctrl.altLayout === 'true' || ctrl.layout === 'tall');
148181
ctrl.isMiniLayout = (ctrl.layout === 'mini');
182+
ctrl.showSpinner = ctrl.showSpinner === true;
183+
184+
if ($scope.loadingCardHeight) {
185+
angular.element($element[0].querySelector('.card-pf-aggregate-status .card-pf-body+.show-spinner')).css('height', $scope.loadingCardHeight);
186+
}
149187
};
150188
}
151189
});

src/card/aggregate-status/aggregate-status-card.html

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div ng-if="!$ctrl.isMiniLayout" class="card-pf card-pf-aggregate-status" ng-class="{'card-pf-accented': $ctrl.shouldShowTopBorder, 'card-pf-aggregate-status-alt': $ctrl.isAltLayout}">
2-
<h2 class="card-pf-title">
2+
<h2 class="card-pf-title" ng-class="{'hide-for-spinner': $ctrl.showSpinner}">
33
<a href="{{$ctrl.status.href}}" ng-if="$ctrl.status.href">
44
<image ng-if="$ctrl.status.iconImage" ng-src="{{$ctrl.status.iconImage}}" alt="" class="card-pf-icon-image"></image>
55
<span class="{{$ctrl.status.iconClass}}"></span>
@@ -13,8 +13,15 @@ <h2 class="card-pf-title">
1313
<span class="card-pf-aggregate-status-title">{{$ctrl.status.title}}</span>
1414
</span>
1515
</h2>
16-
<div class="card-pf-body">
17-
<p class="card-pf-aggregate-status-notifications">
16+
<div class="card-pf-body" ng-class="{'show-spinner': $ctrl.showSpinner}">
17+
<div ng-if="$ctrl.showSpinner" class="spinner-container">
18+
<div class="loading-indicator">
19+
<span class="spinner spinner-lg" aria-hidden="true"></span>
20+
<span ng-if="$ctrl.spinnerText" class="loading-text">{{$ctrl.spinnerText}}</span>
21+
<label ng-if="!$ctrl.spinnerText" class="sr-only">Loading</label>
22+
</div>
23+
</div>
24+
<p class="card-pf-aggregate-status-notifications" ng-class="{'hide-for-spinner': $ctrl.showSpinner}">
1825
<span class="card-pf-aggregate-status-notification" ng-repeat="notification in $ctrl.status.notifications">
1926
<a href="{{notification.href}}" ng-if="notification.href">
2027
<image ng-if="notification.iconImage" ng-src="{{notification.iconImage}}" alt="" class="card-pf-icon-image"></image>
@@ -29,7 +36,7 @@ <h2 class="card-pf-title">
2936
</div>
3037
</div>
3138
<div ng-if="$ctrl.isMiniLayout" class="card-pf card-pf-aggregate-status card-pf-aggregate-status-mini" ng-class="{'card-pf-accented': $ctrl.shouldShowTopBorder}">
32-
<h2 class="card-pf-title">
39+
<h2 class="card-pf-title" ng-class="{'hide-for-spinner': $ctrl.showSpinner}">
3340
<a ng-if="$ctrl.status.href" href="{{$ctrl.status.href}}">
3441
<image ng-if="$ctrl.status.iconImage" ng-src="{{$ctrl.status.iconImage}}" alt="" class="card-pf-icon-image"></image>
3542
<span ng-if="$ctrl.status.iconClass" class="{{$ctrl.status.iconClass}}"></span>
@@ -41,7 +48,14 @@ <h2 class="card-pf-title">
4148
{{$ctrl.status.title}}
4249
</span>
4350
</h2>
44-
<div class="card-pf-body">
51+
<div class="card-pf-body" ng-class="{'show-spinner': $ctrl.showSpinner}">
52+
<div ng-if="$ctrl.showSpinner" class="spinner-container">
53+
<div class="loading-indicator">
54+
<span class="spinner spinner-lg" aria-hidden="true"></span>
55+
<span ng-if="$ctrl.spinnerText" class="loading-text">{{$ctrl.spinnerText}}</span>
56+
<label ng-if="!$ctrl.spinnerText" class="sr-only">Loading</label>
57+
</div>
58+
</div>
4559
<p ng-if="$ctrl.status.notification.iconImage || $ctrl.status.notification.iconClass || $ctrl.status.notification.count" class="card-pf-aggregate-status-notifications">
4660
<span class="card-pf-aggregate-status-notification">
4761
<a ng-if="$ctrl.status.notification.href" href="{{$ctrl.status.notification.href}}">

src/card/card.less

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@
6464
}
6565
}
6666

67+
.card-pf-aggregate-status-mini {
68+
.card-pf-body {
69+
&.show-spinner {
70+
height: 40px;
71+
}
72+
.spinner-container {
73+
top: -22px;
74+
}
75+
}
76+
}
77+
6778
.card-pf-heading-no-bottom {
6879
margin: 0 -20px 0px;
6980
padding: 0 20px 0;
@@ -133,4 +144,7 @@
133144
margin-bottom: 15px;
134145
}
135146
}
147+
.show-spinner {
148+
width: 100%;
149+
}
136150
}

src/card/examples/card-trend.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
*
66
* @param {string} headTitle Title for the card
77
* @param {string=} subTitle Sub-Title for the card
8+
* @param {string=} spinnerText Text for the card spinner
89
* @param {boolean=} showTopBorder Show/Hide the blue top border. True shows top border, false (default) hides top border
10+
* @param {boolean=} showSpinner Show/Hide the spinner for loading state. True shows the spinner, false (default) hides the spinner
911
* @param {boolean=} showTitlesSeparator Show/Hide the grey line between the title and sub-title.
1012
* True (default) shows the line, false hides the line
1113
* @param {object=} footer footer configuration properties:<br/>
@@ -38,7 +40,7 @@
3840
<pf-card head-title="Cluster Utilization" show-top-border="true" footer="footerConfig" filter="filterConfig" style="width: 50%">
3941
<pf-trends-chart config="configSingle" chart-data="dataSingle"></pf-trends-chart>
4042
</pf-card>
41-
<pf-card head-title="Cluster Utilization" show-top-border="true" footer="footerConfig" filter="filterConfig" style="width: 50%">
43+
<pf-card head-title="Cluster Utilization" show-top-border="true" show-spinner="dataLoading" spinner-text="Loading" footer="footerConfig" filter="filterConfig" style="width: 50%">
4244
<pf-trends-chart config="configRightLabel" chart-data="dataSingle"></pf-trends-chart>
4345
</pf-card>
4446
<label class="label-title">Card with Multiple Trends</label>
@@ -52,7 +54,13 @@
5254
</div>
5355
</file>
5456
<file name="script.js">
55-
angular.module( 'demo', ['patternfly.charts', 'patternfly.card'] ).controller( 'ChartCtrl', function( $scope ) {
57+
angular.module( 'demo', ['patternfly.charts', 'patternfly.card'] ).controller( 'ChartCtrl', function( $scope, $timeout ) {
58+
59+
$scope.dataLoading = true;
60+
61+
$timeout(function () {
62+
$scope.dataLoading = false;
63+
}, 3000 );
5664
5765
$scope.footerConfig = {
5866
'iconClass' : 'fa fa-flag',

src/card/info-status/info-status-card.component.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
* </ul>
1414
* @param {boolean=} show-top-border Show/hide the top border, true shows top border, false (default) hides top border
1515
* @param {boolean} htmlContent Flag to allow HTML content within the info options
16+
* @param {boolean=} showSpinner Show/Hide the spinner for loading state. True shows the spinner, false (default) hides the spinner
17+
* @param {string=} spinnerText Text for the card spinner
1618
*
1719
* @description
1820
* Component for easily displaying textual information
@@ -31,13 +33,19 @@
3133
<br/>
3234
<label>With HTML</label>
3335
<pf-info-status-card status="infoStatusAlt" html-content="true"></pf-info-status-card>
36+
<br/>
37+
<label>Loading State</label>
38+
<pf-info-status-card status="infoStatus2" show-top-border="true" show-spinner="dataLoading" spinner-text="Loading"></pf-info-status-card>
3439
</div>
3540
</div>
3641
</file>
3742
3843
<file name="script.js">
39-
angular.module( 'patternfly.card' ).controller( 'CardDemoCtrl', function( $scope, $window ) {
44+
angular.module( 'patternfly.card' ).controller( 'CardDemoCtrl', function( $scope, $window, $timeout ) {
4045
var imagePath = $window.IMAGE_PATH || "img";
46+
47+
$scope.dataLoading = true;
48+
4149
$scope.infoStatus = {
4250
"title":"TinyCore-local",
4351
"href":"#",
@@ -50,6 +58,22 @@
5058
]
5159
};
5260
61+
$timeout(function () {
62+
$scope.dataLoading = false;
63+
64+
$scope.infoStatus2 = {
65+
"title":"TinyCore-local",
66+
"href":"#",
67+
"iconClass": "fa fa-shield",
68+
"info":[
69+
"VM Name: aapdemo002",
70+
"Host Name: localhost.localdomian",
71+
"IP Address: 10.9.62.100",
72+
"Power status: on"
73+
]
74+
};
75+
}, 3000 );
76+
5377
$scope.infoStatusTitless = {
5478
"iconImage": imagePath + "/OpenShift-logo.svg",
5579
"info":[
@@ -79,6 +103,8 @@ angular.module( 'patternfly.card' ).component('pfInfoStatusCard', {
79103
bindings: {
80104
status: '=',
81105
showTopBorder: '@?',
106+
showSpinner: '<?',
107+
spinnerText: '@?',
82108
htmlContent: '@?'
83109
},
84110
templateUrl: 'card/info-status/info-status-card.html',
@@ -88,6 +114,7 @@ angular.module( 'patternfly.card' ).component('pfInfoStatusCard', {
88114
ctrl.$onInit = function () {
89115
ctrl.shouldShowTopBorder = (ctrl.showTopBorder === 'true');
90116
ctrl.shouldShowHtmlContent = (ctrl.htmlContent === 'true');
117+
ctrl.showSpinner = ctrl.showSpinner === true;
91118
ctrl.trustAsHtml = function (html) {
92119
return $sce.trustAsHtml(html);
93120
};

src/card/info-status/info-status-card.html

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
<div class="card-pf card-pf-info-status"
2-
ng-class="{'card-pf-accented': $ctrl.shouldShowTopBorder}">
3-
<div class="card-pf-info-image">
1+
<div class="card-pf card-pf-info-status" ng-class="{'card-pf-accented': $ctrl.shouldShowTopBorder}">
2+
<div ng-if="$ctrl.showSpinner" class="card-pf-body show-spinner">
3+
<div class="spinner-container">
4+
<div class="loading-indicator">
5+
<span class="spinner spinner-lg" aria-hidden="true"></span>
6+
<span ng-if="$ctrl.spinnerText" class="loading-text">{{$ctrl.spinnerText}}</span>
7+
<label ng-if="!$ctrl.spinnerText" class="sr-only">Loading</label>
8+
</div>
9+
</div>
10+
</div>
11+
<div ng-if="!$ctrl.showSpinner" class="card-pf-info-image">
412
<img ng-if="$ctrl.status.iconImage" ng-src="{{$ctrl.status.iconImage}}" alt=""
513
class="info-img"/>
614
<span class="info-icon {{$ctrl.status.iconClass}}"></span>
715
</div>
8-
<div class="card-pf-info-content">
16+
<div ng-if="!$ctrl.showSpinner" class="card-pf-info-content card-pf-body" ng-class="{'show-spinner': $ctrl.showSpinner}">
917
<h2 class="card-pf-title" ng-if="$ctrl.status.title">
1018
<a href="{{$ctrl.status.href}}" ng-if="$ctrl.status.href">
1119
<span class="">{{$ctrl.status.title}}</span>

test/card/aggregate-status/aggregate-status-card.component.spec.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('Component: pfAggregateStatusCard', function() {
2222
"title":"Nodes",
2323
"count":793,
2424
"href":"#",
25-
"iconClass": "fa fa-shield",
25+
"iconClass": "fa fa-shield"
2626
};
2727

2828
element = compileCard('<pf-aggregate-status-card status="status"></pf-aggregate-status-card>', $scope);
@@ -113,6 +113,35 @@ describe('Component: pfAggregateStatusCard', function() {
113113
expect(cardClass).toBeFalsy();
114114
});
115115

116+
it("should show and hide the spinner", function() {
117+
118+
// When data is loaded, spinner should be hidden
119+
$scope.dataLoading = false;
120+
element = compileCard('<pf-aggregate-status-card status="aggStatusAlt2" show-spinner="dataLoading"></pf-aggregate-status-card>', $scope);
121+
cardClass = angular.element(element).find('.spinner-lg');
122+
expect(cardClass.length).toBe(0);
123+
124+
// When data is loading, spinner should be present
125+
$scope.dataLoading = true;
126+
$scope.$digest();
127+
cardClass = angular.element(element).find('.spinner-lg');
128+
expect(cardClass.length).toBe(1);
129+
});
130+
131+
it("should show and hide the spinner text", function() {
132+
133+
// When no spinner text is given, it should be undefined
134+
element = compileCard('<pf-aggregate-status-card status="aggStatusAlt2" show-spinner="dataLoading"></pf-aggregate-status-card>', $scope);
135+
cardClass = angular.element(element).find('.loading-text');
136+
expect(cardClass.html()).toBeUndefined();
137+
138+
// When data is loading, spinner text should be present
139+
$scope.dataLoading = true;
140+
element = compileCard('<pf-aggregate-status-card status="aggStatusAlt2" show-spinner="dataLoading" spinner-text="Test Loading Message"></pf-aggregate-status-card>', $scope);
141+
cardClass = angular.element(element).find('.loading-text');
142+
expect(cardClass.html()).toContain('Test Loading Message');
143+
});
144+
116145
it("should show mini layout", function() {
117146

118147
$scope.status = {

test/card/info-status/info-status-card.component.spec.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,35 @@ describe('Component: pfInfoStatusCard', function () {
9595
expect(cardClass).toBeFalsy();
9696
});
9797

98+
it("should show and hide the spinner", function() {
99+
100+
// When data is loaded, spinner should be hidden
101+
$scope.dataLoading = false;
102+
element = compileCard('<pf-info-status-card status="infoStatus2" show-spinner="dataLoading"></pf-info-status-card>', $scope);
103+
cardClass = angular.element(element).find('.spinner-lg');
104+
expect(cardClass.length).toBe(0);
105+
106+
// When data is loading, spinner should be present
107+
$scope.dataLoading = true;
108+
$scope.$digest();
109+
cardClass = angular.element(element).find('.spinner-lg');
110+
expect(cardClass.length).toBe(1);
111+
});
112+
113+
it("should show and hide the spinner text", function() {
114+
115+
// When no spinner text is given, it should be undefined
116+
element = compileCard('<pf-info-status-card status="infoStatus2" show-spinner="dataLoading"></pf-info-status-card>', $scope);
117+
cardClass = angular.element(element).find('.loading-text');
118+
expect(cardClass.html()).toBeUndefined();
119+
120+
// When data is loading, spinner text should be present
121+
$scope.dataLoading = true;
122+
element = compileCard('<pf-info-status-card status="infoStatus2" show-spinner="dataLoading" spinner-text="Test Loading Message"></pf-info-status-card>', $scope);
123+
cardClass = angular.element(element).find('.loading-text');
124+
expect(cardClass.html()).toContain('Test Loading Message');
125+
});
126+
98127
it('should set of the iconImage value', function () {
99128

100129
$scope.status = {

0 commit comments

Comments
 (0)