I have an AngularJS directive that renders a collection of entities in the following template:
<table class="table"> <thead> <tr> <th><input type="checkbox" ng-click="selectAll()"></th> <th>Title</th> </tr> </thead> <tbody> <tr ng-repeat="e in entities"> <td><input type="checkbox" name="selected" ng-click="updateSelection($event, e.id)"></td> <td>{{e.title}}</td> </tr> </tbody> </table>
As you can see, it's a <table>
where each row can be selected individually with its own checkbox, or all rows can be selected at once with a master checkbox located in the <thead>
. Pretty classic UI.
What is the best way to:
<tr>
containing the entity to reflect its selected state)?<table>
)My current implementation is to add a custom controller to my directive:
controller: function($scope) { // Array of currently selected IDs. var selected = $scope.selected = []; // Update the selection when a checkbox is clicked. $scope.updateSelection = function($event, id) { var checkbox = $event.target; var action = (checkbox.checked ? 'add' : 'remove'); if (action == 'add' & selected.indexOf(id) == -1) selected.push(id); if (action == 'remove' && selected.indexOf(id) != -1) selected.splice(selected.indexOf(id), 1); // Highlight selected row. HOW?? // $(checkbox).parents('tr').addClass('selected_row', checkbox.checked); }; // Check (or uncheck) all checkboxes. $scope.selectAll = function() { // Iterate on all checkboxes and call updateSelection() on them?? }; }
More specifically, I wonder:
link
function?<tr>
of a given checkbox, or selecting all checkboxes in the template.$event
to updateSelection()
doesn't seem very elegant. Isn't there a better way to retrieve the state (checked/unchecked) of an element that was just clicked?Thank you.
# jQuery Code: Use .on('click',functino(){ $("#myCheckBox"). prop("checked", true); }); Here in above code on button click, we have use . prop() method and set attribute checked as true, which marked as checked.
AngularJS ng-checked Directive The ng-checked directive sets the checked attribute of a checkbox or a radiobutton. The checkbox, or radiobutton, will be checked if the expression inside the ng-checked attribute returns true.
The ng-click directive defines AngularJS code that will be executed when the element is being clicked.
This is the way I've been doing this sort of stuff. Angular tends to favor declarative manipulation of the dom rather than a imperative one(at least that's the way I've been playing with it).
The markup
<table class="table"> <thead> <tr> <th> <input type="checkbox" ng-click="selectAll($event)" ng-checked="isSelectedAll()"> </th> <th>Title</th> </tr> </thead> <tbody> <tr ng-repeat="e in entities" ng-class="getSelectedClass(e)"> <td> <input type="checkbox" name="selected" ng-checked="isSelected(e.id)" ng-click="updateSelection($event, e.id)"> </td> <td>{{e.title}}</td> </tr> </tbody> </table>
And in the controller
var updateSelected = function(action, id) { if (action === 'add' && $scope.selected.indexOf(id) === -1) { $scope.selected.push(id); } if (action === 'remove' && $scope.selected.indexOf(id) !== -1) { $scope.selected.splice($scope.selected.indexOf(id), 1); } }; $scope.updateSelection = function($event, id) { var checkbox = $event.target; var action = (checkbox.checked ? 'add' : 'remove'); updateSelected(action, id); }; $scope.selectAll = function($event) { var checkbox = $event.target; var action = (checkbox.checked ? 'add' : 'remove'); for ( var i = 0; i < $scope.entities.length; i++) { var entity = $scope.entities[i]; updateSelected(action, entity.id); } }; $scope.getSelectedClass = function(entity) { return $scope.isSelected(entity.id) ? 'selected' : ''; }; $scope.isSelected = function(id) { return $scope.selected.indexOf(id) >= 0; }; //something extra I couldn't resist adding :) $scope.isSelectedAll = function() { return $scope.selected.length === $scope.entities.length; };
EDIT: getSelectedClass()
expects the entire entity but it was being called with the id of the entity only, which is now corrected
I prefer to use the ngModel and ngChange directives when dealing with checkboxes. ngModel allows you to bind the checked/unchecked state of the checkbox to a property on the entity:
<input type="checkbox" ng-model="entity.isChecked">
Whenever the user checks or unchecks the checkbox the entity.isChecked
value will change too.
If this is all you need then you don't even need the ngClick or ngChange directives. Since you have the "Check All" checkbox, you obviously need to do more than just set the value of the property when someone checks a checkbox.
When using ngModel with a checkbox, it's best to use ngChange rather than ngClick for handling checked and unchecked events. ngChange is made for just this kind of scenario. It makes use of the ngModelController for data-binding (it adds a listener to the ngModelController's $viewChangeListeners
array. The listeners in this array get called after the model value has been set, avoiding this problem).
<input type="checkbox" ng-model="entity.isChecked" ng-change="selectEntity()">
... and in the controller ...
var model = {}; $scope.model = model; // This property is bound to the checkbox in the table header model.allItemsSelected = false; // Fired when an entity in the table is checked $scope.selectEntity = function () { // If any entity is not checked, then uncheck the "allItemsSelected" checkbox for (var i = 0; i < model.entities.length; i++) { if (!model.entities[i].isChecked) { model.allItemsSelected = false; return; } } // ... otherwise ensure that the "allItemsSelected" checkbox is checked model.allItemsSelected = true; };
Similarly, the "Check All" checkbox in the header:
<th> <input type="checkbox" ng-model="model.allItemsSelected" ng-change="selectAll()"> </th>
... and ...
// Fired when the checkbox in the table header is checked $scope.selectAll = function () { // Loop through all the entities and set their isChecked property for (var i = 0; i < model.entities.length; i++) { model.entities[i].isChecked = model.allItemsSelected; } };
CSS
What is the best way to... add a CSS class to the
<tr>
containing the entity to reflect its selected state?
If you use the ngModel approach for the data-binding, all you need to do is add the ngClass directive to the <tr>
element to dynamically add or remove the class whenever the entity property changes:
<tr ng-repeat="entity in model.entities" ng-class="{selected: entity.isChecked}">
See the full Plunker here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With