【问题标题】:How to respond to clicks on a checkbox in an AngularJS directive?如何响应单击 AngularJS 指令中的复选框?
【发布时间】:2012-08-08 20:41:40
【问题描述】:

我有一个 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>

如您所见,这是一个&lt;table&gt;,其中每一行都可以使用自己的复选框单独选择,或者可以使用位于&lt;thead&gt; 中的主复选框一次选择所有行。非常经典的用户界面。

最好的方法是:

  • 选择单行(即选中复选框时,将所选实体的id添加到内部数组,并在包含实体的&lt;tr&gt;添加CSS类以反映其选定状态)?
  • 一次选择所有行? (即对&lt;table&gt; 中的所有行执行前面描述的操作)

我目前的实现是在我的指令中添加一个自定义控制器:

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,我很难只选择给定复选框的父 &lt;tr&gt;,或者选择模板中的所有复选框。
  • $event 传递给updateSelection() 似乎不太优雅。难道没有更好的方法来检索刚刚单击的元素的状态(选中/未选中)吗?

谢谢。

【问题讨论】:

    标签: javascript angularjs


    【解决方案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;
    };
    

    编辑getSelectedClass() 需要整个实体,但它仅使用实体的 id 进行调用,现在已更正

    【讨论】:

    • 谢谢,利维!那行得通,而且很有帮助。多亏了你,我了解了ngChecked 指令。 (我唯一的遗憾是我们不能让这段代码不那么冗长。)
    • 不要认为它是冗长的,从关注点分离的角度来考虑。您的数据模型不应该知道它的呈现方式。请记住,在控制器中没有提到 tr 或 td。最多它包含复选框,但也可以排除。你总是可以把你的控制器应用到第二个模板上;)
    • 感谢您的提问和回答。我很想知道这种方法的效率影响,所以我做了这个 plunkr:plnkr.co/edit/T5aZO3s5DzSnbrLELveG 我注意到每次我选择一个项目时,isSelected 被调用 6 次(每个转发器项目两次)。知道为什么每次都会发生两次吗?有人担心在页面上投放 100 多个中继器项目并在移动设备上运行吗?可能不是问题...
    • @Aaronius 如果您在 isSelected 函数中添加断点并刷新,您会看到在解析和执行指令的内容之前调用了它。我认为因为它是一个替换所有绑定函数的指令,所以被调用了两次
    • 有没有办法只知道选中的复选框?
    【解决方案2】:

    dealing with checkboxes 时,我更喜欢使用ngModelngChange 指令。 ngModel 允许您将复选框的选中/未选中状态绑定到实体上的属性:

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

    每当用户选中或取消选中复选框时,entity.isChecked 的值也会发生变化。

    如果这就是您所需要的,那么您甚至不需要 ngClick 或 ngChange 指令。由于您有“全选”复选框,因此当有人选中复选框时,您显然需要做的不仅仅是设置属性的值。

    当使用带有复选框的 ngModel 时,最好使用 ngChange 而不是 ngClick 来处理选中和未选中的事件。 ngChange 就是针对这种情况而设计的。它利用ngModelController 进行数据绑定(它向ngModelController 的$viewChangeListeners 数组添加了一个侦听器。在设置模型值avoiding this problem 之后,该数组中的侦听器被调用 )。

    <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 类添加到包含实体的 &lt;tr&gt; 以反映其选定状态?

    如果您使用 ngModel 方法进行数据绑定,您只需将 ngClass 指令添加到 &lt;tr&gt; 元素,以便在实体属性更改时动态添加或删除类:

    <tr ng-repeat="entity in model.entities" ng-class="{selected: entity.isChecked}">
    

    查看完整的Plunker here

    【讨论】:

    • allItemsSelected 标志在开始时设置为 false,然后在单击全选复选框时如何设置为 true。你能解释一下吗?
    【解决方案3】:

    Liviu 的回答对我非常有帮助。希望这不是坏的形式,但我做了一个fiddle,将来可能会帮助其他人。

    需要的两个重要部分是:

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

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-22
    • 1970-01-01
    • 2014-12-31
    • 2016-07-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多