To select elements from the DOM, we use browsers APIs like getElementsBy*. To make our lives easier, the result of these methods are Live. What this means is that when the DOM updates, I don't need to fetch it again from the DOM to see the update.
This could be a good thing because we don't have to traverse the DOM again. However, it may also have some undesirable side effects. Here is one I had to deal with recently.
HTML
<button id="clickme">
<div class="list">
<h2>Hello</h2>
</div>
<div class="list">
<h2>World</h2>
</div>
<div class="list">
<h2>App</h2>
</div>
<div class="not-list">
<h2>Hello World</h2>
</div>
CSS
.hide {
display:none;
}
JavaScript
var clickme = document.getElementById("clickme"),
list = document.getElementsByClassName("list");
clickme.onclick = function (){
var i, l = list.length;
for (i = 0;i < l; i++){
list[i].className = "hide";
}
};
This code looks simple enough. When I click on the button, all divs with class list
are supposed to disappear. Unfortunately, when I run the code, I get an error.
Uncaught TypeError: Cannot set property 'className' of undefined
The problem is getElementsByTagName
returns an HTMLCollection not an Array. And this collection is live, it updates as the values change on the DOM.
When we change the class name from list
to hide
the criteria for the selector is no longer valid, hence it is removed from the list. On the first iteration, there are 3 elements in the array, second is 2, third is 1. Which means by the time we reach the third iteration of the loop list[2]
is no longer valid. It returns undefined.
Sometimes we want to change classes name but still want the objects to be present in our array like object. To do so, we have to convert HTMLCollections into arrays first. Here is a simple function that will do that.
function collectionToArray(collection){
var i, length = collection.length,
array = [];
for (i = 0;i< length;i++){
array.push(collection[i]);
}
return array;
}
Now although the individual elements still updates to reflect the DOM, the collection as a whole will not be updated.
var clickme = document.getElementById("clickme"),
list = document.getElementByClassName("list");
list = collectionToArray(list);
clickme.onclick = function (){
var i, l = list.length;
for (i = 0;i < l; i++){
list[i].className = "hide";
}
alert(list.length);
};
You can reuse the list object and all the elements will still be inside intact.
I hope this tip helps you.
Sign up for the Newsletter.
Comments
There are no comments added yet.
Let's hear your thoughts