全选功能在网页开发中算是一个极为常见的功能了。尤其在开发系统的管理后台时,维护人员可能要做重复的机械动作,手动更改每一条数据的状态,这时候如果有全选功能,应该会让我们程序同学和策划同学被人背地里吐槽的几率小很多。
一、要实现的需求
一图胜千言,先来看图。
再来细化一下功能:
- 需要有一个checkbox负责全选和取消全选
- 全选后,取消其中一项,全选取消
- 取消全选后,再手动选上所有项,全选checkbox自动勾上
二、思路1 给数组中的每一项绑定checked
属性
通过checkedArray
这个数组,存储已经选中的数组元素,其作用主要是实现上文中的第3项功能,代码如下。
-
HTML
<div ng-controller="myCtrl">
<label for="select-all">全选
<input
type="checkbox"
id="select-all"
ng-change="selectAll()"
ng-model="all" >
</label>
<ul>
<li ng-repeat="item in list">
<input type="checkbox" ng-model="item.checked" ng-change="selectOne()">
<span ng-bind="item.id"></span>
</li>
</ul>
</div> -
JS
var app = angular.module('myApp', []);
app.controller('myCtrl', ['$scope', function($scope){
$scope.list = [
{
id: '001',
},
{
id: '002',
},
{
id: '003',
},
{
id: '004',
},
{
id: '005',
},
{
id: '006',
},
{
id: '007',
}
];
$scope.checkedArray = [];
$scope.selectAll = function() {
if ($scope.all) {
$scope.checkedArray = [];
angular.forEach($scope.list, function(item) {
item.checked = true;
$scope.checkedArray.push(item.id);
});
} else { // 清空全选
$scope.checkedArray = [];
angular.forEach($scope.list, function(item) {
item.checked = false;
});
}
};
$scope.selectOne = function() {
angular.forEach($scope.list, function(item) {
var localIndex = $scope.checkedArray.indexOf(item.id);
// 选中
if (localIndex === -1 && item.checked) {
$scope.checkedArray.push(item.id);
} else if (localIndex !== -1 && !item.checked) { // 取消选中
$scope.checkedArray.splice(localIndex, 1);
}
$scope.all = $scope.list.length === $scope.checkedArray.length;
});
}
}]);
这种思路比较完整地实现了我们需要的功能,但是在实际项目中,数组list
中的元素引入了一个可能不需要的属性checked
,这种写法引入了脏属性,在表单提交的时候,可能需要额外处理checked
属性,构造合法的请求数据格式,引起不必要的麻烦。
三、思路2 ng-true-value
等指令的运用
思路阐述:
- 根据现有的
list
数组初始化一个初始化一个数组checkedArray
,并在ng-repeat
的时候,将其每一项初始化为false
; - 考虑到要给服务端返回选中的id数组,引入
ng-true-value
指令,ng-true-value="item.id"
; - 使用
$watchCollection
方法监控数组checkedArray
变化,并使用数组的every 方法判断是否都不是false
,决定全选checkbox
的状态。
代码如下。
- HTML
<div ng-controller="myCtrl">
<label for="select-all">全选
<input type="checkbox" id="select-all" ng-click="selectAll()" ng-model="isAll">
</label>
<ul>
<li ng-repeat="item in list track by $index" ng-init="checkedArray[$index]=false">
<input type="checkbox" ng-model="checkedArray[$index]" ng-true-value="{{item.id}}" ng-false-value="false">
<span ng-bind="item.id"></span>
</li>
</ul>
<input type="button" value="获取所有选中的元素" ng-click="getCheckedList()">
</div>
- JS
var app = angular.module('myApp', []);
app.controller('myCtrl', ['$scope', function($scope) {
$scope.list = [{
'id': 101
}, {
'id': 102
}, {
'id': 103
}, {
'id': 104
}, {
'id': 105
}, {
'id': 106
}, {
'id': 107
}];
$scope.checkedArray = new Array($scope.list.length);
$scope.selectAll = function() {
if ($scope.isAll) {
$scope.checkedArray = $scope.list.map(function(item) {
return item.id;
});
} else {
$scope.checkedArray = $scope.list.map(function(item) {
return false;
});
}
};
$scope.$watchCollection('checkedArray', function(newCollection) {
if (newCollection.every(function(item) {
return item !== false;
})) {
$scope.isAll = true;
} else {
$scope.isAll = false;
}
});
$scope.getCheckedList = function() {
console.log($scope.checkedArray.filter(function(item) {
return item
}));
}
}]);
四、思路3 组合指令
全选是一个很常见的功能,如果能做成指令复用,将会大大提高我们的开发效率。我们依照要实现的功能,拆分成四个指令:commonOperate
、checkSingle
、checkAll
、operateBatch
。
将公共的方法和属性写在指令commonOperate
中,在其他三个指令中require
这个指令,完成单选、全选、导出选中项的功能。这种写法的好处是可以嵌入到现有的模板中,使用及其方便快捷。
- HTML
<div ng-controller="myCtrl">
<div common-operate>
<table border="1">
<thead>
<tr>
<th><input type="checkbox" check-all> NO.</th>
<th>name</th>
<th>gender</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox" check-single="1"> 1</td>
<td>David</td>
<td>male</td>
</tr>
<tr>
<td><input type="checkbox" check-single="2"> 2</td>
<td>Bob</td>
<td>male</td>
</tr>
<tr>
<td><input type="checkbox" check-single="3"> 3</td>
<td>Kate</td>
<td>female</td>
</tr>
<tr>
<td><input type="checkbox" check-single="4"> 4</td>
<td>Xiaoming</td>
<td>male</td>
</tr>
</tbody>
</table>
<button type="button" operate-batch="getBatch">导出选中项目</button>
</div>
</div>
- JS
var app = angular.module('myApp', []);
app.controller('myCtrl', ['$scope', function($scope) {
$scope.getBatch = function(list) {
console.log(list)
}
}]);
app.directive('commonOperate', function() {
return {
restrict: 'EA',
controller: ['$scope', '$element', '$attrs', '$timeout',
function($scope, $element, $attrs, $timeout) {
var checkedClassName = $attrs['commonOperate'],
checkList = [];
this.scope = $scope;
$scope.checkCommonOperateCheckList = checkList;
// 设置选中状态
this.setCheckedStatus = function(element, needStatus) {
if (checkedClassName) {
if (needStatus) {
element.removeClass(checkedClassName)
.addClass(checkedClassName);
} else {
element.removeClass(checkedClassName);
}
} else {
element.prop('checked', needStatus);
}
};
// 获取点击之前的状态
this.getCheckedStatus = function(element) {
return checkedClassName ? element.hasClass(checkedClassName)
: !element.prop('checked');
};
// 清除选中列表
this.clearCheckList = function() {
checkList = [];
$timeout(function() {
$scope.checkCommonOperateCheckList = checkList;
});
this.setCheckedStatus($element.find('[check-single]'), false);
};
// 获取选中列表
this.getCheckList = function() {
return checkList;
}
// 增加选中列表
this.setCheckList = function(checkId, needStatus) {
var selectAllDOM = $element.find('[check-all]');
if (needStatus) {
checkList.push(checkId);
if (checkList.length === $element.find('[check-single]').length) {
this.setCheckedStatus(selectAllDOM, true);
}
} else {
this.setCheckedStatus(selectAllDOM, false);
var localIndex = checkList.indexOf(checkId);
if (localIndex !== -1) {
checkList.splice(localIndex, 1);
}
}
$timeout(function () {
$scope.espCommonOperateCheckList = checkList;
}, 0);
return checkList;
};
// 全选
this.selectAll = function(needStatus) {
var singleList = $element.find('[check-single]');
this.setCheckedStatus(singleList, needStatus);
if (needStatus) {
checkList = singleList.map(function (item) {
return $(item).attr('check-single');
});
$timeout(function () {
$scope.espCommonOperateCheckList = checkList;
}, 0);
} else {
this.clearCheckList();
}
}
}]
}
});
app.directive('checkSingle', function() {
return {
restrict: 'A',
require: '^commonOperate',
link: function($scope, $element, $attrs, commonOperate) {
var itemId = $attrs['checkSingle'];
var mapValue = $attrs['mapValue'];
$element.click(function(e) {
var needStatus = !commonOperate.getCheckedStatus($element);
commonOperate.setCheckList(itemId, needStatus);
commonOperate.setCheckedStatus($element, needStatus);
});
}
}
});
app.directive('checkAll', function() {
return {
restrict: 'A',
scope: {
reloadPage: '=?',
page: '=?'
},
require: '^commonOperate',
link: function($scope, $element, $attrs, commonOperate) {
$element.click(function (e) {
var needStatus = !commonOperate.getCheckedStatus($element);
commonOperate.selectAll(needStatus);
commonOperate.setCheckedStatus($element, needStatus);
});
$scope.$watch('reloadPage', function () {
commonOperate.setCheckedStatus($element, false);
commonOperate.clearCheckList();
});
$scope.$watch('page', function () {
commonOperate.setCheckedStatus($element, false);
commonOperate.clearCheckList();
});
}
}
});
app.directive('operateBatch', function() {
return {
restrict: 'A',
require: '^commonOperate',
link: function ($scope, $element, $attrs, commonOperate) {
$element.click(function (e) {
var funcName = $attrs['operateBatch'];
if (funcName && commonOperate.scope[funcName]) {
commonOperate.scope[funcName](commonOperate.getCheckList());
}
});
}
}
});
从以上的代码中可以看出,当需要为现有的页面加入全选功能时,只需要将这几个指令嵌入到相关的DOM上即可,极大地提高了其复用性。
最近几天好不容易闲下来,有时间思考一些东西,这篇博客花了两天时间构思,一天时间写内容和demo,第三个思路是参考同事写的东西,整理得来。写之前总觉得自己有千言万语,到了真正动笔的时候,又不知道从哪里下手,真心羡慕那些能写出赏心悦目文字的作者。
加油2017。