When adding your event listener, you are creating a closure. Each event handler has the variable i
in their scope. Unfortunately, it's the same variable i
, the one that's incremented by your for loop.
At the end of the loop, each event handler has, in its scope, a variable i
whose value is now 6. It results in li[i]
being undefined when the event is fired.
You can solve this by, either :
- not using
i
in your handler, for instance by using the provided event
variable, that references the event. And thourh it, retrieves the targeted element :
var li = document.querySelectorAll("li");
for (var i = 0; i < li.length; i++) {
li[i].addEventListener("click", function(event) {
event.target.classList.toggle("done");
})
}
.done {
text-decoration: line-through;
}
<h1>Shopping List</h1>
<ul style="list-style-type:square">
<li>Pencil</li>
<li>Paper</li>
<li>Eraser</li>
<li>Table Lamp</li>
<li>Comb</li>
</ul>
- declaring
i
with let
instead of var
in your for loop. This will result in each of your closure to have a different binding with i
:
var li = document.querySelectorAll("li");
for (let i = 0; i < li.length; i++) {
li[i].addEventListener("click", function() {
li[i].classList.toggle("done");
})
}
.done {
text-decoration: line-through;
}
<h1>Shopping List</h1>
<ul style="list-style-type:square">
<li>Pencil</li>
<li>Paper</li>
<li>Eraser</li>
<li>Table Lamp</li>
<li>Comb</li>
</ul>
- attaching the event listener to the UL. This would be my favorite :
var ul = document.querySelector("ul");
ul.addEventListener("click", function(event) {
event.target.classList.toggle("done");
}, true);
.done {
text-decoration: line-through;
}
<h1>Shopping List</h1>
<ul style="list-style-type:square">
<li>Pencil</li>
<li>Paper</li>
<li>Eraser</li>
<li>Table Lamp</li>
<li>Comb</li>
</ul>
Read more about JavaScript closures.