Reputation: 2992
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 its 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...
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.
Upvotes: 207
Views: 357513
Reputation: 5788
According to MDN, the way to retrieve an item from a NodeList
is:
nodeItem = nodeList.item(index)
Thus:
const slides = document.getElementsByClassName("slide");
for (let 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.
Upvotes: 242
Reputation: 3368
Nowdays you can just use for..of
for (const element of documents.getElementsByClassName('active')) {
console.log(element)
}
If you do not need any array method like .filter
/ .map
, i think it's the simpler option
Upvotes: 1
Reputation: 129
Simple and easy with Query Selector
const elements = document.querySelectorAll('.fixed-class-name');
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
// Do something with each element
}
Upvotes: 0
Reputation: 924
Here is the example I've done like this to assign random number to each class:
var a = $(".className").length;
for(var i = 0; i < a; i++){
var val = $(".className")[i];
var rand_ = (Math.random()*100).toFixed(0);
$(val).val(rand_);
}
It's just for reference
Upvotes: 2
Reputation: 450
Update 2022
[...document.getElementsByClassName('className')].forEach(el => {
// Do something with each element
})
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.
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)
Upvotes: 12
Reputation: 23406
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.
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.
Upvotes: 31
Reputation: 2822
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>
Object.values(listA.getElementsByClassName('li')).forEach(li => (li.classList.contains('active') ? listB : listC).append(li))
Upvotes: 2
Reputation: 8535
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
};
};
Upvotes: 1
Reputation: 3441
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
});
Showing as Node List
Showing as Array
Upvotes: 166
Reputation: 25
<!--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>
Upvotes: 1
Reputation: 151
You could always use array methods:
var slides = getElementsByClassName("slide");
Array.prototype.forEach.call(slides, function(slide, index) {
Distribute(slides.item(index));
});
Upvotes: 15
Reputation: 1382
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);
}
Upvotes: 8