Как я могу установить флажки angular.js с выбором/отменой всех функций и неопределенными значениями?
Я ищу что-то вроде этих (флажки tri-state с "родителями" ). Но использование этого решения было бы не изящным, поскольку я не буду сейчас зависеть от jQuery, и мне нужно будет вызвать $scope. $Apply, чтобы получить модель, чтобы распознать автоматически (un) check checkboxed jQuery, нажав.
Вот ошибка для angular.js, которая запрашивает ng-неопределенное значение. Но это все равно не даст мне синхронизацию со всеми детьми, что я не думаю, что должен быть частью моего контроллера.
То, что я ищу, будет примерно таким:
- Директива "ng-children-model" с синтаксисом типа:
<input type="checkbox" ng-children-model="child.isSelected for child in listelements">
. Будет вычислен список логических элементов, и если 0 выбрано → checkbox false. Если все выбрано → флажок true. Else → неопределенный.
- В моем контроллере у меня было бы что-то вроде этого:
$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>
.
- Как я понимаю, браузер определит, в каком состоянии будет установлен флажок неопределенного значения.
Ответы
Ответ 1
Поскольку вам нужен новый тип/вид компонента, это звучит как хороший пример для пользовательской директивы.
Поскольку флажок parent/master/tri-said и отдельные флажки с двумя состояниями должны взаимодействовать друг с другом, я предлагаю одну директиву с собственным контроллером для обработки логики.
<tri-state-checkbox checkboxes="listelements"></tri-state-checkbox>
Директива
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
},
};
});
Измените шаблон в соответствии с вашими потребностями или используйте внешний шаблон с templateUrl.
В директиве предполагается, что массив checkboxes содержит объекты, которые имеют свойство isSelected
и свойство desc
.
Plunker.
Обновить. Если вы предпочитаете, чтобы директива отображала только трижды установленный флажок, поэтому отдельные флажки находятся в HTML (например, в решении @Piran), здесь другой плункер для этого. Для этого плункера 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>
Ответ 2
Я думаю, что тестовое решение, которое вы даете, помещает слишком много кода в контроллер. Контроллер должен действительно беспокоиться только о списке, и HTML/Directives должны обрабатывать дисплей (включая отображение флажка "Выбрать все" ). Кроме того, все изменения состояния происходят через модель, а не путем записи функций.
Я собрал решение для Plunker: http://plnkr.co/edit/gSeQL6XPaMsNSnlXwgHt?p=preview
Теперь контроллер просто устанавливает список:
app.controller('MainCtrl', function($scope) {
$scope.list = [{
isSelected: true,
desc: "Donkey"
}, {
isSelected: false,
desc: "Horse"
}];
});
и вид просто отображает их:
<div ng-repeat="elem in list">
<input type="checkbox" ng-model="elem.isSelected" /> {{elem.desc}}
</div>
Для флажка "Выбрать все" я создал новую директиву под названием checkbox-all
:
<input checkbox-all="list.isSelected" /> Select All
И что это касается использования, которое, мы надеемся, прост... кроме написания этой новой директивы:
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);
};
});
Переменная parts
разбивает list.isSelected
на две ее части, поэтому я могу получить значение list
из области действия, свойство isSelected
в каждом объекте.
Я добавляю свойство type="checkbox"
к элементу ввода, делая его реальным флажком для браузера. Это означает, что пользователь может нажать на него, вкладку на него и т.д.
Я привязываюсь к событию onchange
, а не к onclick
, так как этот флажок можно изменить разными способами, в том числе с помощью клавиатуры. Событие onchange выполняется внутри scope.$apply()
, чтобы гарантировать, что изменения модели будут перевариваться в конце.
Наконец, я $watch
модель ввода для изменения флажка (последний true
позволяет мне просматривать сложные объекты). Это означает, что если флажки изменены пользователем или по какой-либо другой причине, то флажок "Выбрать все" всегда синхронизируется. Это намного лучше, чем писать много обработчиков ng-click.
Если флажки отмечены и отмечены флажками, то установите флажок мастер-флажок снят и добавьте стиль "greyed" (см. style.css
). Этот стиль CSS в основном устанавливает непрозрачность до 30%, в результате чего флажок будет отображаться серым, но он по-прежнему доступен для клики; вы также можете использовать вкладку и использовать пробел, чтобы изменить его значение.
Я тестировал в Firefox, Chrome и Safari, но у меня нет IE. Надеюсь, это сработает для вас.
Ответ 3
Здесь уточненная версия решения Piran. Используя .prop()
вместо .attr()
исправляет проблему checked
.
Использование:
<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
Я считаю, что вы должны создавать директиву только в том случае, если вам нужно только выполнить какую-либо манипуляцию с DOM или хотите отвлечь много манипулятивного поведения DOM на "повторно используемый" компонент.
Вот решение, которое выполняет то же самое, что вы пытались, но это делает только логику в контроллерах... Если вы хотите, чтобы контроллеры опирались, тогда вы можете отбросить всю эту логику в эксплуатацию... Сервис также будет хорошим местом для этого, если вы хотите повторно использовать его в нескольких местах.
http://plnkr.co/edit/hNTeZ8Tuht3T9NuY7HRi?p=preview
Обратите внимание, что в контроллере нет манипуляции с DOM. Мы добиваемся требуемого эффекта, используя кучу директив, которые предоставляются с помощью Angular. Никакой новой директивы не требуется.. Я действительно не думаю, что вы должны использовать директиву для абстрагирования логики.
Надеюсь, что это поможет.
Ответ 5
Если вы не можете предположить, что ng-модель назначается логической модели (например, Y/N, '0'/'1') и/или вы предпочитаете иметь собственную разметку, подход, который использует возможности ngModel, и не делает предположение о структуре HTML лучше, ИМХО.
Пример: http://plnkr.co/edit/mZQBizF72pxp4BvmNjmj?p=preview
Использование образца:
<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>
Директива (основные части):
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();
});
}
};
});
Модель:
// Bring your own model
TODO:
- избавиться от элемента. $render() внутри главного директивного контроллера;
- дать лучшее название директиве;
- упрощает использование этой директивы в более чем одном столбце таблицы.
Ответ 6
"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>
Да, это уродливо.
Ответ 7
Переписано с помощью 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);
}
};
});
Ответ 8
Я думаю, это можно решить, объединив 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>
в 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;
}