How to correctly iterate through getElementsByClassName

JavascriptHtmlDom

Javascript Problem Overview


I am Javascript beginner.

I am initing web page via the window.onload, I have to find bunch of elements by their class name (slide) and redistribute them into different nodes based on some logic. I have function Distribute(element) which takes an element as input and does the distribution. I want to do something like this (as outlined for example here or here):

var slides = getElementsByClassName("slide");
for(var i = 0; i < slides.length; i++)
{
   Distribute(slides[i]);
}

however this does not do the magic for me, because getElementsByClassName does not actually return array, but a NodeList, which is...

...this is my speculation...

...being changed inside function Distribute (the DOM tree is being changed inside this function, and cloning of certain nodes happen). For-each loop structure does not help either.

The variable slides act's really un-deterministicaly, through every iteration it changes it's length and order of elements wildly.

What is the correct way to iterate through NodeList in my case? I was thinking about filling some temporary array, but am not sure how to do that...

EDIT:

important fact I forgot to mention is that there might be one slide inside another, this is actually what changes the slides variable as I have just found out thanks to user Alohci.

The solution for me was to clone each element into an array first and pass the array ono-by-one into Distribute() afterwards.

Javascript Solutions


Solution 1 - Javascript

According to MDN, the way to retrieve an item from a NodeList is:

nodeItem = nodeList.item(index)

Thus:

var slides = document.getElementsByClassName("slide");
for (var i = 0; i < slides.length; i++) {
   Distribute(slides.item(i));
}

I haven't tried this myself (the normal for loop has always worked for me), but give it a shot.

Solution 2 - Javascript

If you use the new querySelectorAll you can call forEach directly.

document.querySelectorAll('.edit').forEach(function(button) {
    // Now do something with my button
});

Per the comment below. nodeLists do not have a forEach function.

If using this with babel you can add Array.from and it will convert non node lists to a forEach array. Array.from does not work natively in browsers below and including IE 11.

Array.from(document.querySelectorAll('.edit')).forEach(function(button) {
    // Now do something with my button
});

At our meetup last night I discovered another way to handle node lists not having forEach

[...document.querySelectorAll('.edit')].forEach(function(button) {
    // Now do something with my button
});

Browser Support for [...]

Showing as Node List

Showing as Node List

Showing as Array

Showing as Array

Solution 3 - Javascript

An up-to-date answer in 2021

.getElementsBy* methods return a live HTMLCollection, not a NodeList, getElementsByName being an exception.

There are remarkable differencences between these two lists. Whereas HTMLCollection has two methods, NodeList has five methods, including NodeList.forEach, which can be used to iterate through a NodeList.

Live collections are problematic because there's no way to keep the collection updated under the hood. To achieve a reliable collection, the DOM is traversed every time a collection is accessed, in every current implementation of HTMLCollection. In practice this means, that every time you access a member of a live collection (including the length), the browser traverses the entire document to find the specific element.

The Standard says: >If a collection is live, then the attributes and methods on that object must operate on the actual underlying data, not a snapshot of the data.

Never iterate live HTMLCollection!

Instead, convert the collection to array, and iterate that array. Or rather get the elements using .querySelectorAll, which gives you a static NodeList and a more flexible way to select elements.

If you really need a live list of elements, use the closest possible common ancestor element as the context instead of document.

It's notable, that also live NodeLists exist. Examples of live NodeLists are Node.childNodes and the return value of getElementsByName.

Solution 4 - Javascript

You could always use array methods:

var slides = getElementsByClassName("slide");
Array.prototype.forEach.call(slides, function(slide, index) {
    Distribute(slides.item(index));
});

Solution 5 - Javascript

I followed Alohci's recommendation of looping in reverse because it's a live nodeList. Here's what I did for those who are curious...

  var activeObjects = documents.getElementsByClassName('active'); // a live nodeList

  //Use a reverse-loop because the array is an active NodeList
  while(activeObjects.length > 0) {
    var lastElem = activePaths[activePaths.length-1]; //select the last element

    //Remove the 'active' class from the element.  
    //This will automatically update the nodeList's length too.
    var className = lastElem.getAttribute('class').replace('active','');
    lastElem.setAttribute('class', className);
  }

Solution 6 - Javascript

 <!--something like this-->	
<html>
<body>



<!-- i've used for loop...this pointer takes current element to apply a 
 particular change on it ...other elements take change by else condition 
-->  


<div class="classname" onclick="myFunction(this);">first</div>  
<div class="classname" onclick="myFunction(this);">second</div>


<script>
function myFunction(p) {
 var x = document.getElementsByClassName("classname");
 var i;
 for (i = 0; i < x.length; i++) {
	if(x[i] == p)
    {
x[i].style.background="blue";
    }
    else{
x[i].style.background="red";
    }
}
}


</script>
<!--this script will only work for a class with onclick event but if u want 
to use all class of same name then u can use querySelectorAll() ...-->




var variable_name=document.querySelectorAll('.classname');
for(var i=0;i<variable_name.length;i++){
variable_name[i].(--your option--);
}



 <!--if u like to divide it on some logic apply it inside this for loop 
 using your nodelist-->

</body>
</html>

Solution 7 - Javascript

I had a similar issue with the iteration and I landed here. Maybe someone else is also doing the same mistake I did.

In my case, the selector was not the problem at all. The problem was that I had messed up the javascript code: I had a loop and a subloop. The subloop was also using i as a counter, instead of j, so because the subloop was overriding the value of i of the main loop, this one never got to the second iteration.

var dayContainers = document.getElementsByClassName('day-container');
for(var i = 0; i < dayContainers.length; i++) { //loop of length = 2
        var thisDayDiv = dayContainers[i];
        // do whatever

        var inputs = thisDayDiv.getElementsByTagName('input');

        for(var j = 0; j < inputs.length; j++) { //loop of length = 4
            var thisInput = inputs[j];
            // do whatever

        };

    };

Solution 8 - Javascript

You could use Object.values + for...of loop:

const listA = document.getElementById('A');
const listB = document.getElementById('B');
const listC = document.getElementById('C');
const btn = document.getElementById('btn');

btn.addEventListener('click', e => {
  // Loop & manipulate live nodeLList
  for (const li of Object.values(listA.getElementsByClassName('li'))) {
    if (li.classList.contains('active')) {
      listB.append(li);
    } else {
      listC.append(li);
    }
  }
});

ul {
  display: inline-flex;
  flex-direction: column;
  border: 1px solid;
}

ul::before {
  content: attr(id);
}

.active {
  color: red;
}

.active::after {
  content: " (active)";
}

<ul id="A">
  <li class="li active">1. Item</li>
  <li class="li">2. Item</li>
  <li class="li">3. Item</li>
  <li class="li active">4. Item</li>
  <li class="li active">5. Item</li>
  <li class="li">6. Item</li>
  <li class="li active">7. Item</li>
  <li class="li">8. Item</li>
</ul>

<button id="btn">Distribute A</button>

<ul id="B"></ul>
<ul id="C"></ul>

One-liner:
Object.values(listA.getElementsByClassName('li')).forEach(li => (li.classList.contains('active') ? listB : listC).append(li))

Solution 9 - Javascript

Update 2022

Fastest and shortest solution

[...document.getElementByClassName('className')].forEach(el => {
    //Do something
})

Why does it work?

Iterating live HTML collection is extremely inefficient

As mentioned in styks' answer above, [...htmlCollection] converts the the class collection into an array. It is necessary to convert it to an array, since iterating a live HTMLCollection directly would be extremely inefficient. As teemu wrote above "Never iterate live HTMLCollection!":

> Live collections are problematic because there's no way to keep the collection updated under the hood. To achieve a reliable collection, the DOM is traversed every time a collection is accessed, in every current implementation of HTMLCollection. In practice this means, that every time you access a member of a live collection (including the length), the browser traverses the entire document to find the specific element.

Why is it the fastest and most efficient?

Note that using [...arr] is the fastest and most efficient way to convert htmlCollection to an array. A performance comparison of all methods made by harpo can be found here: http://jsben.ch/h2IFA

(See all details about htmlCollection conversion to an array here)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKuptoView Question on Stackoverflow
Solution 1 - JavascriptAlbert XingView Answer on Stackoverflow
Solution 2 - JavascriptstyksView Answer on Stackoverflow
Solution 3 - JavascriptTeemuView Answer on Stackoverflow
Solution 4 - JavascriptAndrewView Answer on Stackoverflow
Solution 5 - JavascriptayjayView Answer on Stackoverflow
Solution 6 - JavascriptKushal DesaiView Answer on Stackoverflow
Solution 7 - JavascriptJ0ANMMView Answer on Stackoverflow
Solution 8 - JavascriptExodus 4DView Answer on Stackoverflow
Solution 9 - Javascriptlior bakaloView Answer on Stackoverflow