显示或隐藏表列
这篇文章展示了如何通过单击表的标题来显示或隐藏表的任何列
html
<table id="table">...</table>
<ul id="menu"></ul>
1
2
2
构建菜单
在#menu
添加一个菜单, 用于显示或隐藏表的列
html
<ul id="menu">
<li>
<!-- The check box to toggle the first column -->
<label>
<input type="checkbox" />
Label of first column
</label>
<!-- Other items ... -->
</li>
</ul>
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
创建菜单项, 遍历表头的每一列, 并为每一列创建一个菜单项
js
const menu = document.getElementById('menu')
const table = document.getElementById('table')
const headers = [].slice.call(table.querySelectorAll('th'))
// const headers = Array.from(table.querySelectorAll('th'));
headers.forEach(function (th, index) {
// Build the menu item
const li = document.createElement('li')
const label = document.createElement('label')
const checkbox = document.createElement('input')
checkbox.setAttribute('type', 'checkbox')
// Create the text node
const text = document.createTextNode(th.textContent)
label.appendChild(checkbox)
label.appendChild(text)
li.appendChild(label)
menu.appendChild(li)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
监听复选框事件, 当复选框被选中时, 显示对应的列, 否则隐藏对应的列
js
headers.forEach(function(th, index) {
// Build the menu item
...
// Handle the event
checkbox.addEventListener('change', function(e) {
e.target.checked ? showColumn(index) : hideColumn(index);
});
});
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
INFO
稍后我们将将实现showColumn
, hideColumn
方法。
切换菜单
- 右键单击表标题时将显示菜单
- 当用户在菜单外单击时,它将隐藏
js
const thead = table.querySelector('thead');
// Handle the `contextmenu` event of the header
thead.addEventListener('contextmenu', function(e) {
// Prevent the default context menu from being shown
e.preventDefault();
// Show the menu
...
document.addEventListener('click', documentClickHandler);
});
// Hide the menu when clicking outside of it
const documentClickHandler = function(e) {
...
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
切换表列
先给 th 和 td 添加一个data-column-index
属性, 用于标识列的索引
js
const numColumns = headers.length
const cells = [].slice.call(table.querySelectorAll('th, td'))
cells.forEach(function (cell, index) {
cell.setAttribute('data-column-index', index % numColumns)
})
1
2
3
4
5
6
2
3
4
5
6
然后根据data-column-index
属性获取列的索引,找到要隐藏的列, 并将其隐藏,实现hideColumn
方法
js
const hideColumn = function (index) {
cells
.filter(function (cell) {
return cell.getAttribute('data-column-index') === `${index}`
})
.forEach(function (cell) {
cell.style.display = 'none'
})
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
同样的思路实现showColumn
方法
js
const showColumn = function (index) {
cells
.filter(function (cell) {
return cell.getAttribute('data-column-index') === `${index}`
})
.forEach(function (cell) {
cell.style.display = ''
})
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
不允许隐藏最后一列
为了正常使用, 禁止隐藏最后一列, 让我们稍微修改一下构建菜单代码
js
headers.forEach(function(th, index) {
// Build the menu item
...
// 为checkbox添加一个属性, 用于标识列的索引
checkbox.setAttribute('data-column-index', index);
});
1
2
3
4
5
6
2
3
4
5
6
当每个列都隐藏时,我们添加一个自定义属性data-shown
来指示该列已被隐藏:
js
const hideColumn = function(index) {
cells
.filter(function(cell) {
...
})
.forEach(function(cell) {
...
cell.setAttribute('data-shown', 'false');
});
};
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
然后我们可以计算隐藏了多少列,如果只剩下一列,我们将禁用关联的复选框:
js
const hideColumn = function (index) {
// How many columns are hidden
const numHiddenCols = headers.filter(function (th) {
return th.getAttribute('data-shown') === 'false'
}).length
if (numHiddenCols === numColumns - 1) {
// There's only one column which isn't hidden yet
// We don't allow user to hide it
const shownColumnIndex = thead
.querySelector('[data-shown="true"]')
.getAttribute('data-column-index')
const checkbox = menu.querySelector(
`[type="checkbox"][data-column-index="${shownColumnIndex}"]`
)
checkbox.setAttribute('disabled', 'true')
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
为了使它完全工作,我们需要初始化每个单元格的属性,并在显示列时将其转回:data-shown=true
js
cells.forEach(function(cell, index) {
cell.setAttribute('data-shown', 'true');
});
const showColumn = function(index) {
cells
.filter(function(cell) {
...
})
.forEach(function(cell) {
...
cell.setAttribute('data-shown', 'true');
});
menu.querySelectorAll(`[type="checkbox"][disabled]`)
.forEach(function(checkbox) {
checkbox.removeAttribute('disabled');
});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
完整代码
点击查看完整代码
vue
<template>
<div class="container">
<table id="table" class="table">
<thead>
<tr>
<th data-type="number">No.</th>
<th>First name</th>
<th>Last name</th>
<th>Date of birth</th>
<th>Address</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Andrea</td>
<td>Ross</td>
<td>1985-12-24</td>
<td>95945 Rodrick Crossroad</td>
</tr>
<tr>
<td>2</td>
<td>Penelope</td>
<td>Mills</td>
<td>1978-8-11</td>
<td>81328 Eleazar Fork</td>
</tr>
<tr>
<td>3</td>
<td>Sarah</td>
<td>Grant</td>
<td>1981-5-9</td>
<td>5050 Boyer Forks</td>
</tr>
<tr>
<td>4</td>
<td>Vanessa</td>
<td>Roberts</td>
<td>1980-9-27</td>
<td>765 Daryl Street</td>
</tr>
<tr>
<td>5</td>
<td>Oliver</td>
<td>Alsop</td>
<td>1986-10-30</td>
<td>11424 Ritchie Garden</td>
</tr>
<tr>
<td>6</td>
<td>Jennifer</td>
<td>Forsyth</td>
<td>1983-3-13</td>
<td>04640 Nader Ramp</td>
</tr>
<tr>
<td>7</td>
<td>Michelle</td>
<td>King</td>
<td>1980-8-29</td>
<td>272 Alysa Fall</td>
</tr>
<tr>
<td>8</td>
<td>Steven</td>
<td>Kelly</td>
<td>1989-8-6</td>
<td>5749 Foster Pike</td>
</tr>
<tr>
<td>9</td>
<td>Julian</td>
<td>Ferguson</td>
<td>1981-9-17</td>
<td>6196 Wilkinson Parkways</td>
</tr>
<tr>
<td>10</td>
<td>Chloe</td>
<td>Ince</td>
<td>1983-10-28</td>
<td>9069 Daniel Shoals</td>
</tr>
</tbody>
</table>
<ul id="menu" class="container__menu container__menu--hidden"></ul>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
onMounted(() => {
const menu = document.getElementById('menu') as HTMLElement
const table = document.getElementById('table') as HTMLTableElement
const headers = [].slice.call(table.querySelectorAll('th'))
const cells = [].slice.call(table.querySelectorAll('th, td'))
const numColumns = headers.length
const thead = table.querySelector('thead') as HTMLTableSectionElement
thead.addEventListener('contextmenu', function (e) {
e.preventDefault()
const rect = thead.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
menu.style.top = `${y}px`
menu.style.left = `${x}px`
menu.classList.toggle('container__menu--hidden')
document.addEventListener('click', documentClickHandler)
})
// Hide the menu when clicking outside of it
const documentClickHandler = function (e) {
const isClickedOutside = !menu.contains(e.target)
if (isClickedOutside) {
menu.classList.add('container__menu--hidden')
document.removeEventListener('click', documentClickHandler)
}
}
const showColumn = function (index) {
cells
.filter(function (cell) {
return cell.getAttribute('data-column-index') === `${index}`
})
.forEach(function (cell) {
cell.style.display = ''
cell.setAttribute('data-shown', 'true')
})
menu.querySelectorAll(`[type="checkbox"][disabled]`).forEach(function (checkbox) {
checkbox.removeAttribute('disabled')
})
}
const hideColumn = function (index) {
cells
.filter(function (cell) {
return cell.getAttribute('data-column-index') === `${index}`
})
.forEach(function (cell) {
cell.style.display = 'none'
cell.setAttribute('data-shown', 'false')
})
// How many columns are hidden
const numHiddenCols = headers.filter(function (th) {
return th.getAttribute('data-shown') === 'false'
}).length
if (numHiddenCols === numColumns - 1) {
// There's only one column which isn't hidden yet
// We don't allow user to hide it
const shownColumnIndex = thead?.querySelector('[data-shown="true"]')?.getAttribute('data-column-index')
const checkbox = menu?.querySelector(
`[type="checkbox"][data-column-index="${shownColumnIndex}"]`
)
checkbox?.setAttribute('disabled', 'true')
}
}
cells.forEach(function (cell, index) {
cell.setAttribute('data-column-index', index % numColumns)
cell.setAttribute('data-shown', 'true')
})
headers.forEach(function (th, index) {
// Build the menu item
const li = document.createElement('li')
const label = document.createElement('label')
const checkbox = document.createElement('input')
checkbox.setAttribute('type', 'checkbox')
checkbox.setAttribute('checked', 'true')
checkbox.setAttribute('data-column-index', index)
checkbox.style.marginRight = '.25rem'
const text = document.createTextNode(th.textContent)
label.appendChild(checkbox)
label.appendChild(text)
label.style.display = 'flex'
label.style.alignItems = 'center'
li.appendChild(label)
menu?.appendChild(li)
// Handle the event
checkbox.addEventListener('change', function (e) {
e.target.checked ? showColumn(index) : hideColumn(index)
menu?.classList.add('container__menu--hidden')
})
})
})
</script>
<style lang="scss" scoped>
.container {
position: relative;
}
.container__menu {
/* Absolute position */
position: absolute;
/* Reset */
list-style-type: none;
margin: 0;
padding: 0;
/* Misc */
background-color: #f7fafc;
border: 1px solid #cbd5e0;
border-radius: 0.25rem;
padding: 0.5rem;
}
.container__menu--hidden {
display: none;
}
.table {
border-collapse: collapse;
width: 100%;
}
.table,
.table th,
.table td {
border: 1px solid #ccc;
}
.table th,
.table td {
padding: 0.5rem;
}
.table th {
user-select: none;
}
</style>
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
示例
INFO
右键单击表头, 显示菜单, 选择要隐藏的列, 可以看到隐藏的列已经被隐藏, 只有最后一列时菜单中的复选框被禁用
No. | First name | Last name | Date of birth | Address |
---|---|---|---|---|
1 | Andrea | Ross | 1985-12-24 | 95945 Rodrick Crossroad |
2 | Penelope | Mills | 1978-8-11 | 81328 Eleazar Fork |
3 | Sarah | Grant | 1981-5-9 | 5050 Boyer Forks |
4 | Vanessa | Roberts | 1980-9-27 | 765 Daryl Street |
5 | Oliver | Alsop | 1986-10-30 | 11424 Ritchie Garden |
6 | Jennifer | Forsyth | 1983-3-13 | 04640 Nader Ramp |
7 | Michelle | King | 1980-8-29 | 272 Alysa Fall |
8 | Steven | Kelly | 1989-8-6 | 5749 Foster Pike |
9 | Julian | Ferguson | 1981-9-17 | 6196 Wilkinson Parkways |
10 | Chloe | Ince | 1983-10-28 | 9069 Daniel Shoals |