addEventListener on a querySelectorAll() with classList

I am trying to add an event listener but no result came. I know JavaScript has a hoisting feature but I believe I tried all except the correct solution.

const cbox = document.querySelectorAll(".box");
function doit() {
  for (let i = 0; i < cbox.length; i++){
    cbox[i].classList.add("red");
  }
}
cbox.addEventListener("click", doit, false);

Can somebody spot the mistake I make?


Solution 1:

There are some dissimilarities between the code and the link you have provided. There is no function doit() in there.

You have attached addEvenListener to the NodeList in cbox.addEventListener("click",....., you have to loop through the list and attach the event to the current element:

Try the following:

const cbox = document.querySelectorAll(".box");

 for (let i = 0; i < cbox.length; i++) {
     cbox[i].addEventListener("click", function() {
       cbox[i].classList.toggle("red");
     });
 }
*,
html,
body {
    padding: 0;
    margin: 0;
}

.box {
    width: 10rem;
    height: 10rem;
    background-color: yellowgreen;
    float: left;
    position: relative;
    margin: 0.5rem;
    transition: .5s all;
}

h3 {
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.box:not(:first-child) {
    margin-left: 1rem;
}

.red {
    background-color: orangered;
}
<div id="box1" class="box box1">
    <h3>Box 1</h3>
</div>
<div id="box2" class="box box2">
    <h3>Box 2</h3>
</div>
<div id="box3" class="box box3">
    <h3>Box 3</h3>
</div>
<div id="box4" class="box box4">
    <h3>Box 4</h3>
</div>

You can also use Array.prototype.forEach() with arrow function syntax that will allow you to achieve the same with less code:

let cbox = document.querySelectorAll(".box");
cbox.forEach(box => {
  box.addEventListener('click', () => box.classList.toggle("red"));
});
*,
html,
body {
    padding: 0;
    margin: 0;
}

.box {
    width: 10rem;
    height: 10rem;
    background-color: yellowgreen;
    float: left;
    position: relative;
    margin: 0.5rem;
    transition: .5s all;
}

h3 {
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.box:not(:first-child) {
    margin-left: 1rem;
}

.red {
    background-color: orangered;
}
<div id="box1" class="box box1">
    <h3>Box 1</h3>
</div>
<div id="box2" class="box box2">
    <h3>Box 2</h3>
</div>
<div id="box3" class="box box3">
    <h3>Box 3</h3>
</div>
<div id="box4" class="box box4">
    <h3>Box 4</h3>
</div>

Solution 2:

ES6 makes this a bit simpler:

document.querySelectorAll(".box").forEach(box => 
  box.addEventListener("click", () => box.classList.toggle("red"))
)

Example implementation:

document.querySelectorAll(".box").forEach(box => 
  box.addEventListener("click", () => box.classList.toggle("red"))
)
.box {
    width: 5rem;
    height: 5rem;
    background-color: yellowgreen;
    display: inline-block;
}

.box.red {
    background-color: firebrick;
}
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>

Solution 3:

You can use forEach on the class or use Event delegation.

const cboxes = document.querySelectorAll(".box");
function doit() {
    ... do something ...
}
cboxes.forEach(
  function(cbox) {
   cbox.addEventListener("click", doit,false);
  }
);

Notice that I changed your variable name.

EventDelgation

HTML:

<div id="parent">
  <div id="box1" class="box box1">
    <h3>Box 1</h3>
  </div>
  <div id="box2" class="box box2">
      <h3>Box 2</h3>
  </div>
  <div id="box3" class="box box3">
      <h3>Box 3</h3>
  </div>
  <div id="box4" class="box box4">
      <h3>Box 4</h3>
  </div>
</div>

The JS part:

const parent = document.querySelector("#parent");

parent.addEventListener('click', (e) => {
  e.target.classList.add('red');
  console.log(e.target);
});

Event delegation is much better and it uses fewer resources, as you only use 1 Event listener and no for loop.