Modified hzSelectAll to select all visible checkboxes in table
The hzSelectAll directive should be used with the 'st-safe-src' attribute and should be passed in the displayed collection. When used with filtering and pagination, clicking on the select all checkbox will only mark the checkboxes of visible rows in the table. Change-Id: I1e2971f631bf11f5bcff85754b4927212f871f5e Closes-Bug: #1438351
This commit is contained in:
parent
a31b770bfb
commit
64a87d7ad9
46
horizon/static/angular/table/table.js
vendored
46
horizon/static/angular/table/table.js
vendored
@ -109,6 +109,11 @@
|
||||
$scope.numSelected--;
|
||||
}
|
||||
};
|
||||
|
||||
this.isSelected = function(row) {
|
||||
var rowState = $scope.selected[row.id];
|
||||
return rowState && rowState.checked;
|
||||
};
|
||||
},
|
||||
link: function(scope, element, attrs, stTableCtrl) {
|
||||
if (attrs.defaultSort) {
|
||||
@ -126,25 +131,31 @@
|
||||
* @description
|
||||
* The `hzSelectAll` directive updates the checkbox selection state of
|
||||
* every row in the table. Assign this as an attribute to a checkbox
|
||||
* input element, passing in the row collection data.
|
||||
* input element, passing in the displayed row collection data.
|
||||
*
|
||||
* Required: Use `st-table` attribute to pass in the displayed
|
||||
* row collection and `st-safe-src` attribute to pass in the
|
||||
* safe row collection.
|
||||
*
|
||||
* @restrict A
|
||||
* @scope rows: '=hzSelectAll'
|
||||
* @example
|
||||
*
|
||||
* ```
|
||||
* <input type='checkbox' hz-select-all='rowCollection'/>
|
||||
* <input type='checkbox' hz-select-all='displayedCollection'/>
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
app.directive('hzSelectAll', function($timeout) {
|
||||
app.directive('hzSelectAll', [ '$timeout', function($timeout) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '^hzTable',
|
||||
require: [ '^hzTable', '^stTable' ],
|
||||
scope: {
|
||||
rows: '=hzSelectAll'
|
||||
},
|
||||
link: function(scope, element, attrs, hzTableCtrl) {
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
var hzTableCtrl = ctrls[0];
|
||||
var stTableCtrl = ctrls[1];
|
||||
|
||||
// select or unselect all
|
||||
function clickHandler() {
|
||||
@ -156,13 +167,30 @@
|
||||
});
|
||||
}
|
||||
|
||||
// we need to watch rows so that
|
||||
// new rows are added to the hzTable selected
|
||||
// update the select all checkbox when table
|
||||
// state changes (sort, filter, paginate)
|
||||
function updateSelectAll() {
|
||||
var visibleRows = scope.rows;
|
||||
var checkedCnt = visibleRows.filter(hzTableCtrl.isSelected).length;
|
||||
element.prop('checked', visibleRows.length === checkedCnt);
|
||||
}
|
||||
|
||||
element.click(clickHandler);
|
||||
scope.$watch('rows.length', clickHandler);
|
||||
|
||||
// watch the table state for changes
|
||||
// on sort, filter and pagination
|
||||
scope.$watch(function() {
|
||||
return stTableCtrl.tableState();
|
||||
},
|
||||
updateSelectAll,
|
||||
true
|
||||
);
|
||||
|
||||
// watch the row length for add/removed rows
|
||||
scope.$watch('rows.length', updateSelectAll);
|
||||
}
|
||||
};
|
||||
});
|
||||
}]);
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
|
@ -1,146 +1,150 @@
|
||||
/* jshint browser: true, globalstrict: true */
|
||||
'use strict';
|
||||
/* jshint browser: true */
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
describe('hz.widget.table module', function() {
|
||||
it('should have been defined".', function () {
|
||||
expect(angular.module('hz.widget.table')).toBeDefined();
|
||||
describe('hz.widget.table module', function() {
|
||||
it('should have been defined".', function () {
|
||||
expect(angular.module('hz.widget.table')).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('table directives', function () {
|
||||
describe('table directives', function () {
|
||||
|
||||
var $scope, $timeout, $element;
|
||||
var $scope, $timeout, $element;
|
||||
|
||||
beforeEach(module('smart-table'));
|
||||
beforeEach(module('hz'));
|
||||
beforeEach(module('hz.widgets'));
|
||||
beforeEach(module('hz.widget.table'));
|
||||
beforeEach(module('smart-table'));
|
||||
beforeEach(module('hz'));
|
||||
beforeEach(module('hz.widgets'));
|
||||
beforeEach(module('hz.widget.table'));
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
var $compile = $injector.get('$compile');
|
||||
$timeout = $injector.get('$timeout');
|
||||
$scope = $injector.get('$rootScope').$new();
|
||||
beforeEach(inject(function($injector) {
|
||||
var $compile = $injector.get('$compile');
|
||||
$scope = $injector.get('$rootScope').$new();
|
||||
$timeout = $injector.get('$timeout');
|
||||
|
||||
$scope.fakeData = [
|
||||
{ id: '1', animal: 'cat' },
|
||||
{ id: '2', animal: 'dog' },
|
||||
{ id: '3', animal: 'fish' }
|
||||
];
|
||||
$scope.safeFakeData = [
|
||||
{ id: '1', animal: 'cat' },
|
||||
{ id: '2', animal: 'dog' },
|
||||
{ id: '3', animal: 'fish' }
|
||||
];
|
||||
|
||||
var markup =
|
||||
'<table st-table="fakeData" hz-table>' +
|
||||
'<thead>' +
|
||||
'<tr>' +
|
||||
'<th><input type="checkbox" hz-select-all="fakeData" ' +
|
||||
'ng-checked="numSelected === fakeData.length"/></th>' +
|
||||
'<th></th>' +
|
||||
'<th>Animal</th>' +
|
||||
'</tr>'+
|
||||
'</thead>' +
|
||||
'<tbody>' +
|
||||
'<tr ng-repeat-start="row in fakeData">' +
|
||||
'<td><input type="checkbox" ng-model="selected[row.id].checked" ' +
|
||||
'ng-change="updateSelectCount(row)"/></td>' +
|
||||
'<td><i class="fa fa-chevron-right" hz-expand-detail></i></td>' +
|
||||
'<td>{{ row.animal }}</td>' +
|
||||
'</tr>' +
|
||||
'<tr class="detail-row" ng-repeat-end>' +
|
||||
'<td class="detail" colspan="3"></td>' +
|
||||
'</tr>' +
|
||||
'</tbody>' +
|
||||
'</table>';
|
||||
$scope.fakeData = [];
|
||||
|
||||
$element = angular.element(markup);
|
||||
$compile($element)($scope);
|
||||
var markup =
|
||||
'<table st-table="fakeData" st-safe-src="safeFakeData" hz-table>' +
|
||||
'<thead>' +
|
||||
'<tr>' +
|
||||
'<th><input type="checkbox" hz-select-all="fakeData" ' +
|
||||
'ng-checked="numSelected === fakeData.length"/></th>' +
|
||||
'<th></th>' +
|
||||
'<th>Animal</th>' +
|
||||
'</tr>' +
|
||||
'</thead>' +
|
||||
'<tbody>' +
|
||||
'<tr ng-repeat-start="row in fakeData">' +
|
||||
'<td><input type="checkbox" ng-model="selected[row.id].checked" ' +
|
||||
'ng-change="updateSelectCount(row)"/></td>' +
|
||||
'<td><i class="fa fa-chevron-right" hz-expand-detail></i></td>' +
|
||||
'<td>{{ row.animal }}</td>' +
|
||||
'</tr>' +
|
||||
'<tr class="detail-row" ng-repeat-end>' +
|
||||
'<td class="detail" colspan="3"></td>' +
|
||||
'</tr>' +
|
||||
'</tbody>' +
|
||||
'</table>';
|
||||
|
||||
$scope.$digest();
|
||||
}));
|
||||
|
||||
describe('hzTable directive', function() {
|
||||
|
||||
it('should have 3 summary rows', function() {
|
||||
expect($element.find('tbody tr[ng-repeat-start]').length).toBe(3);
|
||||
});
|
||||
|
||||
it('should have 3 detail rows', function() {
|
||||
expect($element.find('tbody tr.detail-row').length).toBe(3);
|
||||
});
|
||||
|
||||
it('should have each checkbox initially unchecked', function() {
|
||||
var checkboxes = $element.find('tbody tr[ng-repeat-start] input[type="checkbox"]');
|
||||
angular.forEach(checkboxes, function(checkbox) {
|
||||
expect(checkbox.checked).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have numSelected === 1 when first checkbox is clicked', function() {
|
||||
var firstInput = $element.find('tbody input[type="checkbox"]').first();
|
||||
var ngModelCtrl = firstInput.controller('ngModel');
|
||||
ngModelCtrl.$setViewValue(true);
|
||||
$element = angular.element(markup);
|
||||
$compile($element)($scope);
|
||||
|
||||
$scope.$digest();
|
||||
}));
|
||||
|
||||
expect($element.scope().numSelected).toBe(1);
|
||||
});
|
||||
describe('hzTable directive', function() {
|
||||
|
||||
it('should have numSelected === 3 and select-all checkbox checked when all rows selected', function() {
|
||||
var checkboxes = $element.find('tbody input[type="checkbox"]');
|
||||
angular.forEach(checkboxes, function(checkbox) {
|
||||
checkbox.checked = true;
|
||||
var ngModelCtrl = angular.element(checkbox).controller('ngModel');
|
||||
it('should have 3 summary rows', function() {
|
||||
expect($element.find('tbody tr[ng-repeat-start]').length).toBe(3);
|
||||
});
|
||||
|
||||
it('should have 3 detail rows', function() {
|
||||
expect($element.find('tbody tr.detail-row').length).toBe(3);
|
||||
});
|
||||
|
||||
it('should have each checkbox initially unchecked', function() {
|
||||
var checkboxes = $element.find('tbody tr[ng-repeat-start] input[type="checkbox"]');
|
||||
angular.forEach(checkboxes, function(checkbox) {
|
||||
expect(checkbox.checked).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have numSelected === 1 when first checkbox is clicked', function() {
|
||||
var firstInput = $element.find('tbody input[type="checkbox"]').first();
|
||||
var ngModelCtrl = firstInput.controller('ngModel');
|
||||
ngModelCtrl.$setViewValue(true);
|
||||
|
||||
$scope.$digest();
|
||||
|
||||
expect($element.scope().numSelected).toBe(1);
|
||||
});
|
||||
|
||||
$scope.$digest();
|
||||
it('should have numSelected === 3 and select-all checkbox checked when all rows selected', function() {
|
||||
var checkboxes = $element.find('tbody input[type="checkbox"]');
|
||||
angular.forEach(checkboxes, function(checkbox) {
|
||||
checkbox.checked = true;
|
||||
var ngModelCtrl = angular.element(checkbox).controller('ngModel');
|
||||
ngModelCtrl.$setViewValue(true);
|
||||
});
|
||||
|
||||
expect($element.scope().numSelected).toBe(3);
|
||||
expect($element.find('thead input[hz-select-all]')[0].checked).toBe(true);
|
||||
});
|
||||
$scope.$digest();
|
||||
|
||||
});
|
||||
|
||||
describe('hzSelectAll directive', function() {
|
||||
|
||||
it('should select all checkboxes if select-all checkbox checked', function() {
|
||||
var selectAll = $element.find('input[hz-select-all]').first();
|
||||
selectAll[0].checked = true;
|
||||
selectAll.triggerHandler('click');
|
||||
$timeout.flush();
|
||||
|
||||
expect($element.scope().numSelected).toBe(3);
|
||||
var checkboxes = $element.find('tbody input[type="checkbox"]');
|
||||
angular.forEach(checkboxes, function(checkbox) {
|
||||
expect(checkbox.checked).toBe(true);
|
||||
expect($element.scope().numSelected).toBe(3);
|
||||
expect($element.find('thead input[hz-select-all]')[0].checked).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
describe('hzSelectAll directive', function() {
|
||||
|
||||
describe('hzExpandDetail directive', function() {
|
||||
it('should select all checkboxes if select-all checkbox checked', function() {
|
||||
var selectAll = $element.find('input[hz-select-all]').first();
|
||||
selectAll[0].checked = true;
|
||||
selectAll.triggerHandler('click');
|
||||
|
||||
it('should have summary row with class "expanded" when expanded', function() {
|
||||
var expandIcon = $element.find('i.fa').first();
|
||||
expandIcon.click();
|
||||
$timeout.flush();
|
||||
|
||||
expect($element.scope().numSelected).toBe(3);
|
||||
var checkboxes = $element.find('tbody input[type="checkbox"]');
|
||||
angular.forEach(checkboxes, function(checkbox) {
|
||||
expect(checkbox.checked).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
var summaryRow = expandIcon.closest('tr');
|
||||
expect(summaryRow.hasClass('expanded')).toBe(true);
|
||||
});
|
||||
|
||||
it('should have summary row without class "expanded" when not expanded', function(done) {
|
||||
var expandIcon = $element.find('i.fa').first();
|
||||
describe('hzExpandDetail directive', function() {
|
||||
|
||||
// Click twice to mock expand and collapse
|
||||
expandIcon.click();
|
||||
expandIcon.click();
|
||||
it('should have summary row with class "expanded" when expanded', function() {
|
||||
var expandIcon = $element.find('i.fa').first();
|
||||
expandIcon.click();
|
||||
|
||||
// Wait for the slide down animation to complete before test
|
||||
setTimeout(function() {
|
||||
var summaryRow = expandIcon.closest('tr');
|
||||
expect(summaryRow.hasClass('expanded')).toBe(false);
|
||||
done();
|
||||
}, 2000);
|
||||
expect(summaryRow.hasClass('expanded')).toBe(true);
|
||||
});
|
||||
|
||||
it('should have summary row without class "expanded" when not expanded', function(done) {
|
||||
var expandIcon = $element.find('i.fa').first();
|
||||
|
||||
// Click twice to mock expand and collapse
|
||||
expandIcon.click();
|
||||
expandIcon.click();
|
||||
|
||||
// Wait for the slide down animation to complete before test
|
||||
setTimeout(function() {
|
||||
var summaryRow = expandIcon.closest('tr');
|
||||
expect(summaryRow.hasClass('expanded')).toBe(false);
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}());
|
Loading…
x
Reference in New Issue
Block a user