This is the tenth project of Wes Bos'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 -
We'll track the latest checked in lastChecked
, every time 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))
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
}
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).
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 rationalizing how this should look if it were perfect. Hence my decision to leave it as it is.