Add class using getElementsByClassName with in HTML [duplicate] - javascript

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.

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.

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 Array

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.

You could always use array methods:
var slides = getElementsByClassName("slide");
Array.prototype.forEach.call(slides, function(slide, index) {
Distribute(slides.item(index));
});

Update 2022
Fastest and shortest solution
[...document.getElementsByClassName('className')].forEach(el => {
// Do something with each element
})
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)

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);
}

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))

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

<!--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>

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
};
};

Related

Is there any way to use forEach through a live element collection in JS? [duplicate]

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.
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.
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 Array
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.
You could always use array methods:
var slides = getElementsByClassName("slide");
Array.prototype.forEach.call(slides, function(slide, index) {
Distribute(slides.item(index));
});
Update 2022
Fastest and shortest solution
[...document.getElementsByClassName('className')].forEach(el => {
// Do something with each element
})
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)
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);
}
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))
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
<!--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>
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
};
};

How to sum elements dynamically added with Javascript?

I am adding table rows dynamically to a table using Javascript.
Is there a reason why items.length doesn't increase when I add a row?
I am also trying to sum the numbers contained in each row. But this doesn't work either. The dynamically added rows are completely ignored for some reason.
I am coming from jQuery where things like these used to work.
I am probably missing something really fundamental here. Thanks for any pointers.
document.addEventListener("DOMContentLoaded", function() {
var form = document.querySelector("#form");
var items = form.querySelectorAll(".item");
form.addEventListener("click", function(event) {
if (event.target.className == ".add_item") {
addFields(event);
}
});
function addFields(event) {
var item = document.createElement("template");
item.innerHTML = fields.trim();
items[items.length - 1].insertAdjacentElement("afterend", item.content.firstChild);
console.log(items.length);
event.preventDefault();
}
})
querySelector and querySelectorAll returns NodeList where as getElementsByClassName returns HTMLCollection.
The difference is, NodeList is a static copy but HTMLCollection is a live copy. So if element is modified, like in your case, a new row is added, HTMLCollection will work but NodeList will not.
So changing
var items = form.querySelectorAll(".item")
to
var items = form.getElementsByClassName("item")
might solve the problem.
Pointers
You cannot have a selector in getElementsByClassName. It expects a className, you cannot use composite selector like #form .item.
Reference:
Difference between HTMLCollection, NodeLists, and arrays of objects
You only query the items once here:
var items = form.querySelectorAll(".item");
form.addEventListener(
//you call addItem here
)
function addItem(){
//you access items.length here
}
You need to keep re-query-ing the selectors every time you add an item.
var items = form.querySelectorAll(".item"); //i got queried for the 1st time
function addFields(event) {
items = form.querySelectorAll(".item"); //get items again
//...
}
Or just don't query outside addFields() all in all. Put var items = ... in the function. There's no need to put it outside.
Read up #Rajesh's answer why this is so.

What is the idiomatic way to iterate over a NodeList and move its elements without converting to an Array?

This jsFiddle illustrates the problem. If I'm understanding what's going on correctly, as I iterate over and modify the NodeList in place, the counter variable i misses every other node.
In that fiddle, I have two lists, #one and #two and I'd like to move all the children of #one to #two--
<ol id='one'>
<li class='move'>one</li>
<li class='move'>two</li>
<li class='move'>three</li>
<li class='move'>four</li>
<li class='move'>four</li>
</ol>
<ol id='two'>
</ol>
with some minimal JavaScript
var lisToMove = document.getElementsByClassName('move');
var destination = document.getElementById('two');
for (var i = 0; i < lisToMove.length; i++) {
destination.insertBefore(lisToMove[i], null);
}
I know I can fix this by simply converting the NodeList to an Array and iterating over the Array instead, but I was wondering what the right way to iterate over a NodeList if you are modifying the NodeList itself (and not just the nodes) is?
Node lists are often implemented as node iterators with a filter. This means that getting a property like length is O(n), and iterating over the list by re-checking the length will be O(n^2).
var paragraphs = document.getElementsByTagName('p');
for (var i = 0; i < paragraphs.length; i++) {
doSomething(paragraphs[i]);
}
It is better to do this instead:
var paragraphs = document.getElementsByTagName('p');
for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) {
doSomething(paragraph);
}
This works well for all collections and arrays as long as the array does not contain things that are treated as boolean false.
In cases where you are iterating over the childNodes you can also use the firstChild and nextSibling properties.
var parentNode = document.getElementById('foo');
for (var child = parentNode.firstChild; child; child = child.nextSibling) {
doSomething(child);
}
I'm not sure there's a "right" way. Whatever is easiest for you to implement, is easy to understand, and works, should be what you use. A simple slice of the NodeList shouldn't be a problem, even if there's the tiniest performance hit for simply creating a new array and copying references to its contents.
If you truly don't want to copy an array, you can loop over the NodeList backwards, guaranteeing that any changes you make to that node in the list won't actually affect the list.
Here's an example of what I mean:
var lisToMove = document.getElementsByClassName('move');
var destination = document.getElementById('two');
var i = lisToMove.length;
while (i--) {
destination.insertBefore(lisToMove[i], destination.firstChild);
}
DEMO: http://jsfiddle.net/TYZPD/
As you can see, it's not a straightforward change in looping - the logic of where/how to insert the node has to change too, since everything is backwards. That's why I changed the second argument to insertBefore to be destination.firstChild. I'm sure there's other ways to handle the logic, but the principle is the same.
References:
Node.firstChild - https://developer.mozilla.org/en-US/docs/Web/API/Node.firstChild
Node.insertBefore - https://developer.mozilla.org/en-US/docs/Web/API/Node.insertBefore
Using an array is the correct way to do things like this. Just like if you ever have to modify an array while you loop over it, you probably want to separate the modification part and the loop part so one can't affect the other.
In this case, doing that is accomplished by converting the NodeList to an array.
As suggested by Benjamin Gruenbaum, you could also use querySelectorAll, which returns a frozen NodeList.
Demo here

Javascript - sorting a collection of divs

I am trying to learn Javascript alone so please don't suggest a library or jQuery.
I have a list of divs and I want the user to be able to sort them by their value. For example:
<button onclick="sort();">Test</button>
<div class="num">2</div>
<div class="num">3</div>
<div class="num">8</div>
<div class="num">1</div>
JS:
function sort(){
var elements = document.getElementsByClassName("num");
elements.sort();
}
I cannot find a straight answer to what's wrong with this. Does getElementsByClassName return an array of the values of each div with that name? How, when the array is then sorted, so I reflect the changes in the divs?
You can't use the sort() function on a NodeList, which is what you are actually getting by calling getElementsByClassName or querySelectorAll.
So you'll have to convert it to an array before using Array.sort():
// Get elements and convert to array
const elems = [...document.querySelectorAll(".num")];
// Sort elements in-place
elems.sort((a, b) => Number(a.innerText) - Number(b.innerText));
// Join the array back into HTML
const outputHtml = elems.reduce((a, el) => a + el.outerHTML, "");
// Append HTML to parent container
document.getElementById('myDiv').innerHTML = outputHtml;
http://jsfiddle.net/g918jmoL/
Sorting nodes is not that simple, you may need to deal with other nodes that might get in the way. Anyhow, here's a function that accepts a NodeList or HTMLCollection, converts it to an array, sorts it, then puts the nodes back in order. Note that the elements are put back at the bottom of the parent element, so any other elements will end up at the top. Anyhow, it demonstrates an approach that you can adapt.
There are many other approaches, including others using DOM methods, beware of any that are based on munging the markup. Also, beware of cloning elements as this may have unwelcome side effects on listeners (they may or may not be removed, depending on the browser and how they've been added).
<script type="text/javascript">
function getText(el) {
return el.textContent || el.innerText || '';
}
function sortElements(nodeList) {
// Assume there's a common parent
var node, parentNode = nodeList[0].parentNode
// Define a sort function
function sortEls(a, b) {
var aText = getText(a);
var bText = getText(b);
return aText == bText? 0 : aText < bText? -1 : 1;
}
// Convert nodelist to an array and remove from the DOM at the same time
var a = [], i = nodeList.length;
while (i--) {
a[i] = parentNode.removeChild(nodeList[i]);
}
// Sort the array
a.sort(sortEls);
// Put elements back in order
i = 0;
while (node = a[i++]) {
parentNode.appendChild(node);
}
}
</script>
<div>0</div>
<div>4</div>
<div>2</div>
<div>3</div>
<div>5</div>
<button onclick="sortElements(document.getElementsByTagName('div'))">Sort</button>
The problem is that getElementsByClassName return a nodeList, not an array. Ok, I have to say, that is very stupid and I still can't get my head around why browser implemented it this way...
What you can do though, is first convert the nodeList to an array and than do the sorting:
var elems = Array.prototype.slice.call(elements);
elems.sort(/* function (a, b) {} */);
Note that the sorting function is optionnal and is passed normally after the first value. (Although, called directly on your elements list, .sort() won't sort anything without a function parameter as it won't know which one will come before the other)
Checkout MDN call description to understand how this really works behind the scene: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call

How to correctly iterate through getElementsByClassName

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.
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.
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 Array
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.
You could always use array methods:
var slides = getElementsByClassName("slide");
Array.prototype.forEach.call(slides, function(slide, index) {
Distribute(slides.item(index));
});
Update 2022
Fastest and shortest solution
[...document.getElementsByClassName('className')].forEach(el => {
// Do something with each element
})
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)
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);
}
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))
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
<!--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>
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
};
};

Categories

Resources