JavaScript

Dealing with Live HTML Collections

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.


Comments

There are no comments added yet.

Let's hear your thoughts

For my eyes only