Як реагувати на клацання прапорця в директиві 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.