JS Checkbox

This is the tenth project of WesBos's JS30 series. To see the whole 30 part series, click here We'll be building a gmail style "hold shift and check all" items type list. You can check any element in the list, then hold shift and check another element, and all the elements in between should also get checked.

Check out the video here

The starter code

We won't be touching the HTML or CSS in this project, but just have a look anyway, the structure is straightforward.

The HTML is primarily a div.inbox with div.item as children. Each item has an checkbox <input> and a <p> element.

<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>
  ... <!-- more items -->
</div>

Clicking on the checkbox strikes through the sibling paragraph. The CSS responsible for that -

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

Now let's get on with our JS bits! At a high level we have the following steps -

  1. Keep track of the latest checked
  2. See if shift is being clicked
  3. If shift is clicked, run through the list of items and check everything in between

Keep track of latest checked

We'll track the latest checked in lastChecked, everytime there is a click on a checkbox we'll update the lastChecked.

const checkboxes = document.querySelectorAll('.inbox input[type="checkbox"]')
let lastChecked

function handleCheck(e) {
    lastChecked = this
}

checkboxes.forEach(checkbox => checkbox.addEventListener('click', handleCheck))

Check for shift

We need to check for shift because only if shift is pressed do we want to do a group check of checkboxes.

function handleCheck(e) {

  //check if shift key was pressed 
  // and that the checkbox is checked
  if (e.shiftKey && this.checked) {
    //logic goes here
  }

  // the logic comes before updating last check as we don't want to 
  //   overwrite the variable before using it
  lastChecked = this
}

Run through the list of items

Now we need to loop through all the items in the inbox until we hit the lastChecked checkbox or the checkbox currently being clicked. If we hit lastChecked first, then we need to continue checking all boxes till we encounter the current box. If we come across the current box first, then we check until we hit lastChecked. Either way we need to check all items in between lastChecked and the current item.

function handleCheck(e) {

  if (e.shiftKey && this.checked) {
    let inBetween = false
    checkboxes.forEach(cb => {
      if(cb === this || cb === lastChecked) inBetween = !inBetween

      if(inBetween) cb.checked = true
    })
  }

  lastChecked = this
}

We use inBetween to keep track of whether the current element in the loop is in between this and lastChecked.

The codepen with the final code


NOTE I know there are certain "bugs" in the code (same as Wes Bos's).

  • Check something, uncheck it and then shift check another box, everything inbetween is still checked.
  • shift+check the very first time you're clicking a box, everything till the end is checked as well.

To tackle these wouldn't be too hard code wise, but I let it be. It is going to be hard user flow wise. Since I don't really have an end user, there isn't much point rationalising how this should look if it were perfect. Hence my decision to leave it as it is.