新手村10 - Hold Shift and Check Checkboxes

10 - Hold Shift and Check Checkboxes

俗話說的好,一天一蘋果,醫生遠離我

一天一 JS,What the f*ck JavaScript?

small steps every day - 記錄著新手村日記

完成目標

  • 功能
    • 對其中一個 checkbox 打勾,再按住 shift 對另一個打勾,中間的選項會自動勾起來
  • 畫面
    • 滑鼠點 checkbox要打勾,再點擊則取消
    • 「按住shift」勾選,若畫面上已有打勾,則此次和上次打勾的選項中間都要自動打勾

index_START.html

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hold Shift to Check Multiple Checkboxes</title>
</head>
<body>
<style>

html {
font-family: sans-serif;
background: #ffc600;
}

.inbox {
max-width: 400px;
margin: 50px auto;
background: white;
border-radius: 5px;
box-shadow: 10px 10px 0 rgba(0,0,0,0.1);
}

.item {
display: flex;
align-items: center;
border-bottom: 1px solid #F1F1F1;
}

.item:last-child {
border-bottom: 0;
}

input:checked + p {
background: #F9F9F9;
text-decoration: line-through;
}

input[type="checkbox"] {
margin: 20px;
}

p {
margin: 0;
padding: 20px;
transition: background 0.2s;
flex: 1;
font-family:'helvetica neue';
font-size: 20px;
font-weight: 200;
border-left: 1px solid #D1E2FF;
}
</style>
<!--
The following is a common layout you would see in an email client.

When a user clicks a checkbox, holds Shift, and then clicks another checkbox a few rows down, all the checkboxes inbetween those two checkboxes should be checked.

-->
<div class="inbox">
<div class="item">
<input type="checkbox">
<p>This is an inbox layout.</p>
</div>
<div class="item">
<input type="checkbox">
<p>Check one item</p>
</div>
<div class="item">
<input type="checkbox">
<p>Hold down your Shift key</p>
</div>
<div class="item">
<input type="checkbox">
<p>Check a lower item</p>
</div>
<div class="item">
<input type="checkbox">
<p>Everything in between should also be set to checked</p>
</div>
<div class="item">
<input type="checkbox">
<p>Try to do it without any libraries</p>
</div>
<div class="item">
<input type="checkbox">
<p>Just regular JavaScript</p>
</div>
<div class="item">
<input type="checkbox">
<p>Good Luck!</p>
</div>
<div class="item">
<input type="checkbox">
<p>Don't forget to tweet your result!</p>
</div>
</div>

<script>
</script>
</body>
</html>

JS - step by step

首先,透過 querySelectorAll 抓出 type="checkbox" ,為了方便之後可以使用 foreach 迴圈讀出每項checkbox,因此再將它轉為陣列 Array.from,如果被點擊的話,觸發 clickHandler 這個方法印出 MouseEvent 可以操作的項目,然後來看看裡面有什麼…

1
2
3
4
5
6
7
8
9
10
11
<script>
const checkboxes = Array.from(document.querySelectorAll('.inbox input[type="checkbox"]'));

checkboxes.forEach(function(input){
input.addEventListener("click", clickHandler);
})

function clickHandler(env){
console.log(env);
}
</script>

根據題目要求,我們要執行的是按住 Shift 按鈕觸發功能,在上方程式碼的 env 變數中,印出來的觸發事件如下,可以看到 shiftKey: false 這個事件物件,勾起來是 true,反之

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// MouseEvent {isTrusted: true, screenX: 287, screenY: 265, clientX: 316, clientY: 80, …}
// altKey: false
// bubbles: true
// button: 0
// buttons: 0
// cancelBubble: false
// cancelable: true
// clientX: 316
// clientY: 80
// -------------------略--------------------
// screenX: 287
// screenY: 265
// shiftKey: false
// sourceCapabilities: InputDeviceCapabilities {firesTouchEvents: true}
// srcElement: input
// target: input
// timeStamp: 1173.464999999851
// toElement: input
// type: "click"
// view: Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
// which: 1
// x: 316
// y: 80
// __proto__: MouseEvent

不過要先要紀錄我們點到的 checkbox 是陣列中的第幾個,透過 indexOf 算出並記於變數 firstCheck

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
const checkboxes = Array.from(document.querySelectorAll('.inbox input[type="checkbox"]'));

checkboxes.forEach(function(input){
input.addEventListener("click", clickHandler);
})

function clickHandler(env){
// console.log(env);
firstCheck = checkboxes.indexOf(this)
console.log(firstCheck);
}
</script>

變數 firstCheck 代表紀錄使用者一開始點的 Checkbox,先宣告一開始是空值,如果使用者點下 Checkbox,就記錄下剛剛抓的陣列第幾個,反之為 null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
const checkboxes = Array.from(document.querySelectorAll('.inbox input[type="checkbox"]'));

checkboxes.forEach(function(input){
input.addEventListener("click", clickHandler);
})

let firstCheck = null;

function clickHandler(env){
// console.log(env);
if (this.checked) {
firstCheck = checkboxes.indexOf(this);
} else {
firstCheck = null;
}
}
</script>

當有 ShiftKey 並且 firstCheck 不是空值的話,可以抓到使用者第二個按下的按鈕,並且透過 slice 將點擊的兩個按鈕排列 Math.min / Math.max,在將全部打勾的讀出來設定為打勾勾

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
<script>
const checkboxes = Array.from(document.querySelectorAll('.inbox input[type="checkbox"]'));

checkboxes.forEach(function(input){
input.addEventListener("click", clickHandler);
})

let firstCheck = null;

function clickHandler(env){
if (this.checked) {
if (env.shiftKey && firstCheck !== null) {
let secondCheck = checkboxes.indexOf(this);
checkboxes
.slice(
Math.min(secondCheck, firstCheck),
Math.max(secondCheck, firstCheck)
)
.forEach(input => (input.checked = true));
}
firstCheck = checkboxes.indexOf(this);
} else {
firstCheck = null;
}
}
</script>

就大功告成啦!

JS - Final

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
<script>
const checkboxes = Array.from(document.querySelectorAll('.inbox input[type="checkbox"]'));

checkboxes.forEach(function(input){
input.addEventListener("click", clickHandler);
})

let firstCheck = null;

function clickHandler(env){
if (this.checked) {
if (env.shiftKey && firstCheck !== null) {
let secondCheck = checkboxes.indexOf(this);
checkboxes
.slice(
Math.min(secondCheck, firstCheck),
Math.max(secondCheck, firstCheck)
)
.forEach(input => (input.checked = true));
}
firstCheck = checkboxes.indexOf(this);
} else {
firstCheck = null;
}
}
</script>

本刊同步於個人網站:http://chestertang.site/

本次範例程式碼原作者來源:https://tinyurl.com/yavm5f5n