iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 10
0
自我挑戰組

新手村-30 Day JS Coding Challenge系列 第 10

新手村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

<!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 可以操作的項目,然後來看看裡面有什麼...

<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,反之

// 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

<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

<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,在將全部打勾的讀出來設定為打勾勾

<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

<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


上一篇
新手村09 - Dev Tools Domination
下一篇
新手村11 - Custom Video Player
系列文
新手村-30 Day JS Coding Challenge30

尚未有邦友留言

立即登入留言