问题描述
这里的多级选择组件问题,指的是存在一个多级的选择组件,当点击某个节点时,该节点及其下的所有节点都要选中,若该节点并列的所有兄弟节点都已选中,则其父节点也要勾选,依此到最顶端节点。反选也类似逻辑。
这个问题也符合平日的认知习惯。如下图所示:
如点
新华区
,则其下所有街道都要选中,再点击桥西区
,桥西区
下的街道要选中,同时石家庄市
这个节点要选中。若再点击廊坊市
这个节点,则整个河北省
都应该选中。
解决方法
有一个问题是,我们并不知道这个选择组件有多少级。如上示例为3级,但实际中是不确定的。
从问题来看,设置目标节点(点击的节点)及其下的节点的选择状态比较容易办到,如使用jquery
查询其下的所有子节点即可。比较困难的是,怎样设置父节点的状态(是否选中)?
可以想象的是,查询兄弟节点的状态,若兄弟节点有没有选中的,任务就结束了(直接返回),若兄弟节点都选中,说明其父节点也应该需要选中,好,设置父节点选中,这样还要判断父节点的兄弟节点…….这样一直循环到顶端节点。这就是所谓的递归的套路。那有没有简单的方法呢?
利用冒泡的解决方法
有一个解决的方法。原理和上面提到的类似,不过向上递归的过程我们用冒泡实现。
这种方法有一定的限制条件,首先我们把每个节点都添加一个相同的样式(有同一个class
),父节点需要包含子节点(即点击子节点时事件需能冒泡上传到父节点)。好了,这样我们只需处理一层父节点即可。
处理父节点时,若其兄弟节点有未选中的,阻止冒泡事件即可。
实践
如上面的示例,解决方法实现如下:
页面1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<html>
<head>
<title>多级选择组件</title>
<style>
.one-level {
margin-left: 30px;
}
</style>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
</head>
<body>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" value="" />河北省</label>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" value="" />石家庄市</label>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />新华区</label>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />A街道</label>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />B街道</label>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />C街道</label>
</div>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />桥西区</label>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />1街道</label>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />2街道</label>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />3街道</label>
</div>
</div>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />廊坊市</label>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />广阳区</label>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />A街道</label>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />B街道</label>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />C街道</label>
</div>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />开发区</label>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />A街道</label>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />B街道</label>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />C街道</label>
</div>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />安次区</label>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />A街道</label>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />B街道</label>
</div>
<div class="node one-level">
<button type="">选择</button>
<label><input type="checkbox" />C街道</label>
</div>
</div>
</div>
</div>
</body>
</html>
js核心实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43$('.node').on('click', function() {
// 判断事件源,只响应按钮单击事件
var tagName = event.target.tagName;
if(tagName!=='BUTTON'){
return false;
}
var name = $(this).children('label').text();
console.log('name:' + name);
var self = $(this).children('label').find('input')[0];
var children = $(this).children('.node').find('input');
if (self.checked) { //已选择,取消选择
self.checked = false;
var isAll = true;
for (let i = 0; i < children.length; i++) {
if (!children[i].checked) {
isAll = false;
break;
}
}
if (isAll) {
for (let i = 0; i < children.length; i++) {
children[i].checked = false;
}
}
} else { //未选择,勾选
self.checked = true;
for (let i = 0; i < children.length; i++) {
children[i].checked = true;
}
}
var sibs = $(this).siblings().children('label').find('input');
var isfull = true;
//查看兄弟节点,看其是否有未选中的
for (var i = 0; i < sibs.length; i++) {
if (!sibs[i].checked) {
isfull = false;
break;
}
}
if (!isfull) {
event.stopPropagation(); // 阻止事件冒泡
}
});
说明:
- 这样做会使单击事件的区域变为块状(区域为div),所以需要在开头判断事件源。
- 对于取消选中,需要判断:若是子节点有未选中的,则不需要管子节点,只需把自身取消选中即可。
- 本文未对中间态(选中、未选中、半选中)做处理,有兴趣的你可以试试