Can I use a For loop with a Nodelist? - javascript

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

Related

using loop to change class of DOM elements

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>

one function to fire on same class elements click

I'm trying to make controls for category list with sub-category and sub-sub-category lists.
Here's HTML:
<ul class="selectbox-ul">
<li>
<div>Category</div>
<ul class="selectbox-ul-child">
<li>
<div>Subcategory</div>
<ul class="selectbox-ul-child">
<li>
<div>Sub-subcategory</div>
</li>
</ul>
<span id="trigger">icon</span>
</li>
</ul>
<span id="trigger">icon</span>
</li>
....
</ul>
So my shot was to add class for ul.selectbox-ul-child :
var trigger = document.getElementById("trigger");
function subCatStatus() {
if(this.parentElement.children[1].className != "... expanded") {
this.parentElement.children[1].className += " expanded"
} else {
this.parentElement.children[1].className == "..."
};
};
trigger.addEventListener("click", subCatStatus);
And it works only for first span#trigger(that shows subcategories), next one (for sub-subcategories) does nothing (I've also tried to use .getElementsByClassName it didn't work for any of triggers) . So i'd like to get some explanation why doesn't this one work. And some advice how to make it work.
As others have already mentioned, you can't stack multiple elements with the same ID since document.getElementById() is not supposed to return more than one value.
You may try instead to assign the "trigger" class to each of those spans instead of IDs and then try the following code
var triggers = document.getElementsByClassName("trigger");
function subCatStatus() {
if(this.parentElement.children[1].className != "... expanded") {
this.parentElement.children[1].className += " expanded"
} else {
this.parentElement.children[1].className == "..."
};
};
for(var i = 0; i < triggers.length; i++) {
triggers[i].addEventListener("click", subCatStatus);
}
javascript getElementById returns only single element so it will only work with your first found element with the ID.
getElementsByClassName returns an array of found elements with the same class, so when adding listener to the event on element you would require to loop through this array and add individually to it.

Select only one of two selectors

Given a containing jQuery object $c that has multiple elements in it that contain elements with classnames tmpSelected and selected, I would like to select only one element from $c for each classname, preferably tmpSelected. The markup is a complex version of this:
<ul id="a">
<li class="selected">Foo</li>
<li>Bar</li>
<li>Baz</li>
<li>Biz</li>
</ul>
<ul id="b">
<li>Woo</li>
<li>War</li>
<li class="tmpSelected">Waz</li>
<li>Wiz</li>
</ul>
<ul id="c">
<li class="selected">Xuu</li>
<li class="tmpSelected">Xur</li>
<li>Xuz</li>
<li>Xyz</li>
</ul>
In this case what I want to end up with is $("#a > .selected, #b > .tmpSelected, #c > .tmpSelected") – I want to avoid the .selected element if it has a sibling of .tmpSelected, and I don't want to select more than one child element for each member of $c where $c = $("#a, #b, #c").
So this is what I came up with:
var $c = $("#a, #b, #c");
var $selected = $c.map(function (idx, el) {
var $el = $(el);
var $tmpSel = $el.children(".tmpSelected");
return $tmpSel.length ? $tmpSel : $el.children(".selected");
});
Is there a reasonable way to do this without explicit looping? (P.S. - It's fine to return an empty selector when no .tmpSelected or .selected child exists.)
Here is a selector but it is pretty messy. I believe it gives the correct solution:
$("ul > li.tmpSelected, ul:not(:has(li.tmpSelected)) > li.selected");
First off you look for and .tmpSelected elements. Then you look for any ul that only have .selected elements. The :has selector looks for the child and I use the :not selector to find ul elements. Then I simply grab the children selected elements.
jsFiddle
I would suggest this which is pretty similar in concept to what you already had except this guarantees that it only ever returns a single item for each parent.
var selected = $("#a, #b, #c").map(function() {
var item = $(this).find(".tmpSelected");
if (!item.length) {
item = $(this).find(".selected");
}
return(item.get(0));
});

A better way to detect the index in the parent's children array by using JQuery?

for example, how to detect the index of the li elem in it's parent ul?
<ul>
<li>this</li>
<li>is</li>
<li>ul</li>
</ul>
<ul>
<li>this</li>
<li>is</li>
<li>another</li>
<li>ul</li>
</ul>
if you have something like above, you could do jQuery as:
$(function(){
$('li').click(function(){ alert('the index is '+$(this).index()) })
})
this will alert the index of the li element based on its parent ul
more on index() here.
In non-IE browsers, whitespace between elements are considered text nodes (which is not the case in IE), which affects the index of subsequent elements. To give the same results in all browsers, the function below by default filters out whitespace nodes, and has an optional parameter to include them if that's what you want:
function indexOfNode(node, includeWhitespace) {
var index = 0, n = node;
while ( (n = n.previousSibling) ) {
if (includeWhitespace || n.nodeType != 3 || !/^\s*$/.test(n.data)) {
++index;
}
}
return index;
}

Javascript change class of an element

I have ul list and I need to change the class of one of <li> tags with javascript:
<ul>
<li>...</li>
<li class="something"> <- need to change this class to "myclass" (javascript goes here)</li>
<li>..</li>
</ul>
Thank you.
using jQuery (naturally):
$(function(){
$("li.something").removeClass("something").addClass("myclass");
});
As there seems to be alot of jquery answers and it's not always possible to use jquery (for example if your customer/company won't let you use it arrgh!), here is a plain javascript example.
// Where 'e' is the element in question, I'd advise using document.getElementById
// Unless this isn't possible.
// to remove
if ( e.className.match(/something/) ) {
e.className = e.className.replace("something", "")
}
// to add back in
if ( !e.className.match(/something/) ) {
e.className += " something"
}
This will work with multiple classes, for example:
<li class="something another">...</li>
Using regular javascript:
var listitems = document.getElementsByTagName("li");
for (int i = 0; i < listitems.length; i++)
{
if (listitems[i].className == "something")
{
listitems[i].className = "new class name";
break;
}
}
If your <li> tag had an id attribute, it would be easier, you could just do
document.getElementById("liID").className = "newclassname";
Using JQuery:
$('ul li:nth-child(2)').attr('class', 'myclass');

Categories