Как реагировать на клики на флажке в директиве AngularJS?
У меня есть AngularJS directive, который отображает коллекцию объектов в следующем шаблоне:
<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>
Как вы можете видеть, это a <table>
, где каждая строка может быть выбрана индивидуально со своим собственным флажком или все строки могут быть выбраны сразу с помощью мастер-флажка, расположенного в <thead>
. Довольно классический интерфейс.
Каков наилучший способ:
- Выберите одну строку (то есть, когда установлен флажок, добавьте идентификатор выбранного объекта во внутренний массив и добавьте класс CSS в
<tr>
, содержащий объект, чтобы отразить его выбранное состояние)?
- Выберите все строки сразу? (т.е. выполнить описанные выше действия для всех строк в
<table>
)
Моя текущая реализация заключается в добавлении настраиваемого контроллера в мою директиву:
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??
};
}
В частности, мне интересно:
- Используется ли код выше в контроллере или должен ли он работать с функцией
link
?
- Учитывая, что jQuery не обязательно присутствует (AngularJS не требует его), какой лучший способ сделать обход DOM? Без jQuery мне сложно выбрать родительский
<tr>
данного флажка или выбрать все флажки в шаблоне.
- Передача
$event
в updateSelection()
не кажется очень элегантной. Разве нет лучшего способа получить состояние (отмечено или не отмечено) элемента, который был просто нажат?
Спасибо.
Ответы
Ответ 1
Так я делал такие вещи. Angular стремится к декларативной манипуляции с dom, а не к императивному (по крайней мере, к тому, как я играл с ним).
Разметка
<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>
И в контроллере
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()
ожидает весь объект, но он вызывается только с идентификатором объекта, который теперь исправлен
Ответ 2
Я предпочитаю использовать ngModel и ngChange, когда работает с флажками. ngModel позволяет привязать проверяемое/непроверенное состояние этого флажка к свойству объекта:
<input type="checkbox" ng-model="entity.isChecked">
Всякий раз, когда пользователь проверяет или снимает флажок, значение entity.isChecked
также изменится.
Если это все, что вам нужно, вам даже не нужны директивы ngClick или ngChange. Поскольку у вас установлен флажок "Проверить все", вам явно нужно сделать больше, чем просто установить значение свойства, когда кто-то проверяет флажок.
При использовании ngModel с флажком лучше использовать ngChange, а не ngClick для обработки отмеченных и непроверенных событий. ngChange выполняется только для такого сценария. Он использует ngModelController для привязки данных (он добавляет слушателя в массив ngModelController $viewChangeListeners
. Слушатели в этом массиве вызывается после того, как значение модели было установлено, избегая этой проблемы).
<input type="checkbox" ng-model="entity.isChecked" ng-change="selectEntity()">
... и в контроллере...
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;
};
Аналогично, флажок "Проверить все" в заголовке:
<th>
<input type="checkbox" ng-model="model.allItemsSelected" ng-change="selectAll()">
</th>
... и...
// 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
Каков наилучший способ... добавить класс CSS в <tr>
, содержащий объект, чтобы отразить его выбранное состояние?
Если вы используете подход ngModel для привязки данных, все, что вам нужно сделать, это добавить директиву ngClass в <tr>
для динамического добавления или удаления класса всякий раз, когда свойство объекта изменяется:
<tr ng-repeat="entity in model.entities" ng-class="{selected: entity.isChecked}">
Смотрите полный Plunker здесь.
Ответ 3
Ливийский ответ был очень полезен для меня. Надеюсь, это неплохая форма, но я сделал fiddle, что может помочь кому-то еще в будущем.
Необходимы две важные части:
$scope.entities = [{
"title": "foo",
"id": 1
}, {
"title": "bar",
"id": 2
}, {
"title": "baz",
"id": 3
}];
$scope.selected = [];