我正在寻找与these完全相同的东西(带有“父母”的三态复选框)。但是使用该解决方案并不优雅,因为我现在不依赖于jQuery,而且我需要调用$ scope。$ apply以使模型识别自动(未)检查的复选框jQuery被点击。
Here's a bug for angular.js请求实现ng-indeterminate-value。但这仍然不会让我与所有孩子同步,这是我认为不应该成为我的控制者的一部分。
我正在寻找的是这样的:
<input type="checkbox" ng-children-model="child.isSelected for child in listelements">
。将计算布尔值列表,如果选择0,则为&gt;复选框false。如果全部选中 - >复选框是的。否则 - &gt;复选框不确定。$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>
。答案 0 :(得分:20)
我认为您提供的示例解决方案会将太多代码放入控制器中。控制器应该只是担心列表,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"
属性添加到input元素,使其成为浏览器的真实复选框。这意味着用户可以点击它,选项卡等等。
我绑定onchange
事件而不是onclick
,因为可以通过多种方式更改复选框,包括通过键盘。 onchange事件在scope.$apply()
内运行,以确保模型更改在最后被消化。
最后,我$watch
更改复选框的输入模型(最后true
允许我观看复杂对象)。这意味着如果用户更改了复选框或出于其他原因,则“全选”复选框始终保持同步。这比编写大量的ng-click处理程序要好得多。
如果选中并取消选中复选框,则我将主复选框设置为取消选中,并添加“灰色”样式(请参阅style.css
)。 CSS样式基本上将不透明度设置为30%,导致复选框显示为灰色,但它仍然可以点击;您也可以选中它并使用空格键更改其值。
我已经在Firefox,Chrome和Safari中进行了测试,但我手头没有IE浏览器。希望这适合你。
答案 1 :(得分:20)
由于您需要一种新类型/类型的组件,这听起来像是一个自定义指令的好例子。
由于父/主/三重复选框和各个双状态复选框需要相互交互,我建议使用一个带有自己的控制器的指令来处理逻辑。
<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的外部模板。
该指令假定复选框数组包含具有isSelected
属性和desc
属性的对象。
更新:如果您希望指令仅呈现三重复选框,因此各个复选框位于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>
答案 2 :(得分:4)
这是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
答案 3 :(得分:2)
我相信你应该只创建一个指令,如果你只需要做某种DOM操作,或者想要将很多DOM操作行为抽象成一个“可重用的”组件。
这是一个实现与您尝试相同的解决方案的解决方案,但是,这仅执行控制器中的逻辑...如果您希望保持控制器的精益,那么您可以将所有这些逻辑推向服务。 ..如果你想在多个地方重复使用它,服务也是一个很好的地方..
http://plnkr.co/edit/hNTeZ8Tuht3T9NuY7HRi?p=preview
请注意,控制器中没有DOM操作。我们正在使用Angular提供的一组指令来实现我们所需的效果。不需要新的指令..我真的不认为你应该使用指令来抽象逻辑..
希望这会有所帮助..
答案 4 :(得分:1)
如果您不能假设ng模型被分配到布尔模型(例如Y / N,&#39; 0&#39; /&#39; 1&#39;)和/或您更喜欢拥有自己的标记,一种利用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:
答案 5 :(得分:0)
"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>
是的,这太丑了。
答案 6 :(得分:0)
使用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);
}
};
});
答案 7 :(得分:0)
我猜它可以通过组合角度与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;
}