How can I replace one class with another on all elements, using just the DOM?

I just want to change a classname to another one.

I've tried using

document.getElementsByClassName("current").setAttribute("class", "none");

but it doesn't work. I'm new at javascript.


Explanation

document.getElementsByClassName returns an HTMLCollection, not just an array of elements. That means that the collection is live, so in this specific situation, it retains the requirement that it will always hold all elements with the class "current".

Coincidentally, you are removing the very class that the collection depends on, therefore updating the collection. It would be totally different if you were setting the value property (for example) in the loop - the collection wouldn't be affected, because the class "current" wasn't removed. It would also be totally different if you were adding a class, such as el.className += " none";, but that isn't the case.

A great description from the MDN docs:

HTMLCollections in the HTML DOM are live; they are automatically updated when the underlying document is changed.

Approach 1

An easy way to overcome all this pandemonium is by looping backwards.

var els = document.getElementsByClassName('current'),
    i = els.length;

while (i--) {
    els[i].className = 'none';
}

DEMO: http://jsfiddle.net/fAJgT/

(the code in the demo has a setTimeout, simply so you can see the original border color at first, then after 1.5 seconds, see it change)

This works because it modifies the last item in the collection - when it is modified (and automatically removed), move onto the item before it. So it doesn't suffer any consequences of the automatic removal.

An alternate setup, doing the same thing, is:

for (i = els.length; i >= 0; i--) {

Approach 2

Another answer made me realize you could just continually operate on the first item found. When you remove the specific class, the element is removed from the collection, so you're guaranteed that the first item is always a fresh item in the collection. Therefore, checking the length property should be a safe condition to check. Here's an example:

var els = document.getElementsByClassName('current');
while (els.length) {
    els[0].className = 'none';
}

DEMO: http://jsfiddle.net/EJLXe/

This is basically saying "while there's still items in the collection, modify the first one (which will be removed after modified)". I really wouldn't recommend ever using that method though, because it only works specifically because you end up modifying the collection. This would infinitely loop if you were not removing the specific class, or with a normal array or a non-live collection (without spliceing).

Approach 3

Another option is to slice (shallow copy) the collection into an array and loop through that normally. But I don't see any reason/improvement to do that. Here's an example anyways:

var els = document.getElementsByClassName('current'),
    sliced = Array.prototype.slice.call(els), i;

for (i = 0; i < sliced.length; i++) {
    sliced[i].className = 'none';
}

DEMO: http://jsfiddle.net/LHe95/2/

Approach 4

Finally, you could use document.querySelector - it returns a non-live NodeList (therefore you can loop like normal), and even has better support in browsers than document.getElementsByClassName does. Here's an example:

var els = document.querySelectorAll('.current'),
    i;

for (i = 0; i < els.length; i++) {
    els[i].className = 'none';
}

DEMO: http://jsfiddle.net/xq8Xr/


References:

  • document.getElementsByClassName: https://developer.mozilla.org/en-US/docs/DOM/document.getElementsByClassName
  • HTMLCollection: https://developer.mozilla.org/en-US/docs/DOM/HTMLCollection
  • slice: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/slice
  • querySelectorAll: https://developer.mozilla.org/en-US/docs/DOM/Document.querySelectorAll

document.getElementsByClassName("current") returns a HTMLCollection, not an HTMLElement. You can access elements in the list the same way you would access an array.

The following will change the class of the first element in the HTMLCollection.

document.getElementsByClassName("current")[0].setAttribute("class", "none");

However, you do not have to use the setAttribute method, you can just set the className property of the element.

 element.className = 'none';

Please note that HTMLCollection are live, wich means that as soon as the element will not have the current class name anymore, the element will be removed from the array, so to avoid any issues you could iterate over it using the following approach:

var list = document.getElementsByClassName("current");

while (list.length) {
    list[0].className = 'none';
}