I think the sample solution you give puts too much code into the controller. The controller should really only be worry about the list, and the HTML/Directives should be handling the display (including displaying the Select All checkbox). Also, all state changes are through the model, not by writing functions.
I've put together a solution on Plunker: http://plnkr.co/edit/gSeQL6XPaMsNSnlXwgHt?p=preview
Now, the controller just sets up the list:
app.controller('MainCtrl', function($scope) {
$scope.list = [{
isSelected: true,
desc: "Donkey"
}, {
isSelected: false,
desc: "Horse"
}];
});
and the view simply renders those out:
<div ng-repeat="elem in list">
<input type="checkbox" ng-model="elem.isSelected" /> {{elem.desc}}
</div>
For the Select All checkbox, I've created a new directive called checkbox-all
:
<input checkbox-all="list.isSelected" /> Select All
And that's it as far as use goes, which is hopefully simple... apart from writing that new directive:
app.directive('checkboxAll', function () {
return function(scope, iElement, iAttrs) {
var parts = iAttrs.checkboxAll.split('.');
iElement.attr('type','checkbox');
iElement.bind('change', function (evt) {
scope.$apply(function () {
var setValue = iElement.prop('checked');
angular.forEach(scope.$eval(parts[0]), function (v) {
v[parts[1]] = setValue;
});
});
});
scope.$watch(parts[0], function (newVal) {
var hasTrue, hasFalse;
angular.forEach(newVal, function (v) {
if (v[parts[1]]) {
hasTrue = true;
} else {
hasFalse = true;
}
});
if (hasTrue && hasFalse) {
iElement.attr('checked', false);
iElement.addClass('greyed');
} else {
iElement.attr('checked', hasTrue);
iElement.removeClass('greyed');
}
}, true);
};
});
The parts
variable breaks down the list.isSelected
into its two parts, so I can get the value of list
from the scope, an the isSelected
property in each object.
I add the type="checkbox"
property to the input element, making it a real checkbox for the browser. That means that the user can click on it, tab to it, etc.
I bind on the onchange
event rather than onclick
, as the checkbox can be changed in many ways, including via the keyboard. The onchange event runs inside a scope.$apply()
to ensure that the model changes get digested at the end.
Finally, I $watch
the input model for changes to the checkbox (the last true
allows me to watch complex objects). That means if the checkboxes are changed by the user or for some other reason, then the Select All checkbox is always kept in sync. That's much better than writing lots of ng-click handlers.
If the checkboxes are both checked and unchecked, then I set the master checkbox to unchecked and add the style 'greyed' (see style.css
). That CSS style basically sets the opacity to 30%, causing the checkbox to appear greyed, but it's still clickable; you can also tab to it and use spacebar to change its value.
I've tested in Firefox, Chrome and Safari, but I don't have IE to hand. Hopefully this works for you.