iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 10
0
Modern Web

JS30 錄系列 第 10

Day 10 - Hold Shift and Check Checkboxes

任務目標

喧猴結婚了.

開玩笑的, 喧猴為了體驗婚後生活, 模仿未來的老婆寫了一張家事清單給自己. 喧猴希望透過一件一件的完成清單上的事情來證明自己也是有當家庭主夫的能力!
但是執行沒多久他就懶了, 看著清單上的待辦事項, 他心裡想著...

「能不能直接勾選其中一個項目, 然後按住Shift, 點選另外一個項目, 就將介於中間所有的清單項目都全選了, 假裝自己完成了這些項目呢?」

於是他找上了我們. 沒錯, 按住Shift達到多重選取, 就是今天的課題! 而且喧猴特別強調, 他希望不管是往上勾選還是往下勾選, 都必須要能夠多選.

來吧!

HTML架構如下:

<div class="inbox">
  <div class="item">
    <input type="checkbox">
    <p>Mop the Floor</p>
  </div>
  <div class="item">
    <input type="checkbox">
    <p>Clean the toliet</p>
  </div>
  <div class="item">
    <input type="checkbox">
    <p>Clean the bed</p>
  </div>
  <div class="item">
    <input type="checkbox">
    <p>Wash dishes</p>
  </div>
  <div class="item">
    <input type="checkbox">
    <p>Watering plants</p>
  </div>
  <div class="item">
    <input type="checkbox">
    <p>Kick some ass</p>
  </div>
</div>

作法

首先,每次點擊清單的任何勾選欄位時, 都得呼叫自訂函式來處理「如何勾選」這個問題, 因此JS基本架構如下:

// 選取每個勾選欄位
const checkboxes = document.querySelectorAll(`.inbox input[type="checkbox"]`);

function handleCheck(e) {
  // 處理如何勾選
}

// 為每個勾選欄位設置監聽器
checkboxes.forEach(checkbox => checkbox.addEventListener('click', handleCheck));

那如何勾選呢? 要把「中間段」的欄位全部勾選, 意味著欄位必須有「頭」有「尾」, 才會存在著中間段, 因此「頭」的限制是很重要的.

要成為「頭」必須達到兩個條件: 第一, 該欄位必須是被勾選的, 也就是該input標籤必須有checked屬性. 第二, 在點擊該欄位的同時, Shift 鍵必須是被按住. 如果沒有按 Shift , 代表我們只是想要進行普通勾選, 而不是多選.

接下來要有「尾」. 通常我們選取一排東西的行為會像這樣, 先勾選[1], 再按住 Shift 勾選一個[2], 中間的都被選取了. 再按住 Shift 勾選一個[3], [2]和[3]之間都被勾選了. 也就是說, 每次勾選一個新項目, 舊的項目就可以設成「尾」.

依照上述概念, 程式碼雛形如下:

// 宣告可變的變數「尾」
let lastChecked ;  

function handleCheck(e) {
  // 判斷此次勾選的項目是否達到「頭」的條件
  if(e.shiftKey && this.checked) {
    // 如果有, 執行程式碼將中間段全部勾選
  }
  
  // 執行完畢後, 將現在這個欄位指定為下一次的「尾」
  lastChecked = this;
}

補充一下, 程式碼內出現的this在這裡指向呼叫handleCheck函式的物件, 也就是<input type="checkbox">元素本人.

補充二, e.shiftKeyclick事件的屬性, 如果click事件觸發的瞬間, Shift 鍵是被按下的, e.shiftKey便為true, 這可以用來代替e.keyCode == 16,真是不錯.

太好了, 接下來要來處理中間段了. 剛提到介於頭跟尾之間的稱為中間段, 首先要將中間段標示出來.

標示中間段的過程類似開關的概念, 首先設置一個旗標( flag )作為開關, 命名isBetween.

接著從剛剛選出來的「頭」開始迭代到「尾」, 遇到「頭」的時候把isBetween開關打開: isBetween = true, 接下來開始迭代每一個選項, 這段期間isBetween都維持在true的狀態. 到「尾」時再讓isBetween開關關閉: isBetween = false. 如此一來, 只要在每次迭代的過程加入if(isBetween === true) { //勾選 }, 就能把這些中間段全部勾選.

別忘了每次迭代前先初始化inBetween開關, 以免上次的結果意外殘留而影響到下次的運行.

聽起來微抽象, 看看程式碼就會清楚些了:

function handleCheck(e) {
  // 初始化旗標
  let inBetween = false;

  if(e.shiftKey && this.checked) {
    //從頭到尾開始迭代
    checkboxes.forEach(checkbox => {
      //  遇到頭, 尾時分別打開, 關掉開關
      if(checkbox === this || checkbox === lastChecked) {
        inBetween = !inBetween;
      }
      
      // 在迭代期間, 只要開關是打開的, 就勾選該欄位
      if(inBetween) {
        checkbox.checked = true;
      }     
    })
  }
  lastChecked = this;
}

如此一來, 喧猴的要求就被滿足了! 喧猴表達了感激, 同時他的婚後生活體驗也告了一段落.

以上就是 JS30 第十篇!

Reference

練習的完整程式碼


上一篇
Day 9 - Dev Tools
下一篇
Day 11 - Custom Video Player
系列文
JS30 錄30

尚未有邦友留言

立即登入留言