事前規劃
關於今天的主題我的想法是:
建立一個變數來記錄最後打勾的 checkbox
建立新的陣列來儲存上次打勾與目前打勾的之間的全部元素
最後將新陣列中的全部元素都打勾
當然還需要考慮到使用者是否有按下 shift (有按下 shift 的狀態才會選取區間內的 checkbox),但話不多說我們就開始吧!
開始寫程式碼
:::warning
:zap: 程式碼由於拆解看起來可能有點亂,完整版請見 GitHub 程式碼連結
:::
從 CSS 可以看到當 input[type=”checkbox”] 被勾選後,同一層的 p 會被畫線,所以們我只需要單純處理 input[type=”checkbox”] 的狀態就好而不需要去管其它元素
input:checked + p {
background: #F9F9F9;
text-decoration: line-through;
}
首先先選取全部的 input[type=”checkbox”]
const checkboxes = document.querySelectorAll(‘input’);
偵測 shift 是否按下
接著建立一個變數來辨識使用者是否有按下 shift,預設 false,接著使用監聽事件來監測狀態:如果事件是 keydown 且按下 shift,則變數變為 true;反之當 keyup 時且無按下 shift,則變數變為 false
let holdingShift = false;
window.addEventListener(‘keydown’, function(e) {
if (e.keyCode === 16) { // keyCode 16 代表 shift
holdingShift = true;
}
});
window.addEventListener(‘keyup’, function(e) {
holdingShift = false;
});
追蹤最後打勾的元素
接著建立新變數來儲存最後打勾的 checkbox
let lastChecked;
使用 let 而不是 const 是因為之後值會變動
然後為每個 checkbox 設定監聽事件,並把被點取的 checkbox 存入 lastChecked 變數
checkBoxes.forEach((checkbox, index) => {
checkbox.addEventListener(‘click’, function(e) {
// 執行打勾功能
lastChecked = checkBoxes[index];
}
});
建立新陣列儲存要打勾的元素
這裡開始要做一些判斷了,當 lastChecked 已被打勾且使用者按下 shift 時,將 lastChecked 與目前點選項目之間的元素存入新的陣列
checkBoxes.forEach((checkbox, index) => {
checkbox.addEventListener(‘click’, function(e) {
// 執行打勾功能
if (lastChecked.checked && holdingShift) {
let newList;
}
lastChecked = checkBoxes[index];
}
});
此時會發現當按下 checkbox 時遇到錯誤:Uncaught TypeError: Cannot read property ‘checked’ of undefined,這是因為一開始我們並沒有賦予 lastChecked 值,故目前還是 undefined,所以需要另做處理
checkBoxes.forEach((checkbox, index) => {
checkbox.addEventListener(‘click’, function(e) {
// 當 lastChecked 是 undefined 時,lastChecked = 被點選的元素
if (lastChecked === undefined) {
lastChecked = checkbox;
}
// 執行打勾功能
if (lastChecked.checked && holdingShift) {
let newList;
}
lastChecked = checkBoxes[index];
}
});
建立要加入 newList 的元素
至此我們已經有了當前點擊元素 & 前一次點擊的元素(lastChecked),可以來補充 newList 的內容了!
我的想法是比較當前點擊元素 & 前一次點擊的元素(lastChecked)在 checkBoxes 陣列中的位置,並把在中間的元素加入新陣列。有什麼方法可以截取陣列的部分?
沒錯,就是使用 slice()!但 slice() 並不是 nodeList 可用的 method,所以需要先將原本的 checkBoxes 轉成陣列
為了要判斷順序,需要在全域再宣告新的變數 lastIndex 來紀錄上次點選元素的 index,並且在監聽事件中與 lastChecked 一同更新
const checkBoxes = Array.from(document.querySelectorAll(‘.inbox input[type=”checkbox”]’));
let holdingShift = false;
let lastIndex;
let lastChecked;
checkBoxes.forEach((checkbox, index) => {
checkbox.addEventListener(‘click’, function(e) {
// 當 lastChecked 是 undefined 時,lastChecked = 被點選的元素
if (lastChecked === undefined) {
lastChecked = checkbox;
}
// 執行打勾功能
if (lastChecked.checked && holdingShift) {
let newList;
}
lastIndex = index;
lastChecked = checkBoxes[index];
}
});
有時候可能會先點後方項目再點擊前面項目(例如點擊第 5 項再點擊第 2 項),所以要將各種情況做不同處理
if (index < lastIndex) {
newList = checkBoxes.slice(index, lastIndex);
} else if (lastIndex < index) {
newList = checkBoxes.slice(lastIndex, index);
} else return; // 當 index = lastIndex 時直接 return ,不然會做出長度為 0 的陣列
接著就把 newList 內全部的項目打勾即可!
newList.forEach(checkbox => {
checkbox.checked = true;
console.log(checkbox);
});
完成!
小補充
這裡順便補充一下右鍵的觸發事件;左鍵我們都知道是 click,而右鍵是 contextmenu 喔!