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:
Kelly Domico 2015-03-30 13:47:07 -07:00
parent a31b770bfb
commit 64a87d7ad9
2 changed files with 155 additions and 123 deletions

View File

@ -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

View File

@ -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);
});
});
});
});
}());