Як реагувати на клацання прапорця в директиві AngularJS?


79

У мене є директива AngularJS, яка відображає колекцію сутностей у такому шаблоні:

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

Як ви можете бачити, це те, <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()здається не дуже елегантним. Чи не існує кращого способу отримати стан (позначений / не позначений) елемента, на який щойно клацнули?

Дякую.

Відповіді:


122

Це спосіб, яким я займався подібними речами. Angular має тенденцію віддавати перевагу декларативному маніпулюванню будинком, а не імперативному (принаймні, таким чином я з ним грав).

Розмітка

<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;
};

РЕДАГУВАТИ : getSelectedClass()очікується ціла сутність, але її викликали лише з ідентифікатором сутності, що тепер виправлено


Дякую, Лівіу! Це працює, і це допомогло. І завдяки вам я дізнався про ngCheckedдирективу. (Я шкодую лише про те, що ми не можемо зробити цей код дещо детальнішим.)
AngularChef

1
Не думайте про це як про багатослів'я, думайте про це з точки зору поділу проблем. Ваші моделі даних не повинні знати про спосіб їх подання. Пам'ятайте, що в контролері немає жодної згадки про tr або td. щонайбільше він містить прапорець, але це також може бути враховано. Ви завжди можете взяти свій контролер і застосувати його до другого шаблону;)
Liviu T.

Дякую за це запитання та відповідь. Мені було цікаво дізнатись про наслідки ефективності цього підходу, тому я зробив цей додаток: plnkr.co/edit/T5aZO3s5DzSnbrLELveG Я помітив, що кожного разу, коли я вибираю один із елементів, isSelected викликається 6 разів (двічі для кожного елемента-ретранслятора). Будь-яка ідея, чому це відбувається двічі для кожного? Хтось стурбований тим, як кинути на сторінку понад 100 предметів-повторювачів та запустити їх на мобільному пристрої? Можливо, це не проблема ...
Аароній,

@Aaronius Якщо ви додасте точку зупинки у функцію isSelected та оновите, ви побачите, що вона викликається до аналізу та виконання вмісту директиви. Думаю, оскільки це директива, яка виконує заміну, всі зв’язані функції викликаються двічі
Liviu T.

Чи існує спосіб дізнатися лише вибрані прапорці?
Сана Джозеф

35

Я вважаю за краще використовувати директиви ngModel та ngChange при роботі з прапорцями . ngModel дозволяє прив'язати позначений / невстановлений стан прапорця до властивості сутності:

<input type="checkbox" ng-model="entity.isChecked">

Щоразу, коли користувач ставить або знімає прапорець, entity.isCheckedзначення також змінюється.

Якщо це все, що вам потрібно, то вам навіть не потрібні директиви ngClick або ngChange. Оскільки у вас є прапорець "Позначити все", вам, очевидно, потрібно робити більше, ніж просто встановлювати значення властивості, коли хтось ставить прапорець.

При використанні ngModel із прапорцем, краще використовувати ngChange, а не ngClick для обробки перевірених та неперевірених подій. ngChange створений саме для цього сценарію. Він використовує ngModelController для прив'язки даних (він додає слухач до $viewChangeListenersмасиву ngModelController . Слухачі в цьому масиві викликаються після встановлення значення моделі, уникаючи цієї проблеми ).

<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 дивіться тут .


allItemsSelected прапорець спочатку встановлюється як false, а потім, як він встановлюється в true, коли клацніть прапорець All. Ви можете пояснити?
user2514925 04

11

Відповідь Лівіу була для мене надзвичайно корисною. Сподіваюся, це не погана форма, але я зробив скрипку, яка може допомогти комусь іншому в майбутньому.

Потрібні дві важливі частини:

    $scope.entities = [{
    "title": "foo",
    "id": 1
}, {
    "title": "bar",
    "id": 2
}, {
    "title": "baz",
    "id": 3
}];
$scope.selected = [];

1
Кутові документи мають простішу відповідь для перевірки всієї частини. docs.angularjs.org/api/ng.directive:ngChecked . Збір перевіреного - це те, що я намагаюся з’ясувати.
Hayden
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.