Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get All Elements By ClassName and Change ClassName

Tags:

javascript

I would like to...

  1. Scan the document for all elements that have a certain class name
  2. Perform some key functions on the innerHTML of that element
  3. Change the class name of that element so that if I do another scan later I don't redo that element

I thought this code would work but for some reason it breaks the loop after the first instance and the element's class names are never changed. No frameworks please.

function example()
{
    var elementArray;

    elementArray = document.getElementsByClassName("exampleClass");

    for(var i = 0; i < elementArray.length; i++)
    {
        // PERFORM STUFF ON THE ELEMENT
        elementArray[i].setAttribute("class", "exampleClassComplete");
        alert(elementArray[i].className);
    }   
}

EDIT (FINAL ANSWER) -- Here is the final product and how I have implemented @cHao's solution within my site. The objective was to grab an assortment of timestamps on the page and change them to time ago. Thank you all for your help, I learned a ton from this question.

function setAllTimeAgos()
{
    var timestampArray = document.getElementsByClassName("timeAgo");

    for(var i = (timestampArray.length - 1); i >= 0; i--)
    {
        timestampArray[i].innerHTML = getTimeAgo(timestampArray[i].innerHTML);
        timestampArray[i].className = "timeAgoComplete";
    }
}
like image 435
gmustudent Avatar asked Sep 11 '25 08:09

gmustudent


2 Answers

The problem is that the NodeList returned to you is "live" - it changes as you alter the class name. That is, when you change the class on the first element, the list is immediately one element shorter than it was.

Try this:

  while (elementArray.length) {
    elementArray[0].className = "exampleClassComplete";
  }

(There's no need to use setAttribute() to set the "class" value - just update the "className" property. Using setAttribute() in old versions of IE wouldn't work anyway.)

Alternatively, convert your NodeList to a plain array, and then use your indexed iteration:

  elementArray = [].slice.call(elementArray, 0);
  for (var i = 0; i < elementArray.length; ++i)
    elementArray[i].className = "whatever";

As pointed out in a comment, this has the advantage of not relying on the semantics of NodeList objects. (Note also, thanks again to a comment, that if you need this to work in older versions of Internet Explorer, you'd have to write an explicit loop to copy element references from the NodeList to an array.)

like image 127
Pointy Avatar answered Sep 13 '25 23:09

Pointy


Most DOM functions that return a list of elements, return a NodeList rather than an array. The biggest difference is that a NodeList is typically live, meaning that changing the document can cause nodes to magically appear or disappear. That can cause nodes to move around a bit, and throw off a loop that doesn't account for it.

Rather than turning the list into an array or whatever, though, you could simply loop backwards on the list you get back.

function example()
{
    var elements = document.getElementsByClassName("exampleClass");

    for(var i = elements.length - 1; i >= 0; --i)
    {
        // PERFORM STUFF ON THE ELEMENT
        elements[i].className = "exampleClassComplete";

        // elements[i] no longer exists past this point, in most browsers
    }   
}

The liveness of a NodeList won't matter at that point, since the only elements removed from it will be the ones after the one you're currently on. The nodes that appear before it won't be affected.

like image 43
cHao Avatar answered Sep 13 '25 23:09

cHao