Four months into javascript, and I ran into the following code that I can’t put in terms of the techniques that I understand. I was hoping that someone could refactor this code not in order to satisfy what its purpose is, but to put it in terms that I would understand. Perhaps just explaining how the forEach() works with call(). I understand them separately but not combined.
I have tried to rewrite it without the forEach and call but that defeats my purpose. I can implement this functionality without these two, but it sure makes the code succinct with them.
let parentElement = document.getElementById("node1");
let array = [];
parentElement.addEventListener("click", (e) => {
var elems = document.querySelectorAll(".active"); // make them all active
array.forEach.call(elems, function(el) {
el.classList.remove("active"); // remove active
});
e.target.className = "active"; // set clicked element to active
});
.active,
.btn:hover {
background-color: #666;
color: white;
}
<div id="node1">
<button class="btn">1</button>
<button class="btn">2</button>
</div>
2
Answers
This code pattern is a bit dated. I’ll explain how it works, and then show you a modern method.
The value of
elems
at this point is a NodeList. It is not an Array.Back in the day, you couldn’t use
.forEach()
on a NodeList. Therefore, you needed to borrow that method from Array. (It was added later, but for this example let’s assume it doesn’t exist yet.)This line allows you to use
Array.forEach()
over theelems
NodeList. You’re executing/calling thearray.forEach
function, withelems
as the "this
" object for context.The Modern Way
These days, you can use
for … of
, which allows you to iterate over iterables like NodeList, Arrays, and many others.I think you’ll find this syntax easier to read and understand.
(By the way, have you considered that radio button inputs might fit better for your use in this case? You can style them to look like buttons.)
We no longer need Array.call.
Also
var elems = document.querySelectorAll(".active"); // make them all active
does not make all active, but just selects the active onesAll modern browsers can do .forEach on the static (not live) NodeList returned from querySelectorAll (you need to spread to use map and filter line this:
[...elems].map(el => something(el))
)Here is the more concise modern way to do want you want – note the classList.toggle with the boolean to
force
the class when true