如何获得带有选择/取消选择所有功能和不确定值的 angular.js 复选框?
Posted
技术标签:
【中文标题】如何获得带有选择/取消选择所有功能和不确定值的 angular.js 复选框?【英文标题】:How can I get angular.js checkboxes with select/unselect all functionality and indeterminate values? 【发布时间】:2012-09-20 20:06:24 【问题描述】:我正在寻找与these 完全相同的东西(带有“父母”的三态复选框)。但是使用该解决方案并不优雅,因为我现在不依赖 jQuery,我需要调用 $scope.$apply 来让模型识别自动(未)选中的复选框 jQuery 点击。
Here's a bug for angular.js 请求实现了 ng-indeterminate-value。但这仍然不能让我与所有孩子同步,我认为这不应该是我的控制器的一部分。
我正在寻找的是这样的:
“ng-children-model”指令,语法如下:<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>
。
据我了解,浏览器将确定单击的不确定复选框进入哪个状态。
【问题讨论】:
【参考方案1】:由于您需要一种新类型/种类的组件,这听起来像是自定义指令的一个很好的例子。 由于父/主/三态复选框和单独的双态复选框需要相互交互,我建议使用带有自己控制器的单个指令来处理逻辑。
<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 的解决方案),这里是 another 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>
【讨论】:
您可以使用标准的 ng-model 和 ng-checked 指令来执行您的 plunker 中的操作。 docs.angularjs.org/api/ng.directive:ngChecked Another version,适用于listelemts
是对象的情况(将复选框键映射为真/假)。【参考方案2】:
我认为您提供的示例解决方案将太多代码放入控制器中。控制器应该只关心列表,HTML/指令应该处理显示(包括显示全选复选框)。另外,所有的状态变化都是通过模型,而不是通过编写函数。
我在 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>
对于 Select All 复选框,我创建了一个名为 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
允许我查看复杂对象)。这意味着如果用户或其他原因更改了复选框,则 Select All 复选框始终保持同步。这比编写大量 ng-click 处理程序要好得多。
如果复选框同时被选中和未选中,那么我将主复选框设置为未选中并添加样式“灰色”(请参阅style.css
)。该 CSS 样式基本上将不透明度设置为 30%,导致复选框显示为灰色,但仍可点击;您也可以使用 Tab 键并使用空格键更改其值。
我在 Firefox、Chrome 和 Safari 中进行了测试,但我手头没有 IE。希望这对你有用。
【讨论】:
+1。我建议使用两个单独的属性,而不是传递“list.isSelected”(看起来像是对某物的引用,但实际上并非如此)。或者,只传递“list”并让指令假设数组对象上有一个“isSelected”属性(这就是我在回答中所做的)。<input checkbox-all ...>
并在指令中将类型添加为“复选框”——这似乎用户必须记住在 HTML 中设置复选框的一半,然后设置另一半由指令。我建议将 HTML 替换为包含 <input type="text" ...>
的模板。这将允许用户指定任何元素(div、span、input 等)并且它将始终有效。或者指令可以是一个新元素,而不是一个属性。
对于“list.isSelected”,“list[].isSelected”或“list[*].isSelected”如何表示它是一个神奇的值?
据我所见(在 Opera 中测试),此示例中也没有触发不确定状态。甚至可以从 CSS 触发吗?我不知道该怎么做。如果代码被更新,我会很感激,因为我认为不确定状态比 CSS 样式表现得更好,因为它使用本机不确定状态。
我从来不知道不确定(*** 不是很好!)。这是一个在复选框上设置了中间属性的 plunker:plnkr.co/edit/tgLWZurGdkrW2PqLWhMb?p=preview【参考方案3】:
这是 Piran 解决方案的refined version。使用 .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 提供的一堆指令来实现我们需要的效果。不需要新指令..我真的不认为你应该使用指令来抽象逻辑..
希望这会有所帮助..
【讨论】:
在“master”复选框上设置indeterminate
属性让我想把它放在一个指令中。此外,由于服务是单例的,我认为使用这种方法会很困难,特别是如果应用程序中有这个三态复选框的多个实例......服务需要以某种方式跟踪它们。对于逻辑,我选择了一个带有自己控制器的指令。
在您在 Opera 上的示例中,我没有看到任何不确定的状态被触发。如果马 XOR 驴我希望“全选”处于不确定状态。但非常感谢您的贡献。
我现在在我的答案中添加了一个 Plunker 链接。如果您没有发现差异,请告诉我。【参考方案5】:
如果您不能假设 ng-model 已分配给布尔模型(例如 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
待办事项:
去掉主指令控制器中的 item.$render(); 给指令起一个更好的名字; 使该指令在多个表格列中易于使用。【讨论】:
【参考方案6】: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>
是的,很丑。
【讨论】:
【参考方案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;
【讨论】:
这不支持不确定的值 您的意思是更改 child 的值并取消选中全选复选框?这可以通过在单击子复选框时调用一个函数来完成,该函数将取消选中父复选框...可以吗?以上是关于如何获得带有选择/取消选择所有功能和不确定值的 angular.js 复选框?的主要内容,如果未能解决你的问题,请参考以下文章