Javascript 如何获得带有选择/取消选择所有功能和不确定值的 angular.js 复选框?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12648466/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 10:56:34  来源:igfitidea点击:

How can I get angular.js checkboxes with select/unselect all functionality and indeterminate values?

javascripthtmlcheckboxangularjstri-state-logic

提问by Janus Troelsen

I am looking for something exactly like these(tri-state checkboxes with "parents"). But using that solution wouldn't be elegant, as I do not depend on jQuery right now, and I would need to call $scope.$apply to get the model to recognize the automatically (un)checked checkboxed jQuery clicked.

我正在寻找与这些完全相同的东西(带有“父母”的三态复选框)。但是使用该解决方案不会很优雅,因为我现在不依赖于 jQuery,我需要调用 $scope.$apply 来让模型识别点击的自动(未)选中复选框的 jQuery。

Here's a bug for angular.jsthat requests ng-indeterminate-value implemented. But that still wouldn't give me the synchronization to all the children, which is something I don't think should be a part of my controller.

这是 angular.js 的一个错误,它要求实现 ng-indeterminate-value。但这仍然不能让我与所有孩子同步,我认为这不应该成为我的控制器的一部分。

What I am looking for would be something like this:

我正在寻找的是这样的:

  • A "ng-children-model" directive with syntax like: <input type="checkbox" ng-children-model="child.isSelected for child in listelements">. The list of booleans would be computed, and if 0 selected -> checkbox false. If all selected -> checkbox true. Else -> checkbox indeterminate.
  • In my controller, I would have something like this: $scope.listelements = [{isSelected: true, desc: "Donkey"},{isSelected: false, desc: "Horse"}]
  • The checkboxes would be made as usual with <tr ng-repeat="elem in listelements"><td><input type="checkbox" ng-model="elem.isSelected"></td><td>{{elem.desc}}</td></tr>.
  • As I understand it, the browser will determine which state a clicked indeterminate checkbox goes into.
  • A“NG-儿童模型”指令用的语法:<input type="checkbox" ng-children-model="child.isSelected for child in listelements">。将计算布尔值列表,如果选择 0 -> 复选框为 false。如果全部选中 -> 复选框为真。否则 -> 复选框不确定。
  • 在我的控制器中,我会有这样的事情: $scope.listelements = [{isSelected: true, desc: "Donkey"},{isSelected: false, desc: "Horse"}]
  • 复选框将像往常一样使用<tr ng-repeat="elem in listelements"><td><input type="checkbox" ng-model="elem.isSelected"></td><td>{{elem.desc}}</td></tr>.
  • 据我了解,浏览器将确定单击的不确定复选框进入哪个状态。

采纳答案by Mark Rajcok

Since you want a new type/kind of component, this sounds like a good case for a custom directive.
Since the parent/master/tri-stated checkbox and the individual dual-state checkboxes need to interact with each other, I suggest a single directive, with its own controller, to handle the logic.

由于您想要一种新的类型/种类的组件,这听起来像是自定义指令的好例子。
由于父/主/三态复选框和单独的双态复选框需要相互交互,我建议使用一个带有自己的控制器的指令来处理逻辑。

<tri-state-checkbox checkboxes="listelements"></tri-state-checkbox>

Directive:

指示:

app.directive('triStateCheckbox', function() {
  return {
    replace: true,
    restrict: 'E',
    scope: { checkboxes: '=' },
    template: '<div><input type="checkbox" ng-model="master" ng-change="masterChange()">'
      + '<div ng-repeat="cb in checkboxes">'
      + '<input type="checkbox" ng-model="cb.isSelected" ng-change="cbChange()">{{cb.desc}}'
      + '</div>'
      + '</div>',
    controller: function($scope, $element) {
      $scope.masterChange = function() {
        if($scope.master) {
          angular.forEach($scope.checkboxes, function(cb, index){
            cb.isSelected = true;
          });
        } else {
          angular.forEach($scope.checkboxes, function(cb, index){
            cb.isSelected = false;
          });
        }
      };
      var masterCb = $element.children()[0];
      $scope.cbChange = function() {
        var allSet = true, allClear = true;
        angular.forEach($scope.checkboxes, function(cb, index){
          if(cb.isSelected) {
            allClear = false;
          } else {
            allSet = false;
          }
        });
        if(allSet)        { 
          $scope.master = true; 
          masterCb.indeterminate = false;
        }
        else if(allClear) { 
          $scope.master = false; 
          masterCb.indeterminate = false;
        }
        else { 
          $scope.master = false;
          masterCb.indeterminate = true;
        }
      };
      $scope.cbChange();  // initialize
    },
  };
});

Change the template to suit your needs, or use an external template with templateUrl.

更改模板以满足您的需要,或使用带有 templateUrl 的外部模板。

The directive assumes that the checkboxes array contains objects that have an isSelectedproperty and a descproperty.

该指令假定 checkboxes 数组包含具有isSelected属性和desc属性的对象。

Plunker.

普朗克

Update: If you prefer to have the directive only render the tri-stated checkbox, hence the individual checkboxes are in the HTML (like @Piran's solution), here's another plunkervariation for that. For this plunker, the HTML would be:

更新:如果您更喜欢让指令只呈现三态复选框,因此各个复选框都在 HTML 中(如@Piran的解决方案),这是另一个 plunker变体。对于这个 plunker,HTML 将是:

<tri-state-checkbox checkboxes="listelements" class="select-all-cb">
</tri-state-checkbox>select all
<div ng-repeat="item in listelements">
   <input type="checkbox" ng-model="item.isSelected"> {{item.desc}}
</div>

回答by Piran

I think the sample solution you give puts too much code into the controller. The controller should really only be worry about the list, and the HTML/Directives should be handling the display (including displaying the Select All checkbox). Also, all state changes are through the model, not by writing functions.

我认为您提供的示例解决方案在控制器中放入了太多代码。控制器应该只关心列表,HTML/指令应该处理显示(包括显示全选复选框)。此外,所有状态更改都通过模型,而不是通过编写函数。

I've put together a solution on Plunker: http://plnkr.co/edit/gSeQL6XPaMsNSnlXwgHt?p=preview

我在 Plunker 上整理了一个解决方案:http://plnkr.co/edit/gSeQL6XPaMsNSnlXwgHt?p=preview

Now, the controller just sets up the list:

现在,控制器只设置列表:

app.controller('MainCtrl', function($scope) {
    $scope.list = [{
        isSelected: true,
        desc: "Donkey"
    }, {
        isSelected: false,
        desc: "Horse"
    }];
});

and the view simply renders those out:

并且视图只是将它们呈现出来:

<div ng-repeat="elem in list">
  <input type="checkbox" ng-model="elem.isSelected" /> {{elem.desc}}
</div>

For the Select All checkbox, I've created a new directive called checkbox-all:

对于 Select All 复选框,我创建了一个名为 的新指令checkbox-all

  <input checkbox-all="list.isSelected" /> Select All

And that's it as far as use goes, which is hopefully simple... apart from writing that new directive:

就使用而言,就是这样,希望很简单……除了编写新指令:

app.directive('checkboxAll', function () {
  return function(scope, iElement, iAttrs) {
    var parts = iAttrs.checkboxAll.split('.');
    iElement.attr('type','checkbox');
    iElement.bind('change', function (evt) {
      scope.$apply(function () {
        var setValue = iElement.prop('checked');
        angular.forEach(scope.$eval(parts[0]), function (v) {
          v[parts[1]] = setValue;
        });
      });
    });
    scope.$watch(parts[0], function (newVal) {
      var hasTrue, hasFalse;
      angular.forEach(newVal, function (v) {
        if (v[parts[1]]) {
          hasTrue = true;
        } else {
          hasFalse = true;
        }
      });
      if (hasTrue && hasFalse) {
        iElement.attr('checked', false);
        iElement.addClass('greyed');
      } else {
        iElement.attr('checked', hasTrue);
        iElement.removeClass('greyed');
      }
    }, true);
  };
});

The partsvariable breaks down the list.isSelectedinto its two parts, so I can get the value of listfrom the scope, an the isSelectedproperty in each object.

parts变量将 分解list.isSelected为两部分,因此我可以list从作用域中获取 的值,即isSelected每个对象中的属性。

I add the type="checkbox"property to the input element, making it a real checkbox for the browser. That means that the user can click on it, tab to it, etc.

我将该type="checkbox"属性添加到输入元素,使其成为浏览器的真正复选框。这意味着用户可以点击它,选择它等等。

I bind on the onchangeevent rather than onclick, as the checkbox can be changed in many ways, including via the keyboard. The onchange event runs inside a scope.$apply()to ensure that the model changes get digested at the end.

我绑定onchange事件而不是onclick,因为可以通过多种方式更改复选框,包括通过键盘。onchange 事件在 a 内运行scope.$apply()以确保模型更改在最后得到消化。

Finally, I $watchthe input model for changes to the checkbox (the last trueallows me to watch complex objects). That means if the checkboxes are changed by the user or for some other reason, then the Select All checkbox is always kept in sync. That's much better than writing lots of ng-click handlers.

最后,我$watch将输入模型更改为复选框(最后一个true允许我观察复杂的对象)。这意味着如果用户或其他原因更改了复选框,则 Select All 复选框始终保持同步。这比编写大量 ng-click 处理程序要好得多。

If the checkboxes are both checked and unchecked, then I set the master checkbox to unchecked and add the style 'greyed' (see style.css). That CSS style basically sets the opacity to 30%, causing the checkbox to appear greyed, but it's still clickable; you can also tab to it and use spacebar to change its value.

如果复选框既选中又未选中,那么我将主复选框设置为未选中并添加样式“灰色”(请参阅​​ 参考资料style.css)。该 CSS 样式基本上将不透明度设置为 30%,导致复选框显示为灰色,但它仍然可以点击;您也可以使用 Tab 键并使用空格键更改其值。

I've tested in Firefox, Chrome and Safari, but I don't have IE to hand. Hopefully this works for you.

我已经在 Firefox、Chrome 和 Safari 中进行了测试,但我手头没有 IE。希望这对你有用。

回答by wldaunfr

Here's a refined versionof Piran's solution. Using .prop()instead of .attr()fixes the checkedissue.

这是 Piran 解决方案的改进版本。使用.prop()而不是.attr()解决checked问题。

Usage:

用法:

<div ng-repeat="elem in list">
    <input type="checkbox" ng-model="elem.isSelected" /> {{elem.desc}}
</div>
<ui-select-all items="list" prop="isSelected"></ui-select-all> Select all

回答by ganaraj

I believe that you should only be creating a directive if you only need to do some kind of a DOM manipulation or want to abstract away a lot of DOM manipulative behaviour into a "re-usable" component.

我相信,如果您只需要执行某种 DOM 操作或想要将大量 DOM 操作行为抽象为“可重用”组件,您应该只创建一个指令。

Here is a solution which achieves the same thing that you were attempting, but, this does only the logic in the controllers... If you want to keep the controllers lean, then you could push away all this logic into service...A service would also be a good place to do this, if you want to re-use this in multiple places..

这是一个解决方案,它实现了与您正在尝试的相同的事情,但是,这只适用于控制器中的逻辑......如果您想保持控制器精益,那么您可以将所有这些逻辑推向服务......A如果你想在多个地方重复使用它,服务也是一个很好的地方。

http://plnkr.co/edit/hNTeZ8Tuht3T9NuY7HRi?p=preview

http://plnkr.co/edit/hNTeZ8Tuht3T9NuY7HRi?p=preview

Note that there is no DOM manipulation in the controller. We are achieving the effect we require using a bunch of directives that are provided with Angular. No new directive required.. I really dont think you should use a directive to abstract away logic..

请注意,控制器中没有 DOM 操作。我们正在使用 Angular 提供的一堆指令来实现我们需要的效果。不需要新指令..我真的不认为你应该使用指令来抽象逻辑..

Hope this helps..

希望这可以帮助..

回答by André Werlang

If you can't assume that ng-model is assigned to a boolean model (e.g. Y/N, '0'/'1') and/or you prefer to have your own markup, an approach that leverages ngModel capabilities, and makes no assumption on HTML structure is better, IMHO.

如果你不能假设 ng-model 被分配给一个布尔模型(例如 Y/N、'0'/'1')和/或你更喜欢有自己的标记,一种利用 ngModel 功能的方法,并使恕我直言,对 HTML 结构没有更好的假设。

Example: http://plnkr.co/edit/mZQBizF72pxp4BvmNjmj?p=preview

示例:http: //plnkr.co/edit/mZQBizF72pxp4BvmNjmj?p=preview

Sample usage:

示例用法:

  <fieldset indeterminate-group>
    <legend>Checkbox Group</legend>
    <input type="checkbox" name="c0" indeterminate-cue> Todos <br>
    <input type="checkbox" name="c1" ng-model="data.c1" ng-true-value="'Y'" ng-false-value="'F'" indeterminate-item> Item 1 <br>
    <input type="checkbox" name="c2" ng-model="data.c2" ng-true-value="'Y'" ng-false-value="'F'" indeterminate-item> Item 2 <br>
    <input type="checkbox" name="c3" ng-model="data.c3" ng-true-value="'Y'" ng-false-value="'F'" indeterminate-item> Item 3 <br>
  </fieldset>

Directive (main parts):

指令(主要部分):

angular.module('app', [])
  .directive('indeterminateGroup', function() {
    function IndeterminateGroupController() {
      this.items = [];
      this.cueElement = null;
    }
    ...
    function setAllValues(value) {
      if (this.inChangeEvent) return;

      this.inChangeEvent = true;
      try {
        this.items.forEach(function(item) {
          item.$setViewValue(value);
          item.$render();
        });
      } finally {
        this.inChangeEvent = false;
      }
    }

    return {
      restrict: "A",
      controller: IndeterminateGroupController,
      link: function(scope, element, attrs, ctrl) {
        ctrl.inputChanged = function() {
          var anyChecked = false;
          var anyUnchecked = false;
          this.items.forEach(function(item) {
            var value = item.$viewValue;
            if (value === true) {
              anyChecked = true;
            } else if (value === false) {
              anyUnchecked = true;
            }
          });

          if (this.cueElement) {
            this.cueElement.prop('indeterminate', anyChecked && anyUnchecked);
            this.cueElement.prop('checked', anyChecked && !anyUnchecked);
          }
        };
      }
    };
  })
  .directive('indeterminateCue', function() {
    return {
      restrict: "A",
      require: '^^indeterminateGroup',
      link: function(scope, element, attrs, indeterminateGroup) {
        indeterminateGroup.addCueElement(element);
        var inChangeEvent = false;
        element.on('change', function(event) {
          if (event.target.checked) {
            indeterminateGroup.checkAll();
          } else {
            indeterminateGroup.uncheckAll();
          }
        });
      }
    };
  })
  .directive('indeterminateItem', function() {
    return {
      restrict: "A",
      require: ['^^indeterminateGroup', 'ngModel'],
      link: function(scope, element, attrs, ctrls) {
        var indeterminateGroup = ctrls[0];
        var ngModel = ctrls[1];
        indeterminateGroup.addItem(ngModel);
        ngModel.$viewChangeListeners.push(function() {
          indeterminateGroup.inputChanged();
        });
      }
    };
  });

Model:

模型:

// Bring your own model

TODO:

去做:

  • get rid of item.$render() inside main directive controller;
  • give a better name to the directive;
  • make easy to use this directive in more than one table column.
  • 摆脱主指令控制器中的 item.$render() ;
  • 给指令一个更好的名字;
  • 使在多个表列中使用此指令变得容易。

回答by Janus Troelsen

Plunker

普朗克

"use strict";

var module = angular.module("myapp", []);

function Ctrl($scope) {
    var element = $("#select_all");
    $scope.$watch("$scope.isgreyed", $scope.fun = function() {
        element.prop("indeterminate", $scope.isgreyed);
    });
    $scope.list = [{
        isSelected: true,
        desc: "Donkey"
    }, {
        isSelected: false,
        desc: "Horse"
    }]
    $scope.isgreyed = true;
    $scope.master = false;
    $scope.onmasterclick = function() {
        $scope.list.map(function(v) {
            v.isSelected = $scope.master
        })
    }

    $scope.oncheckboxclick = function() {?????
        if ($('.select_one:checked').length === 0) {
            $scope.isgreyed = false;
            $scope.master = false;?????
        } else if ($('.select_one:not(:checked)').length === 0) {
            $scope.isgreyed = false;
            $scope.master = true;?????
        } else {
            $scope.isgreyed = true;?????
        }
        $scope.fun();
    }   ???
}

HTML:

HTML:

<div ng-controller="Ctrl">
<table>
<tr>
  <td>
     <input type="checkbox" id="select_all" ng-model="master" ng-click="onmasterclick()">
  </td>
</tr>
<tr ng-repeat="elem in list">
  <td>
    <input ng-click="oncheckboxclick(elem)" class="select_one" type="checkbox" ng-model="elem.isSelected">
  </td>
  <td>{{elem.desc}}</td>
</tr>
</table>
</div>

Yes, it's ugly.

是的,它很丑。

回答by Fyodor Dostoyevsky

Rewritten using Plnkerto a bit better code without resource-consuming ForEach's and some other complicated stuff:

使用Plnker重写为更好的代码,而无需消耗资源 ForEach 和其他一些复杂的东西:

var app = angular.module('angularjs-starter', []);

app.controller('MainCtrl', function($scope) {
  $scope.listelements = [{
    isSelected: true,
    desc: "Donkey"
  }, {
    isSelected: false,
    desc: "Horse"
  }];
});

app.directive('triStateCheckbox', function() {
  return {
    replace: true,
    restrict: 'E',
    scope: {
      checkboxes: '='
    },
    template: '<input type="checkbox" ng-model="master" ng-change="masterChange()">',
    controller: function($scope, $element) {
      $scope.masterChange = function() {
        for(i=0;i<$scope.checkboxes.length; i++)
          $scope.checkboxes[i].isSelected=$scope.master;
      };
      $scope.$watch('checkboxes', function() {
        var set=0;
        for (i=0;i<$scope.checkboxes.length;i++)
          set += $scope.checkboxes[i].isSelected?1:0;
        $element.prop('indeterminate', false);
        $scope.master = (set === 0) ? false : true;
        if (set > 0 && set < i) {
          $scope.master = false;
          $element.prop('indeterminate', true);
        }
      }, true);
    }
  };
});

回答by Rishul Matta

i guess it can be solved by combining angular with javascript:

我想可以通过将 angular 与 javascript 结合来解决:

<div>

<input type="checkbox" id="select-all" name="selectAll" value="" ng-click="checkAll($event)" />

<div >
  <input type="checkbox"  name="childCheckbox" value=""  />

  <input type="checkbox"  name="childCheckbox" value=""  />

  <input type="checkbox"  name="childCheckbox" value=""  />

  <input type="checkbox"  name="childCheckbox" value=""  />

  <input type="checkbox"  name="childCheckbox" value=""  />

  <input type="checkbox"  name="childCheckbox" value=""  />
 </div>

</div>

in checkAll() the following logic will do the job

在 checkAll() 中,以下逻辑将完成这项工作

  $scope.checkAll = function (source) {
   checkboxes = document.getElementsByName('childCheckbox');                                                 
   for (var i = 0, n = checkboxes.length; i < n; i++)   {
     checkboxes[i].checked = source.originalEvent.srcElement.checked;
   }