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 -
- Keep track of the latest checked
- See if shift is being clicked
- 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.