Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
276 views
in Technique[技术] by (71.8m points)

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

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.

Here's a bug for angular.js that 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.

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.
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.

I've put together a solution on 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:

  <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 parts variable breaks down the list.isSelected into its two parts, so I can get the value of list from the scope, an the isSelected property in each object.

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.

I bind on the onchange event 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.

Finally, I $watch the input model for changes to the checkbox (the last true allows 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.

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.

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...