using loop to change class of DOM elements - javascript

I am trying to create a loop that will change the class of every item in a list, but it seems that each time the loop access the list, it get's shorter after each itteration
for example:
html code:
for(let i = 0; i<document.getElementsByClassName('list').length;i++){
document.getElementsByClassName('list')[i].className="student";
}
<ul class="list">
<li>John</li>
<li>Pete</li>
</ul>
<ul class="list">
<li>David</li>
<li>Sarah</li>
<li>Dan</li>
</ul>
Can someone please explain why is it happening?
Thanks!

getElementsByClassName returns a live html collection. So when you alter index [0] it is dropped from the collection. So the item that was in [1] is not at [0].
You either loop backwards, you use a while loop, or use modern way with querySelectorAll

I think #epascarello answer explains, this is the additional part with querySelectorAll
getElementsByClassName returns a live html collection. So when you
alter index [0] it is dropped from the collection. So the item that
was in 1 is not at [0].
You either loop backwards, you use a while loop, or use modern way
with querySelectorAll
you can use querySelectorAll
let c = document.querySelectorAll("ul");
let i;
for (i in c) {
c[i].className = 'student';
}
.student{
color: red;
}
<ul class="list">
<li>John</li>
<li>Pete</li>
</ul>
<ul class="list">
<li>David</li>
<li>Sarah</li>
<li>Dan</li>
</ul>

The problem is that you are using the parent element, you should get all li and not anul.
Try this:
const listItems = document.querySelectorAll('.list li')
for(let i = 0; i < listItems.length; i++) {
listItems[i].classList.add("student");
}

You can use higher order function:
[...document.getElementsByClassName("list")].forEach((element) => {
element.className = "student";
});

two things can be improved in your code; first using storing result of document.getElementsByClassName in a variable and since reading DOM is expensive, and second thing is to use querySelectorAll instead of getElementsByClassName; here is your code re-written:
let elements = document.querySelectorAll('.list');
setTimeout(()=>{
for(let i = 0; i<elements.length;i++){
elements[i].className="student";
}
}, 2000)
.list{color: red}
.student{color: blue}
<ul class="list">
<li>John</li>
<li>Pete</li>
</ul>
<ul class="list">
<li>David</li>
<li>Sarah</li>
<li>Dan</li>
</ul>

Related

Can I use a For loop with a Nodelist?

Basically I have some HTML code that is a tree, I was traverse the Nodelist for it and and assign certain classes to nodes if they have children, here's a snippet:
<li id='test' class="parentNode">
<button class="customer-btn button"><a href='#'>Customer 6</a></button>
<ul>
<li>
<a href='#'>Customer A</a>
</li>
</ul>
</li>
<li class="parentNode">
<button class="customer-btn button"><a href='#'>Customer 7</a></button>
<ul>
<li>
<a href='#'> Customer A</a>
</li>
</ul>
</li>
This is my Javascript:
parent_list = document.getElementsByTagName("LI");
var i;
$(document).ready(function() {
for (i=0; i < parent_list.length; i++){
children = $(i).find('LI');
document.getElementById('check').innerHTML = children;
}
});
The for loop I have return [object Object], what's the best what to do this?
You don't need jQuery.
window.addEventListener('DOMContentLoaded', run );
function run() {
var allLIElements = document.getElementsByTagName('LI');
for( var i = 0; i < allLIElements.length; i++ ) {
var li = allLIElements[i];
if( li.firstElementChild != null ) {
li.classList.add('hasChildren');
}
}
}
Note that this will soon be unnecessary as CSS has the proposed :has() pseudo-class which you can use to select elements that meet some criteria.
https://developer.mozilla.org/en-US/docs/Web/CSS/:has
The :has() CSS pseudo-class represents an element if any of the selectors, relative to the:scope of the given element, passed as parameters, matches at least one element. The :has() pseudo-class takes a selector list as an argument.
Consider this style rule instead, it will match any li element that contains another element. No JavaScript required.
li:has(> *) { /* As of early 2017 no browser supports this selector yet! */
}
Since you're using jQuery you don't need a for loop
$('li').each(function(index, element){
if($(element).children().length > 0){
$(element).addClass('myClass')
}
})
You can use a for loop for anything, but your code isn't correct...assigning children to innerHTML isn't a compatible assignment, and doing it in a loop will just result in the element 'check' being assigned multiple times, not with an addition.
If you are using jQuery then use:
$('#check').append(children);

target a <li> so that you can click

I am currently developing a program. It includes a 3 option navigation bar. It uses <li> and does not have id's, when i try to add id's to them it messes up the order, and doesent even work with a click! Im starting to loose faith with it.. can anyone help me on this one,
my GOAL is to have it alert different things on different clicks, so than I could link different html pages,
fiddle used HERE.
<ul class="ui-module menu-selector" id="menu-selector">
<li>Home</li>
<li class="js-is-active">Notif's</li>
<li>Profile</li>
</ul>
Since you don't have ids, I suppose that childNodes property will help a lot.
For example, you can use:
var lis = document.getElementById('menu-selector').childNodes;
// or you can select lis directly...
// var lis = document.querySelectorAll('#menu-selector li');
Array.prototype.slice.call(lis)
.forEach(function(li) {
// do something... like
li.onclick = function () {
console.log(this);
}
});
Note: childNodes (or querySelectorAll return) is NodeList type, and I use Array.prototype.slice.call() in order to use forEach() method on it.
See childNodes for more details.
if you don't want to have ids on your li elements for some reason you can use the following logic to select active li:
$("#menu-selector li.active").on("click", function(){
alert($(this).text())
});
I added id's for you, not sure what you meant by it messing up the order.
HTML
<div class="ui-items">
<header class="ui-module app-header">VoiceBox <i class="entypo-user-add"></i>
<i class="entypo-pencil"></i>
</header>
<div id="outer">
<ul class="ui-module menu-selector" id="menu-selector">
<li id="home_li">Home</li>
<li id="notif_li" class="js-is-active">Notif's</li>
<li id="profile_li">Profile</li>
</ul>
</div>
</div>
Javascript
var listItem = $('#menu-selector > li');
$(listItem).click(function() {
$(listItem).removeClass('js-is-active');
$(this).toggleClass('js-is-active');
});
$('#home_li').click(function(){
alert('home clicked')
})
$('#notif_li').click(function(){
alert('notifs clicked')
})
$('#profile_li').click(function(){
alert('profile clicked')
})
Fiddle http://jsfiddle.net/1swep9oq/2/

get text of the first li element from every ul

I have a lot of <ul> list and try to get from every first li of this list the text.
the markup is simple like:
<ul>
<li>abc</li>
<li>def</li>
<li>ghi</li>
</ul>
and so on.
my jQuery attempt is:
var elems = $('ul'); // returns a nodeList
var arr = jQuery.makeArray(elems);
arr.reverse(); // use an Array method on list of dom elements
for( var i=0; i < elems.length; i++) {
console.log($(this).find('li:lt(1)').text());
}
But I have a mistake in the for loop with $(this). I don't know how to get the first text of ul number 1 or 3 if i don't use $(this).
So how can point it correctly in the for loop?
.each will give you this.
$('ul').each(function() {
console.log($(this).find('li').eq(0).text());
})
Alternative sytax using :first instead of :eq(0)
$('ul').each(function() {
console.log($(this).find('li:first').text());
});
or, to forgo the find() function.
$('ul').each(function() {
console.log( $('li:first', this).text() );
});
you can also use:
$("li:nth-child(1)").each(function()
{
console.log($(this).text());
});
notes:
with :nth-child(n), all children are counted, regardless of what they are.
with :nth-child(n), n is 1-based (the first index is 1 instead of 0)

JavaScript: can't access a childNode of one iteration of a nodeList

Given this markup:
<ul id="navtabs">
<li><a onClick="someFunction();" href="#">Link</a></li>
<li><a onClick="someFunction();" href="#">Link</a></li>
</ul>
and this function:
function someFunction(){
navList=document.getElementById('navtabs').childNodes;
for(i=0;i<navList.length;i++){
navList[i].childNodes[0].className='fgf';
}
}
my expectaction is that the class of each anchor is changed, however, when the function runs I get:
Error: navList[i].childNodes[0] is undefined
When I use:
navList[i].className='fgf';
in lieu of the above code, the class name of the list item is changed as expected.
How can i access the childNode of the childNode through the loop as seen in the above function?
Thank you.
childNodes is likely giving you text nodes as well as dom elements, which is why you're getting that error. In your loop, navList[i] is at times a text node with no children of its own, which is of course why navList[i].childNodes[0] is undefined
Instead of using childNodes you could do
navList=document.getElementById('navtabs').children;
to just get the child elements. But I would recommend being even more specific with getElementsByTagName
function someFunction() {
navList = document.getElementById('navtabs').getElementsByTagName("li");
for(i = 0; i < navList.length; i++){
navList[i].getElementsByTagName("a")[0].className='fgf';
}
}
You are looking for <a> element children-
function someFunction(){
var navList=document.getElementById('navtabs').getElementsByTagName('a');
for(i=0;i<navList.length;i++){
navList[i]className='fgf';
}
}

Get all LI elements in array

How can i make JS select every LI element inside a UL tag and put them into an array?
<div id="navbar">
<ul>
<li id="navbar-One">One</li>
<li id="navbar-Two">Two</li>
<li id="navbar-Three">Three</li>
<li id="navbar-Four">Four</li>
<li id="navbar-Five">Five</li>
</ul>
</div>
Can i make it so JS gets each of them into an array eg
navbar['0'] would return document.getElementById("navbar-One")?
You can get a NodeList to iterate through by using getElementsByTagName(), like this:
var lis = document.getElementById("navbar").getElementsByTagName("li");
You can test it out here. This is a NodeList not an array, but it does have a .length and you can iterate over it like an array.
After some years have passed, you can do that now with ES6 Array.from (or spread syntax):
const navbar = Array.from(document.querySelectorAll('#navbar>ul>li'));
console.log('Get first: ', navbar[0].textContent);
// If you need to iterate once over all these nodes, you can use the callback function:
console.log('Iterate with Array.from callback argument:');
Array.from(document.querySelectorAll('#navbar>ul>li'),li => console.log(li.textContent))
// ... or a for...of loop:
console.log('Iterate with for...of:');
for (const li of document.querySelectorAll('#navbar>ul>li')) {
console.log(li.textContent);
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
<div id="navbar">
<ul>
<li id="navbar-One">One</li>
<li id="navbar-Two">Two</li>
<li id="navbar-Three">Three</li>
</ul>
</div>
QuerySelectorAll will get all the matching elements with defined selector. Here on the example I've used element's name(li tag) to get all of the li present inside the div with navbar element.
let navbar = document
.getElementById("navbar")
.querySelectorAll('li');
navbar.forEach((item, index) => {
console.log({ index, item })
});
<div id="navbar">
<ul>
<li id="navbar-One">One</li>
<li id="navbar-Two">Two</li>
<li id="navbar-Three">Three</li>
<li id="navbar-Four">Four</li>
<li id="navbar-Five">Five</li>
</ul>
</div>
If you want all the li tags in an array even when they are in different ul tags then you can simply do
var lis = document.getElementByTagName('li');
and if you want to get particular div tag li's then:
var lis = document.getElementById('divID').getElementByTagName('li');
else if you want to search a ul first and then its li tags then you can do:
var uls = document.getElementsByTagName('ul');
for(var i=0;i<uls.length;i++){
var lis=uls[i].getElementsByTagName('li');
for(var j=0;j<lis.length;j++){
console.log(lis[j].innerHTML);
}
}
var allElmnts = document.querySelectorAll("ul");
var arr = [];
arr.length = allElmnts.length;
for(var i = 0; i < allElmnts.length; i++){
arr[i] = allElmnts[i];
}

Categories