Indicate table loading, error, and empty states
This adds a loading indicator to the tables so the user can see when items are being loaded or there was an error loading the items. It also adds the "No items to display" message if there are no items. Closes-Bug: #1560541 Change-Id: I3f53e22e2899a562962d4a14734b23ad4f18ae29
This commit is contained in:
parent
c68b80b0bb
commit
19fdae7029
|
@ -74,3 +74,15 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Progress indicator in the table while items are loading */
|
||||
[table-status] {
|
||||
.progress {
|
||||
margin: 0px auto;
|
||||
width: 25%;
|
||||
height: $line-height-computed;
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,6 +46,8 @@
|
|||
var ctrl = this;
|
||||
ctrl.items = [];
|
||||
ctrl.src = [];
|
||||
ctrl.loading = true;
|
||||
ctrl.error = false;
|
||||
ctrl.checked = {};
|
||||
ctrl.loadbalancerId = $routeParams.loadbalancerId;
|
||||
ctrl.batchActions = batchActions.init(ctrl.loadbalancerId);
|
||||
|
@ -56,11 +58,21 @@
|
|||
////////////////////////////////
|
||||
|
||||
function init() {
|
||||
api.getListeners(ctrl.loadbalancerId).success(success);
|
||||
ctrl.src = [];
|
||||
ctrl.loading = true;
|
||||
ctrl.error = false;
|
||||
api.getListeners(ctrl.loadbalancerId).then(success, fail);
|
||||
}
|
||||
|
||||
function success(response) {
|
||||
ctrl.src = response.items;
|
||||
ctrl.src = response.data.items;
|
||||
ctrl.loading = false;
|
||||
}
|
||||
|
||||
function fail(/*response*/) {
|
||||
ctrl.src = [];
|
||||
ctrl.loading = false;
|
||||
ctrl.error = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,12 +18,17 @@
|
|||
|
||||
describe('LBaaS v2 Listeners Table Controller', function() {
|
||||
var controller, lbaasv2API, rowActions, batchActions;
|
||||
var items = [];
|
||||
var items = [{ foo: 'bar' }];
|
||||
var apiFail = false;
|
||||
|
||||
function fakeAPI() {
|
||||
return {
|
||||
success: function(callback) {
|
||||
callback({ items: items });
|
||||
then: function(success, fail) {
|
||||
if (apiFail && fail) {
|
||||
fail();
|
||||
} else {
|
||||
success({ data: { items: items } });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -64,6 +69,8 @@
|
|||
var ctrl = createController();
|
||||
expect(ctrl.items).toEqual([]);
|
||||
expect(ctrl.src).toEqual(items);
|
||||
expect(ctrl.loading).toBe(false);
|
||||
expect(ctrl.error).toBe(false);
|
||||
expect(ctrl.checked).toEqual({});
|
||||
expect(ctrl.loadbalancerId).toEqual('1234');
|
||||
expect(rowActions.init).toHaveBeenCalledWith(ctrl.loadbalancerId);
|
||||
|
@ -74,13 +81,16 @@
|
|||
});
|
||||
|
||||
it('should invoke lbaasv2 apis', function() {
|
||||
createController();
|
||||
var ctrl = createController();
|
||||
expect(lbaasv2API.getListeners).toHaveBeenCalled();
|
||||
expect(ctrl.src.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should init the rowactions', function() {
|
||||
createController();
|
||||
expect(lbaasv2API.getListeners).toHaveBeenCalled();
|
||||
it('should show error if loading fails', function() {
|
||||
apiFail = true;
|
||||
var ctrl = createController();
|
||||
expect(ctrl.src.length).toBe(0);
|
||||
expect(ctrl.error).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -112,6 +112,8 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr table-status table="table" column-count="7"></tr>
|
||||
|
||||
</tbody>
|
||||
<!--
|
||||
Table-footer:
|
||||
|
|
|
@ -46,6 +46,8 @@
|
|||
var ctrl = this;
|
||||
ctrl.items = [];
|
||||
ctrl.src = [];
|
||||
ctrl.loading = true;
|
||||
ctrl.error = false;
|
||||
ctrl.checked = {};
|
||||
ctrl.batchActions = batchActions;
|
||||
ctrl.rowActions = rowActions;
|
||||
|
@ -57,11 +59,20 @@
|
|||
////////////////////////////////
|
||||
|
||||
function init() {
|
||||
api.getLoadBalancers(true).success(success);
|
||||
ctrl.src = [];
|
||||
ctrl.loading = true;
|
||||
api.getLoadBalancers(true).then(success, fail);
|
||||
}
|
||||
|
||||
function success(response) {
|
||||
ctrl.src = response.items;
|
||||
ctrl.src = response.data.items;
|
||||
ctrl.loading = false;
|
||||
}
|
||||
|
||||
function fail(/*response*/) {
|
||||
ctrl.src = [];
|
||||
ctrl.error = true;
|
||||
ctrl.loading = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,12 +18,17 @@
|
|||
|
||||
describe('LBaaS v2 Load Balancers Table Controller', function() {
|
||||
var controller, lbaasv2API, scope;
|
||||
var items = [];
|
||||
var items = [{ foo: 'bar' }];
|
||||
var apiFail = false;
|
||||
|
||||
function fakeAPI() {
|
||||
return {
|
||||
success: function(callback) {
|
||||
callback({ items: items });
|
||||
then: function(success, fail) {
|
||||
if (apiFail && fail) {
|
||||
fail();
|
||||
} else {
|
||||
success({ data: { items: items } });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -55,6 +60,8 @@
|
|||
var ctrl = createController();
|
||||
expect(ctrl.items).toEqual([]);
|
||||
expect(ctrl.src).toEqual(items);
|
||||
expect(ctrl.loading).toBe(false);
|
||||
expect(ctrl.error).toBe(false);
|
||||
expect(ctrl.checked).toEqual({});
|
||||
expect(ctrl.batchActions).toBeDefined();
|
||||
expect(ctrl.rowActions).toBeDefined();
|
||||
|
@ -63,8 +70,16 @@
|
|||
});
|
||||
|
||||
it('should invoke lbaasv2 apis', function() {
|
||||
createController();
|
||||
var ctrl = createController();
|
||||
expect(lbaasv2API.getLoadBalancers).toHaveBeenCalled();
|
||||
expect(ctrl.src.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should show error if loading fails', function() {
|
||||
apiFail = true;
|
||||
var ctrl = createController();
|
||||
expect(ctrl.src.length).toBe(0);
|
||||
expect(ctrl.error).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -143,6 +143,8 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr table-status table="table" column-count="9"></tr>
|
||||
|
||||
</tbody>
|
||||
<!--
|
||||
Table-footer:
|
||||
|
|
|
@ -46,6 +46,8 @@
|
|||
var ctrl = this;
|
||||
ctrl.items = [];
|
||||
ctrl.src = [];
|
||||
ctrl.loading = true;
|
||||
ctrl.error = false;
|
||||
ctrl.checked = {};
|
||||
ctrl.loadbalancerId = $routeParams.loadbalancerId;
|
||||
ctrl.listenerId = $routeParams.listenerId;
|
||||
|
@ -58,11 +60,21 @@
|
|||
////////////////////////////////
|
||||
|
||||
function init() {
|
||||
api.getMembers(ctrl.poolId).success(success);
|
||||
ctrl.src = [];
|
||||
ctrl.loading = true;
|
||||
ctrl.error = false;
|
||||
api.getMembers(ctrl.poolId).then(success, fail);
|
||||
}
|
||||
|
||||
function success(response) {
|
||||
ctrl.src = response.items;
|
||||
ctrl.src = response.data.items;
|
||||
ctrl.loading = false;
|
||||
}
|
||||
|
||||
function fail(/*response*/) {
|
||||
ctrl.src = [];
|
||||
ctrl.loading = false;
|
||||
ctrl.error = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,12 +18,17 @@
|
|||
|
||||
describe('LBaaS v2 Members Table Controller', function() {
|
||||
var controller, lbaasv2API, scope;
|
||||
var items = [];
|
||||
var items = [{ foo: 'bar' }];
|
||||
var apiFail = false;
|
||||
|
||||
function fakeAPI() {
|
||||
return {
|
||||
success: function(callback) {
|
||||
callback({ items: items });
|
||||
then: function(success, fail) {
|
||||
if (apiFail && fail) {
|
||||
fail();
|
||||
} else {
|
||||
success({ data: { items: items } });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -60,6 +65,8 @@
|
|||
var ctrl = createController();
|
||||
expect(ctrl.items).toEqual([]);
|
||||
expect(ctrl.src).toEqual(items);
|
||||
expect(ctrl.loading).toBe(false);
|
||||
expect(ctrl.error).toBe(false);
|
||||
expect(ctrl.checked).toEqual({});
|
||||
expect(ctrl.loadbalancerId).toBeDefined();
|
||||
expect(ctrl.listenerId).toBeDefined();
|
||||
|
@ -68,8 +75,16 @@
|
|||
});
|
||||
|
||||
it('should invoke lbaasv2 apis', function() {
|
||||
createController();
|
||||
var ctrl = createController();
|
||||
expect(lbaasv2API.getMembers).toHaveBeenCalled();
|
||||
expect(ctrl.src.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should show error if loading fails', function() {
|
||||
apiFail = true;
|
||||
var ctrl = createController();
|
||||
expect(ctrl.src.length).toBe(0);
|
||||
expect(ctrl.error).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -73,6 +73,8 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr table-status table="table" column-count="6"></tr>
|
||||
|
||||
</tbody>
|
||||
<!--
|
||||
Table-footer:
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.project.lbaasv2')
|
||||
.directive('tableStatus', tableStatus);
|
||||
|
||||
tableStatus.$inject = [
|
||||
'horizon.dashboard.project.lbaasv2.basePath'
|
||||
];
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name horizon.dashboard.project.lbaasv2:tableStatus
|
||||
* @description
|
||||
* The `tableStatus` directive provides a status indicator while loading a table. The table
|
||||
* should have loading and error properties that give the status of the table, and an items
|
||||
* array that holds the items being displayed in the table. The column count can be provided
|
||||
* to fit the status row to an exact number of columns.
|
||||
* @restrict A
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* <tr table-status table="table" column-count="9"></tr>
|
||||
* ```
|
||||
*/
|
||||
|
||||
function tableStatus(basePath) {
|
||||
var directive = {
|
||||
restrict: 'A',
|
||||
templateUrl: basePath + 'widgets/table/table-status.html',
|
||||
scope: {
|
||||
table: '=',
|
||||
columnCount: '=?'
|
||||
}
|
||||
};
|
||||
return directive;
|
||||
}
|
||||
}());
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
function digestMarkup(scope, compile, markup) {
|
||||
var element = angular.element(markup);
|
||||
compile(element)(scope);
|
||||
scope.$apply();
|
||||
return element;
|
||||
}
|
||||
|
||||
describe('tableStatus directive', function() {
|
||||
var $scope, $compile, markup, table;
|
||||
|
||||
beforeEach(module('templates'));
|
||||
beforeEach(module('horizon.dashboard.project.lbaasv2'));
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
$compile = $injector.get('$compile');
|
||||
$scope = $injector.get('$rootScope').$new();
|
||||
table = {
|
||||
loading: true,
|
||||
error: false,
|
||||
items: []
|
||||
};
|
||||
$scope.table = table;
|
||||
markup = '<tr table-status table="table"></tr>';
|
||||
}));
|
||||
|
||||
it('initially shows loading status', function() {
|
||||
var element = digestMarkup($scope, $compile, markup);
|
||||
expect(element).toBeDefined();
|
||||
|
||||
expect(element.children().length).toBe(1);
|
||||
expect(element.children().first().hasClass('no-rows-help')).toBe(false);
|
||||
expect(element.find('.progress-bar').hasClass('progress-bar-striped')).toBe(true);
|
||||
expect(element.find('.progress-bar').hasClass('progress-bar-danger')).toBe(false);
|
||||
expect(element.find('.progress-bar > span').length).toBe(1);
|
||||
expect(element.find('.progress-bar > span').hasClass('sr-only')).toBe(true);
|
||||
});
|
||||
|
||||
it('indicates error status on error', function() {
|
||||
var element = digestMarkup($scope, $compile, markup);
|
||||
expect(element).toBeDefined();
|
||||
|
||||
table.loading = false;
|
||||
table.error = true;
|
||||
$scope.$apply();
|
||||
|
||||
expect(element.children().length).toBe(1);
|
||||
expect(element.children().first().hasClass('no-rows-help')).toBe(false);
|
||||
expect(element.find('.progress-bar').hasClass('progress-bar-striped')).toBe(false);
|
||||
expect(element.find('.progress-bar').hasClass('progress-bar-danger')).toBe(true);
|
||||
expect(element.find('.progress-bar > span').length).toBe(1);
|
||||
expect(element.find('.progress-bar > span').hasClass('sr-only')).toBe(false);
|
||||
expect(element.find('.progress-bar > span').text().trim())
|
||||
.toBe('An error occurred. Please try again later.');
|
||||
});
|
||||
|
||||
it('indicates no rows when there are no rows to display', function() {
|
||||
var element = digestMarkup($scope, $compile, markup);
|
||||
expect(element).toBeDefined();
|
||||
|
||||
table.loading = false;
|
||||
table.error = false;
|
||||
$scope.$apply();
|
||||
|
||||
expect(element.children().length).toBe(1);
|
||||
expect(element.children().first().hasClass('no-rows-help')).toBe(true);
|
||||
expect(element.find('.progress').length).toBe(0);
|
||||
expect(element.find('span').length).toBe(1);
|
||||
expect(element.find('span').text().trim()).toBe('No items to display.');
|
||||
});
|
||||
|
||||
it('goes away when done loading and there are rows to display', function() {
|
||||
var element = digestMarkup($scope, $compile, markup);
|
||||
expect(element).toBeDefined();
|
||||
|
||||
table.loading = false;
|
||||
table.error = false;
|
||||
table.items = ['foo'];
|
||||
$scope.$apply();
|
||||
|
||||
expect(element.children().length).toBe(0);
|
||||
});
|
||||
|
||||
});
|
||||
}());
|
|
@ -0,0 +1,12 @@
|
|||
<td ng-if="table.loading || table.error || table.items.length === 0"
|
||||
colspan="{$ columnCount || 100 $}"
|
||||
ng-class="{ 'no-rows-help': !table.loading && !table.error }">
|
||||
<span translate ng-if="!table.loading && !table.error">No items to display.</span>
|
||||
<div class="progress" ng-if="table.loading || table.error">
|
||||
<div class="progress-bar" role="progressbar"
|
||||
ng-class="{ 'progress-bar-striped active': !table.error, 'progress-bar-danger': table.error }">
|
||||
<span ng-if="!table.error" class="sr-only" translate>Loading</span>
|
||||
<span ng-if="table.error" translate>An error occurred. Please try again later.</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
Loading…
Reference in New Issue