web-dev-qa-db-fra.com

Comment obtenir des cases à cocher angular.js avec la sélection/désélection de toutes les fonctionnalités et les valeurs indéterminées?

Je cherche quelque chose qui ressemble exactement à ceux-ci (cases à cocher tri-state avec "parents"). Mais utiliser cette solution ne serait pas élégant, car je ne dépend pas de jQuery pour le moment, et je devrais appeler $ scope. $ Apply pour que le modèle reconnaisse le jQuery coché et coché automatiquement.

Voici un bogue pour angular.js qui demande que ng-indeterminate-value soit implémenté. Mais cela ne me permettait toujours pas de synchroniser tous les enfants, ce qui, à mon avis, ne devrait pas faire partie de mon contrôleur.

Ce que je cherche serait quelque chose comme ceci:

  • Une directive "ng-children-model" avec une syntaxe telle que: <input type="checkbox" ng-children-model="child.isSelected for child in listelements">. La liste des booléens serait calculée, et si 0 sélectionné -> case à cocher false. Si tout est sélectionné -> case à cocher true. Sinon -> case à cocher indéterminée.
  • Dans mon contrôleur, j'aurais quelque chose comme ceci: $scope.listelements = [{isSelected: true, desc: "Donkey"},{isSelected: false, desc: "Horse"}]
  • Les cases à cocher seraient faites comme d'habitude avec <tr ng-repeat="elem in listelements"><td><input type="checkbox" ng-model="elem.isSelected"></td><td>{{elem.desc}}</td></tr>.
  • Si j'ai bien compris, le navigateur déterminera l'état dans lequel se trouve une case à cocher indéterminée sur laquelle l'utilisateur a cliqué.
27
Janus Troelsen

Puisque vous voulez un nouveau type/type de composant, cela semble être un bon cas pour une directive personnalisée .
Étant donné que la case à cocher parent/maître/tri-énoncé et les cases à cocher à deux états individuelles doivent interagir, je suggère une directive unique, avec son propre contrôleur, pour gérer la logique.

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

Directif:

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

Modifiez le modèle en fonction de vos besoins ou utilisez un modèle externe avec templateUrl.

La directive suppose que le tableau de cases à cocher contient des objets ayant une propriété isSelected et une propriété desc.

Plunker .

Update: Si vous préférez que la directive ne rende que la case à cocher à trois énoncés, les cases individuelles sont donc au format HTML (comme la solution de @ Piran), voici une autre variante de plunker . Pour ce plunker, le code HTML serait:

<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>
20
Mark Rajcok

Je pense que l'exemple de solution que vous donnez met trop de code dans le contrôleur. Le contrôleur doit uniquement se préoccuper de la liste et le code HTML/les directives doivent gérer l’affichage (y compris l’affichage de la case à cocher Sélectionner tout). En outre, tous les changements d'état se font par le modèle et non par l'écriture de fonctions.

J'ai mis en place une solution sur Plunker: http://plnkr.co/edit/gSeQL6XPaMsNSnlXwgHt?p=preview

Maintenant, le contrôleur configure simplement la liste:

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

et la vue rend simplement ceux dehors:

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

Pour la case à cocher Tout sélectionner, j'ai créé une nouvelle directive appelée checkbox-all:

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

Et c'est tout ce qui est utilisé, ce qui, espérons-le, est simple ... à part la rédaction de cette nouvelle 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);
  };
});

La variable parts décompose le list.isSelected en ses deux parties afin que je puisse obtenir la valeur de list à partir de la portée, ainsi que de la propriété isSelected dans chaque objet.

J'ajoute la propriété type="checkbox" à l'élément input, ce qui en fait une véritable case à cocher pour le navigateur. Cela signifie que l'utilisateur peut cliquer dessus, cliquer dessus, etc.

Je lie sur l'événement onchange plutôt que onclick, car la case à cocher peut être modifiée de nombreuses manières, y compris via le clavier. L'événement onchange s'exécute dans une scope.$apply() pour s'assurer que les modifications du modèle sont digérées à la fin.

Enfin, je $watch le modèle d'entrée pour les modifications apportées à la case à cocher (la dernière true me permet de regarder des objets complexes). Cela signifie que si les cases à cocher sont modifiées par l'utilisateur ou pour toute autre raison, la case à cocher Tout sélectionner est toujours synchronisée. C'est beaucoup mieux que d'écrire beaucoup de gestionnaires ng-click.

Si les cases à cocher sont cochées et non cochées, je désactive la case à cocher principale et ajoute le style "grisé" (voir style.css). Ce style CSS définit en gros l'opacité sur 30%, ce qui a pour effet de rendre la case à cocher grisée, mais vous pouvez toujours cliquer dessus. vous pouvez également y accéder et utiliser la barre d'espace pour changer sa valeur.

J'ai testé sous Firefox, Chrome et Safari, mais je n'ai pas IE sous la main. J'espère que cela fonctionne pour vous.

20
Piran

Voici une version raffinée de la solution de Piran. L'utilisation de .prop() au lieu de .attr() résout le problème 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
4
wldaunfr

Je pense que vous ne devriez créer une directive que si vous avez seulement besoin de faire une sorte de manipulation du DOM ou si vous voulez faire abstraction de nombreux comportements de manipulation du DOM dans un composant "réutilisable".

Voici une solution qui réalise la même chose que ce que vous essayiez, mais cela ne concerne que la logique des contrôleurs ... Si vous souhaitez que les contrôleurs restent minces, vous pouvez alors mettre toute cette logique en service le service serait également un bon endroit pour le faire, si vous voulez le réutiliser dans plusieurs endroits ..

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

Notez qu'il n'y a pas de manipulation DOM dans le contrôleur. Nous obtenons l’effet que nous demandons en utilisant un ensemble de directives fournies avec Angular. Aucune nouvelle directive requise .. Je ne pense vraiment pas que vous devriez utiliser une directive pour faire abstraction de la logique ..

J'espère que cela t'aides..

2
ganaraj

Si vous ne pouvez pas supposer que ng-model est affecté à un modèle booléen (par exemple, Y/N, '0'/'1') et/ou si vous préférez avoir votre propre balisage, une approche qui exploite les capacités de ngModel et permet aucune hypothèse sur la structure HTML n'est meilleure, à mon humble avis.

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

Exemple d'utilisation:

  <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 (parties principales):

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

Modèle:

// Bring your own model

FAIRE:

  • se débarrasser de item. $ render () dans le contrôleur principal de la directive;
  • donner un meilleur nom à la directive;
  • rendre facile l’utilisation de cette directive dans plusieurs colonnes de la table.
1
André Werlang

Réécrit avec Plnker pour un code un peu meilleur sans ForEach's, gros consommateur de ressources, et quelques autres choses compliquées:

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);
    }
  };
});
0
Fyodor Dostoyevsky

je suppose que cela peut être résolu en combinant angular avec 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>

dans checkAll () la logique suivante fera le travail

  $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;
   }
0
Rishul Matta

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:

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

Oui, c'est moche.

0
Janus Troelsen