open javascript html5 checkbox angularjs tri-state-logic

javascript - open - angular material



¿Cómo puedo obtener las casillas de verificación angular.js con seleccionar/deseleccionar todas las funcionalidades y valores indeterminados? (8)

Estoy buscando algo exactamente como these (casillas de verificación tri-estatales con "padres"). Pero usar esa solución no sería elegante, ya que no dependo de jQuery en este momento, y necesitaría llamar a $ scope. $ Apply para que el modelo reconozca el jQuery con casillas automáticamente (no) marcadas.

Aquí hay un error para angular.js que solicita ng-indeterminate-value implementado. Pero eso aún no me da la sincronización para todos los niños, que es algo que no creo que deba ser parte de mi controlador.

Lo que estoy buscando sería algo como esto:

  • Una directiva "ng-children-model" con sintaxis como: <input type="checkbox" ng-children-model="child.isSelected for child in listelements"> . La lista de booleanos se calculará, y si se selecciona 0 -> checkbox false. Si todos seleccionados -> casilla de verificación verdadero. Else -> checkbox indeterminado.
  • En mi controlador, tendría algo como esto: $scope.listelements = [{isSelected: true, desc: "Donkey"},{isSelected: false, desc: "Horse"}]
  • Las casillas de verificación se realizarían como de costumbre con <tr ng-repeat="elem in listelements"><td><input type="checkbox" ng-model="elem.isSelected"></td><td>{{elem.desc}}</td></tr> .
  • Según lo entiendo, el navegador determinará en qué estado entra una casilla de verificación indeterminada.

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>

Sí, es feo


Aquí hay una versión refinada de la solución de Piran. Usar .prop() lugar de .attr() corrige el problema checked .

Uso:

<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


Como desea un nuevo tipo / tipo de componente, esto parece ser un buen ejemplo para una directiva personalizada.
Como la casilla de verificación padre / maestro / tri-declarada y las casillas de estado dual individuales necesitan interactuar entre sí, sugiero una sola directiva, con su propio controlador, para manejar la lógica.

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

Directiva:

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

Cambie la plantilla para adaptarla a sus necesidades o use una plantilla externa con templateUrl.

La directiva asume que la matriz de casillas de verificación contiene objetos que tienen una propiedad isSelected y una propiedad desc .

Plunker .

Actualización : si prefiere que la directiva solo muestre la casilla de verificación tri declarada, por lo tanto, las casillas de verificación individuales están en el HTML (como la solución de @ Piran), aquí hay otra variación de plunker para eso. Para este plunker, el HTML sería:

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


Creo que la solución de muestra que das pone demasiado código en el controlador. En realidad, el controlador solo debería preocuparse por la lista, y las directivas HTML / deberían manejar la pantalla (incluso mostrar la casilla de verificación Seleccionar todo). Además, todos los cambios de estado se realizan a través del modelo, no escribiendo funciones.

He creado una solución en Plunker: http://plnkr.co/edit/gSeQL6XPaMsNSnlXwgHt?p=preview

Ahora, el controlador simplemente configura la lista:

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

y la vista simplemente los muestra:

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

Para la casilla de verificación Seleccionar todo, he creado una nueva directiva llamada checkbox-all :

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

Y eso es todo por lo que respecta al uso, lo que es, con suerte, simple ... aparte de escribir esa nueva directiva:

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 divide la list.isSelected en sus dos partes, de modo que puedo obtener el valor de la list del alcance y la propiedad isSelected en cada objeto.

Añado la propiedad type="checkbox" al elemento de entrada, lo que lo convierte en una casilla de verificación real para el navegador. Eso significa que el usuario puede hacer clic en él, tabularlo, etc.

Me enlace en el evento onchange lugar de onclick , ya que la casilla de verificación se puede cambiar de muchas maneras, incluso a través del teclado. El evento onchange se ejecuta dentro de un scope.$apply() para garantizar que los cambios del modelo se digieren al final.

Finalmente, $watch el modelo de entrada para los cambios en la casilla de verificación (la última true me permite ver objetos complejos). Eso significa que si el usuario cambia las casillas de verificación o por algún otro motivo, la casilla de verificación Seleccionar todo siempre se mantiene sincronizada. Eso es mucho mejor que escribir muchos manejadores de ng-click.

Si las casillas de verificación están marcadas y desmarcadas, configuro la casilla maestra para desmarcarla y agrego el estilo ''greyed'' (vea style.css ). Ese estilo CSS básicamente establece la opacidad en un 30%, lo que hace que la casilla de verificación aparezca en gris, pero todavía se puede hacer clic; también puede tabular y usar la barra espaciadora para cambiar su valor.

He probado en Firefox, Chrome y Safari, pero no tengo IE a mano. Espero que esto funcione para ti.


Creo que solo deberías crear una directiva si solo necesitas hacer algún tipo de manipulación DOM o si quieres abstraer mucho comportamiento manipulador DOM en un componente "reutilizable".

Aquí hay una solución que logra lo mismo que intentabas, pero esto solo hace la lógica en los controladores ... Si quieres mantener los controladores delgados, entonces puedes empujar toda esta lógica al servicio ... A el servicio también sería un buen lugar para hacer esto, si desea reutilizar esto en múltiples lugares.

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

Tenga en cuenta que no hay manipulación DOM en el controlador. Estamos logrando el efecto que requerimos utilizando un conjunto de directivas que se proporcionan con Angular. No se requiere una nueva directiva. Realmente no creo que deba usar una directiva para abstraer la lógica de escape.

Espero que esto ayude..


Reescrito utilizando Plnker para un código un poco mejor sin ForEach''s y otras cosas complicadas que consumen muchos recursos:

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


Si no puede suponer que ng-model está asignado a un modelo booleano (por ejemplo, S / N, ''0'' / ''1'') y / o prefiere tener su propio marcado, un enfoque que aprovecha las capacidades de ngModel, y hace ninguna suposición sobre la estructura HTML es mejor, en mi humilde opinión.

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

Uso de muestra:

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

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

Modelo:

// Bring your own model

QUE HACER:

  • deshacerse del ítem. $ render () dentro del controlador de la directiva principal;
  • dar un mejor nombre a la directiva;
  • facilite el uso de esta directiva en más de una columna de la tabla.

Supongo que se puede resolver combinando angular con 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>

en checkAll () la siguiente lógica hará el trabajo

$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; }