Skip to content

Commit 254e74a

Browse files
authored
Merge pull request #661 from amarie401/card-loading-state
fix(pfCard): Add loading state for pfCard
2 parents c4193f1 + 658656f commit 254e74a

File tree

7 files changed

+140
-33
lines changed

7 files changed

+140
-33
lines changed

src/card/basic/card.component.js

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
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
910
* @param {boolean=} showTitlesSeparator Show/Hide the grey line between the title and sub-title.
1011
* True (default) shows the line, false hides the line
12+
* @param {boolean=} showSpinner Show/Hide the spinner for loading state. True shows the spinner, false (default) hides the spinner
1113
* @param {object=} footer footer configuration properties:<br/>
1214
* <ul style='list-style-type: none'>
1315
* <li>.iconClass - (optional) the icon to show on the bottom left of the footer panel
@@ -35,7 +37,7 @@
3537
<file name="index.html">
3638
<div ng-controller="ChartCtrl">
3739
<label class="label-title">Card With Multiple Utilization Bars</label>
38-
<pf-card head-title="System Resources" show-top-border="true" style="width: 65%">
40+
<pf-card head-title="System Resources" show-spinner="dataLoading" spinner-text="Loading" show-top-border="true">
3941
<pf-utilization-bar-chart chart-data=data2 chart-title=title2 layout=layoutInline units=units2 threshold-error="85" threshold-warning="60"></pf-utilization-bar-chart>
4042
<pf-utilization-bar-chart chart-data=data3 chart-title=title3 layout=layoutInline units=units3 threshold-error="85" threshold-warning="60"></pf-utilization-bar-chart>
4143
<pf-utilization-bar-chart chart-data=data4 chart-title=title4 layout=layoutInline units=units4 threshold-error="85" threshold-warning="60"></pf-utilization-bar-chart>
@@ -44,37 +46,44 @@
4446
</div>
4547
</file>
4648
<file name="script.js">
47-
angular.module( 'demo', ['patternfly.charts', 'patternfly.card'] ).controller( 'ChartCtrl', function( $scope ) {
49+
angular.module( 'demo', ['patternfly.charts', 'patternfly.card'] ).controller( 'ChartCtrl', function( $scope, $timeout ) {
50+
51+
$scope.dataLoading = true;
4852
4953
$scope.title2 = 'Memory';
5054
$scope.units2 = 'GB';
5155
52-
$scope.data2 = {
53-
'used': '25',
54-
'total': '100'
55-
};
56-
5756
$scope.title3 = 'CPU Usage';
5857
$scope.units3 = 'MHz';
5958
60-
$scope.data3 = {
61-
'used': '420',
62-
'total': '500'
63-
};
64-
6559
$scope.title4 = 'Disk Usage';
6660
$scope.units4 = 'TB';
67-
$scope.data4 = {
68-
'used': '350',
69-
'total': '500'
70-
};
7161
7262
$scope.title5 = 'Disk I/O';
73-
$scope.units5 = 'I/Ops';
74-
$scope.data5 = {
75-
'used': '450',
76-
'total': '500'
77-
};
63+
$scope.units5 = 'I/Ops';
64+
65+
$timeout(function () {
66+
$scope.dataLoading = false;
67+
68+
$scope.data2 = {
69+
'used': '25',
70+
'total': '100'
71+
};
72+
73+
$scope.data3 = {
74+
'used': '420',
75+
'total': '500'
76+
};
77+
78+
$scope.data4 = {
79+
'used': '350',
80+
'total': '500'
81+
};
82+
$scope.data5 = {
83+
'used': '450',
84+
'total': '500'
85+
};
86+
}, 3000 );
7887
7988
$scope.layoutInline = {
8089
'type': 'inline'
@@ -91,6 +100,8 @@ angular.module('patternfly.card').component('pfCard', {
91100
subTitle: '@?',
92101
showTopBorder: '@?',
93102
showTitlesSeparator: '@?',
103+
showSpinner: '<?',
104+
spinnerText: '@?',
94105
footer: '=?',
95106
filter: '=?'
96107
},
@@ -104,11 +115,9 @@ angular.module('patternfly.card').component('pfCard', {
104115
ctrl.currentFilter = ctrl.filter.filters[0];
105116
}
106117
}
107-
108118
ctrl.footerCallBackFn = function () {
109119
ctrl.footerCallBackResult = ctrl.footer.callBackFn();
110120
};
111-
112121
ctrl.filterCallBackFn = function (f) {
113122
ctrl.currentFilter = f;
114123
if (ctrl.filter.callBackFn) {
@@ -130,6 +139,7 @@ angular.module('patternfly.card').component('pfCard', {
130139

131140
ctrl.$onInit = function () {
132141
ctrl.shouldShowTitlesSeparator = (!ctrl.showTitlesSeparator || ctrl.showTitlesSeparator === 'true');
142+
ctrl.showSpinner = ctrl.showSpinner === true;
133143
};
134144
}
135145
});

src/card/basic/card.html

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1-
<div ng-class="$ctrl.showTopBorder === 'true' ? 'card-pf card-pf-accented' : 'card-pf'">
1+
<div class="card-pf" ng-class="{'card-pf-accented': $ctrl.showTopBorder === 'true'}">
22
<div ng-if="$ctrl.showHeader()" ng-class="$ctrl.shouldShowTitlesSeparator ? 'card-pf-heading' : 'card-pf-heading-no-bottom'">
3-
<div ng-if="$ctrl.showFilterInHeader()" ng-include="'card/basic/card-filter.html'"></div>
3+
<div ng-if="$ctrl.showFilterInHeader()" class="card-pf-footer-in-header" ng-class="{'hide-for-spinner': $ctrl.showSpinner}" ng-include="'card/basic/card-filter.html'"></div>
44
<h2 class="card-pf-title">{{$ctrl.headTitle}}</h2>
55
</div>
6-
76
<span ng-if="$ctrl.subTitle" class="card-pf-subtitle">{{$ctrl.subTitle}}</span>
87

9-
<div class="card-pf-body">
10-
<div ng-transclude></div>
8+
<div class="card-pf-body" ng-class="{'show-spinner': $ctrl.showSpinner}">
9+
<div ng-class="{'hide-for-spinner': $ctrl.showSpinner}" ng-transclude></div>
10+
<div ng-if="$ctrl.showSpinner" class="spinner-container">
11+
<div class="loading-indicator">
12+
<span class="spinner spinner-lg" aria-hidden="true"></span>
13+
<span ng-if="$ctrl.spinnerText" class="loading-text">{{$ctrl.spinnerText}}</span>
14+
<label ng-if="!$ctrl.spinnerText" class="sr-only">Loading</label>
15+
</div>
16+
</div>
1117
</div>
12-
<div ng-if="$ctrl.footer" class="card-pf-footer">
18+
<div ng-if="$ctrl.footer" class="card-pf-footer" ng-class="{'hide-for-spinner': $ctrl.showSpinner}">
1319
<div ng-if="$ctrl.showFilterInFooter()" ng-include="'card/basic/card-filter.html'"></div>
1420
<p>
1521
<a ng-if="$ctrl.footer.href" href="{{$ctrl.footer.href}}" ng-class="{'card-pf-link-with-icon':$ctrl.footer.iconClass,'card-pf-link':!$ctrl.footer.iconClass}">

src/card/card.less

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,41 @@
2929
}
3030
}
3131

32+
.card-pf {
33+
.hide-for-spinner {
34+
opacity: 0;
35+
}
36+
.card-pf-body {
37+
&.show-spinner {
38+
position: relative;
39+
}
40+
.spinner-container {
41+
display: flex;
42+
justify-content: center;
43+
left: 0;
44+
position: absolute;
45+
right: 0;
46+
top: -20px;
47+
bottom: 0;
48+
49+
.loading-indicator {
50+
align-items: center;
51+
justify-content: center;
52+
display: flex;
53+
54+
.loading-text {
55+
font-size: 16px;
56+
margin-left: 10px;
57+
}
58+
59+
.spinner.spinner-lg {
60+
display: inline-block;
61+
}
62+
}
63+
}
64+
}
65+
}
66+
3267
.card-pf-heading-no-bottom {
3368
margin: 0 -20px 0px;
3469
padding: 0 20px 0;

src/card/examples/card-timeframe.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
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
910
* @param {boolean=} showTitlesSeparator Show/Hide the grey line between the title and sub-title.
1011
* True (default) shows the line, false hides the line
12+
* @param {boolean=} showSpinner Show/Hide the spinner for loading state. True shows the spinner, false (default) hides the spinner
1113
* @param {object=} footer footer configuration properties:<br/>
1214
* <ul style='list-style-type: none'>
1315
* <li>.iconClass - (optional) the icon to show on the bottom left of the footer panel
@@ -45,10 +47,20 @@
4547
footer="footerConfig" filter="filterConfig" style="width: 50%">
4648
Card Contents
4749
</pf-card>
50+
<label class="label-title">Loading State</label>
51+
<pf-card show-spinner="dataLoading" spinner-text="Loading" head-title="Card Title" sub-title="Card Subtitle" show-top-border="true" filter="filterConfigHeader" style="width: 50%">
52+
Card Contents
53+
</pf-card>
4854
</div>
4955
</file>
5056
<file name="script.js">
51-
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 );
5264
5365
$scope.footerConfig = {
5466
'iconClass' : 'fa fa-flag',

src/charts/utilization-bar/utilization-bar-chart.component.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,16 @@ angular.module('patternfly.charts').component('pfUtilizationBarChart', {
172172
prevChartData = angular.copy(ctrl.chartData);
173173
prevLayout = angular.copy(ctrl.layout);
174174

175+
if (!ctrl.chartData) {
176+
return;
177+
}
178+
175179
//Calculate the percentage used
176-
ctrl.chartData.percentageUsed = Math.round(100 * (ctrl.chartData.used / ctrl.chartData.total));
180+
if (!isNaN(ctrl.chartData.used) && !isNaN(ctrl.chartData.total) && (ctrl.chartData.total > 0)) {
181+
ctrl.chartData.percentageUsed = Math.round(100 * (ctrl.chartData.used / ctrl.chartData.total));
182+
} else {
183+
ctrl.chartData.percentageUsed = 0;
184+
}
177185

178186
if (ctrl.thresholdError || ctrl.thresholdWarning) {
179187
ctrl.isError = (ctrl.chartData.percentageUsed >= ctrl.thresholdError);
@@ -201,11 +209,11 @@ angular.module('patternfly.charts').component('pfUtilizationBarChart', {
201209
};
202210

203211
ctrl.usedTooltipMessage = function () {
204-
return ctrl.usedTooltipFunction ? ctrl.usedTooltipFunction() : ctrl.chartData.percentageUsed + '% Used';
212+
return ctrl.usedTooltipFunction ? ctrl.usedTooltipFunction() : _.get(ctrl.chartData, 'percentageUsed', 'N/A') + '% Used';
205213
};
206214

207215
ctrl.availableTooltipMessage = function () {
208-
return ctrl.availableTooltipFunction ? ctrl.availableTooltipFunction() : (100 - ctrl.chartData.percentageUsed) + '% Available';
216+
return ctrl.availableTooltipFunction ? ctrl.availableTooltipFunction() : (100 - _.get(ctrl.chartData, 'percentageUsed', 0)) + '% Available';
209217
};
210218
}
211219
});

test/card/basic/card.spec.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,36 @@ describe('Component: pfCard', function() {
8585

8686
});
8787

88+
it("should show and hide the spinner", function() {
89+
90+
// When data is loaded, spinner should be hidden
91+
$scope.dataLoading = false;
92+
element = compileCard('<pf-card head-title="My card title" show-spinner="dataLoading" sub-title="My card subtitle title" show-top-border="false">Inner content goes here</pf-card>', $scope);
93+
cardClass = angular.element(element).find('.spinner-lg');
94+
expect(cardClass.length).toBe(0);
95+
96+
// When data is loading, spinner should be present
97+
$scope.dataLoading = true;
98+
$scope.$digest();
99+
cardClass = angular.element(element).find('.spinner-lg');
100+
expect(cardClass.length).toBe(1);
101+
});
102+
103+
it("should show and hide the spinner text", function() {
104+
105+
// When no spinner text is given, it should be undefined
106+
element = compileCard('<pf-card head-title="My card title" show-spinner="dataLoading" sub-title="My card subtitle title" show-top-border="false">Inner content goes here</pf-card>', $scope);
107+
cardClass = angular.element(element).find('.loading-text');
108+
expect(cardClass.html()).toBeUndefined();
109+
110+
// When data is loading, spinner text should be present
111+
$scope.dataLoading = true;
112+
element = compileCard('<pf-card head-title="My card title" show-spinner="dataLoading" spinner-text="Test Loading Message" sub-title="My card subtitle title" show-top-border="false">Inner content goes here</pf-card>', $scope);
113+
cardClass = angular.element(element).find('.loading-text');
114+
expect(cardClass.html()).toContain('Test Loading Message');
115+
116+
});
117+
88118
it("should hide the action bar footer by default", function() {
89119

90120
// by default, if footer not defined, footer should not be shown

test/charts/utilization-bar/utilization-bar.spec.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,10 @@ describe('Directive: pfUtilizationBarChart', function() {
106106
expect(emptyChart.length).toBe(1);
107107
});
108108

109+
it("should handle no data given", function() {
110+
element = compileChart('<pf-utilization-bar-chart chart-data=data2 chart-title=title units=units></pf-utilization-bar-chart>', $scope);
111+
utilizationBar = angular.element(element).find('.progress-bar-remaining').css('width');
112+
expect(utilizationBar).toBe("100%");
113+
});
114+
109115
});

0 commit comments

Comments
 (0)