I have an unordered nested list
I want to count these nested lists in such a way that inside <li>Animals</li> there are 19 animals inside this li. I wanted to count all li having the name of animals using Javascript. How should I proceed?
<ul>
<li>
Animals
<ul>
<li>
Mammals
<ul>
<li>Apes
<ul>
<li>Chimpanzee</li>
<li>Gorilla</li>
<li>Orangutan</li>
</ul>
</li>
<li>Coyotes</li>
<li>Dogs</li>
<li>Elephants</li>
<li>Horses</li>
<li>Whales</li>
</ul>
</li>
<li>
Other
<ul>
<li>
Birds
<ul>
<li>Albatross</li>
<li>Emu</li>
<li>Ostrich</li>
</ul>
</li>
<li>Lizards</li>
<li>Snakes</li>
</ul>
</li>
</ul>
</li>
<li>Fish
<ul>
<li>Goldfish</li>
<li>Salmon</li>
<li>Trout</li>
</ul>
</li>
</ul>
#mplungjan put forward this solution in the comments and it works. document.querySelectorAll("ul")[1].querySelectorAll("li").length
this gives 17 which is the correct count (after clarification in comments which overrode the first count given which was 19).
The question asks about traversing and we show that step by step here so as to help with more general solutions.
We first have to find the element which is holding all the other lists. I have assumed that that ul element is the first in the document. In the general case we'd probably find such an element by knowing it's id or its class.
We then find the li element which is associated with Animals and again we assume it's the first one, as it is in the HTML given. However, if it was to be more sophisticated you'd want to look for the li element that has Animals as its innerHTML.
We then find the associated lis and count them.
Here is the code and you can run the snippet to see it gets to 17 (which was clarified in comments, the original count in the question was given as 19, also clarified was that this has nothing to do with counting actual animal names, only to do with counting li elements).
<!DOCTYPE html>
<html>
<body>
<ul>
<li>
Animals
<ul>
<li>
Mammals
<ul>
<li>Apes
<ul>
<li>Chimpanzee</li>
<li>Gorilla</li>
<li>Orangutan</li>
</ul>
</li>
<li>Coyotes</li>
<li>Dogs</li>
<li>Elephants</li>
<li>Horses</li>
<li>Whales</li>
</ul>
</li>
<li>
Other
<ul>
<li>
Birds
<ul>
<li>Albatross</li>
<li>Emu</li>
<li>Ostrich</li>
</ul>
</li>
<li>Lizards</li>
<li>Snakes</li>
</ul>
</li>
</ul>
</li>
<li>Fish
<ul>
<li>Goldfish</li>
<li>Salmon</li>
<li>Trout</li>
</ul>
</li>
</ul>
<script>
var firstUL = document.getElementsByTagName('UL')[0]; //assuming the whole list is the first UL in the document
var animalsLI = firstUL.getElementsByTagName('LI')[0];
var animalsLIUL = animalsLI.getElementsByTagName('UL')[0];
var animalsLIs = animalsLI.getElementsByTagName('LI');
alert('The count of LIs is ' + animalsLIs.length);
</script>
</body>
</html>
Related
I'm struggling to understand how to get the last item in an each() loop. I asked a question last week on this topic, which can be seen here: .find() relationship with loops, each() and this keyword - .find() returning 4 when it should only be 2
The original requirement was to check a series of uls inside uls, and if there were more than 1 lists I need to add a class. Now - I need to build upon this code where if there are more than three lists inside a div, or it is the last ul in a series, I need to add a class to the last ul as well.
I did research on the topic and will be referencing this answer: Last element in .each() set
For reference, the first sample case is below:
$(function(e) {
var getMenuItems = $(".item");
getMenuItems.each(function( index ) {
if ($(this).find("ul.sub-menu").length > 0) {
$(this).addClass("red");
}
});
});
.red {background-color: red;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul>
<li class="item">
<ul class="sub-menu">
<li></li>
</ul>
</li>
<li class="item">
</li>
<li class="item">
<ul class="sub-menu">
<li></li>
</ul>
</li>
<li class="item">
</li>
</ul>
This code just checks if there are more than 1 lists inside a div and if there are add a class.
Now the next step is to not only add a class to the divs with more than 1 list but the last ul in the series irregardless of amount of lists. The answer Last element in .each() set suggests to simply cross reference index and see if you are at the last item.
The highest upvoted answer says to:
Check index against the length of the set and you're good to go:
That concept integrated into my sample looks like this:
$(function(e) {
var getMenuItems = $(".item");
var howManyListItems = getMenuItems.length;
getMenuItems.each(function( index ) {
if ($(this).find("ul.sub-menu").length > 0) {
$(this).addClass("red");
} else if (index == (howManyListItems.length - 1)) {
console.log($(this));
$(this).addClass("red");
}
});
});
.red {background-color: red;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul>
<li class="item">
<ul class="sub-menu">
<li></li>
</ul>
</li>
<li class="item">
</li>
<li class="item">
<ul class="sub-menu">
<li></li>
</ul>
</li>
<li class="item">
</li>
<li class="item">
</li>
<li class="item">
</li>
<li class="item">
</li>
</ul>
The expected/desired behavior is to add the red to the last item but sadly that does not happen.
As can be seen as a troubleshooting measure, console logging this into that conditional returns nothing. So is that not the last item of the array? How would you modify it/target it? What does that conditional represent? Since console logging does nothing, how does one go about troubleshooting this code?
How do you hit the last element in an each loop and modify it's DOM properties?
This is as easy as:
$(function(e) {
$(".sub-menu:last-of-type").last().addClass("red");
});
.red {background-color: red;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul>
<li class="item">
<ul class="sub-menu">
<li></li>
</ul>
</li>
<li class="item">
</li>
<li class="item">
<ul class="sub-menu">
<li></li>
</ul>
</li>
<li class="item">
</li>
<li class="item">
</li>
<li class="item">
</li>
<li class="item">
</li>
</ul>
My program generates the following html code. And I need to adjust it via client side scripting (jquery) in order to look exactly the code I have found extreme below. Please note I can't modify the code server-side. I need to remove ul and li tags while retaining the href links and tag names and add a css class. I also need to remove the given font size: 10px. I am not very expert in js so I need your help figuring out how to do that. I have tried some scripts including the following but it entirely removes all the li tags and their contents.
<script type="text/javascript">
var lis = document.querySelectorAll('.Zend_Tag_Cloud li');
for(var i=0; li=lis[i]; i++) {
li.parentNode.removeChild(li);
}
</script>
The original code generated by my program:
<ul class="Zend_Tag_Cloud">
<li>
workout definition
</li>
<li>
workout plans for men
</li>
<li>
workout program
</li>
<li>
workout routines for beginners
</li>
<li>
workout schedule
</li>
<li>
workouts at home
</li>
</ul>
The final html code should look like the following:
<a class="in-the-news-bar__link" href="/content/article/tag/136/">workout definition</a>
<a class="in-the-news-bar__link" href="/content/article/tag/140/">workout plans for men</a>
<a class="in-the-news-bar__link" href="/content/article/tag/139/">workout program</a>
<a class="in-the-news-bar__link" href="/content/article/tag/141/">workout routines for beginners</a>
<a class="in-the-news-bar__link" href="/content/article/tag/138/">workout schedule</a>
<a class="in-the-news-bar__link" href="/content/article/tag/137/">workouts at home</a>
What about this?
var $zendTagCloudLinks = $(".Zend_Tag_Cloud").find("a")
.addClass("in-the-news-bar__link")
.removeAttr("style");
$zendTagCloudLinks.insertAfter(".Zend_Tag_Cloud");
$(".Zend_Tag_Cloud").remove();
This code converts each li to a div with the class stuff. It uses jQuery. Additionally, it removes the ul entirely (I believe, per your request).
<div class="restyle">
<ul class="Zend_Tag_Cloud">
<li>
workout definition
</li>
<li>
workout plans for men
</li>
<li>
workout program
</li>
<li>
workout routines for beginners
</li>
<li>
workout schedule
</li>
<li>
workouts at home
</li>
</ul>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script>
newHtml = "";
$("div.restyle ul.Zend_Tag_Cloud li").each(function() {
$(this).find("a").attr("style","").addClass("in-the-news-bar__link");
newHtml += "<div class='stuff'>" + $(this).html() + "</div>";
});
$(".restyle").html(newHtml);
</script>
You can iterate over the list links - add the class and remove the style attribute and then prepend it to the parent div that contains the list (I called that #parentDiv) - note that removing the lis t structure will cause all the a's to display in a sequence on one line rather than on separate lines - so I also added a CSS rule to cause them to display:block.
$(document).ready(function(){
$('.Zend_Tag_Cloud li a').each(function(){
$(this).addClass('in-the-news-bar__link').removeAttr('style');
$('#parentDiv').prepend($(this).parent().html());
})
$('.Zend_Tag_Cloud').remove();
})
a{display:block}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="parentDiv">
<ul class="Zend_Tag_Cloud">
<li>
workout definition
</li>
<li>
workout plans for men
</li>
<li>
workout program
</li>
<li>
workout routines for beginners
</li>
<li>
workout schedule
</li>
<li>
workouts at home
</li>
</ul>
</div>
You could use something like the following. This solution is a bit more verbose, but doesn't require jQuery. Essentially, you put your list in a parent element (.Zend_Tag_Cloud--container) and the javascript finds the links and inserts them back into the container (removing original style and adding a class) overwriting the original ul. Depending on the context you are using this in, you will want to considering how to best namespace your class / ID names.
var links = [];
var container = document.querySelector('.Zend_Tag_Cloud--container');
var lis = document.querySelectorAll('.Zend_Tag_Cloud a');
for (var i=0; li=lis[i]; i++) {
links.push(li);
}
container.innerHTML = '';
links.forEach(function(link) {
link.style = null;
link.className += " in-the-news-bar__link";
container.appendChild(link);
});
<div class="Zend_Tag_Cloud--container">
<ul class="Zend_Tag_Cloud">
<li>
workout definition
</li>
<li>
workout plans for men
</li>
<li>
workout program
</li>
<li>
workout routines for beginners
</li>
<li>
workout schedule
</li>
<li>
workouts at home
</li>
</ul>
</div>
You can use $.each() to loop through the a tags, add the class, remove the style attribute, and append it to the document before the .Zend_Tag_Cloud list, then remove the list using $.remove()
$('a').each(function() {
$(this).addClass('in-the-news-bar__link').removeAttr('style').insertBefore('.Zend_Tag_Cloud');
});
$('.Zend_Tag_Cloud').remove();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="Zend_Tag_Cloud">
<li>
workout definition
</li>
<li>
workout plans for men
</li>
<li>
workout program
</li>
<li>
workout routines for beginners
</li>
<li>
workout schedule
</li>
<li>
workouts at home
</li>
</ul>
So we get a reference to the ul that we'll eventually replace with a document fragment. This document fragment contains the a we find within the ul. We will replace the ul with the fragment using jQuery's replaceWith().
The document fragment acts as a container to hold the a we find until we're ready to insert the new markup. Document fragments are fast and appending our items to one allow us to perform a single DOM insertion for all of the found a.
end() allows us to "rewind" to the previous collection as once we performed find() we were working with a collection of a and not the original collection contained in $ul.
$( function () {
var $ul = $( 'ul' ),
frag = document.createDocumentFragment();
$ul.find( 'a' )
.each( function ( i, item ) {
frag.appendChild( $( item ).addClass( 'in-the-news-bar__link' ).attr( 'style', null )[ 0 ] );
} )
.end()
.replaceWith( frag );
} );
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="Zend_Tag_Cloud">
<li>
workout definition
</li>
<li>
workout plans for men
</li>
<li>
workout program
</li>
<li>
workout routines for beginners
</li>
<li>
workout schedule
</li>
<li>
workouts at home
</li>
</ul>
I want jquery to include <li> and all other tags inside <ul class="visib-1"></ul> to <ul class="visib-2"></ul> without writing li,a href="" tags again. Is there have any solution?Is it possible to capture all elements from one UL and transfer to other UL?
From:
<ul class="visib-1">
<li>Home</li>
<li>Properties</li>
<li>Blog</li>
<li>Start Now</li>
</ul>
To:
<ul visib-1>
?
</ul>
If all you want is to move from one <ul> to another:
$('#first-ul-id li').appendTo('#second-ul-id');
If you want to copy use clone()
$('#first-ul-id li').clone().appendTo('#second-ul-id');
Here is the solution. Check the code below:
This code will also copy the event handlers attached.
var cloneOfOld = $('[data-status="old"]').clone(true);
cloneOfOld.attr('data-status','new');
var newUl = $('[data-status="new"]');
newUl.replaceWith(cloneOfOld);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul data-status="old"><li>Home</li>
<li>Properties</li>
<li>Blog</li>
<li>Start Now</li>
</ul>
<ul data-status="new">
</ul>
I'm saving markup this way:
var nav_large = nav_container.html();
The saved markup looks like this:
<ul>
<li>Toolbox</li>
<li>Starten</li>
<li>
Basis
<div class="main-navigation-cart">
<ul>
<li>CSS</li>
<li>KOMPONENTEN</li>
<li>JAVASCRIPT</li>
<li>ICONS</li>
</ul>
</div>
</li>
<li>Komponenten</li>
<li>Single Sign-On</li>
</ul>
Now I need to be able to search after ul for example. The markup from the nav_container has been removed at this stage of the script.
Hope you guys can help me out!
I've been trying to get this problem solved, but I can't seem to figure it out without some serious workarounds.
if I have the following HTML:
<ul>
<li class="parent"> headertext </li>
<li> text </li>
<li> text </li>
<li> text </li>
<li class="parent"> headertext </li>
<li> text </li>
<li> text </li>
</ul>
Now, how do I now just select the <li> tags following the first parent (or second, for that matter)? Basically selecting an <li> with class="parent" and the following siblings until it reaches another <li> with the parent class.
I could restructure the list with nested lists, but I don't want to do that. Any suggestions?
actually, you can easily do this using nextUntil().
no need to write your own "nextUntil" since it already exists.
ex. -
$(".a").nextUntil(".b");
or as suggested by Vincent -
$(".parent:first").nextUntil(".parent");
The root of your problem is that the <li>s you have classed as parent really are NOT parents of the <li>s "below" them. They are siblings. jQuery has many, many functions that work with actual parents. I'd suggest fixing your markup, really. It'd be quicker, cleaner, easier to maintain, and more semantically correct than using jQuery to cobble something together.
I don't think there is a way to do this without using each since any of the other selectors will also select the second parent and it's next siblings.
function getSibs( elem ) {
var sibs = [];
$(elem).nextAll().each( function() {
if (!$(this).hasClass('parent')) {
sibs.push(this);
}
else {
return false;
}
});
return $(sibs);
}
You will have to run the loop yourself since jQuery does not know how to stop on a specific condition.
jQuery.fn.nextUntil = function(selector)
{
var query = jQuery([]);
while( true )
{
var next = this.next();
if( next.length == 0 || next.is(selector) )
{
return query;
}
query.add(next);
}
return query;
}
// To retrieve all LIs avec a parent
$(".parent:first").nextUntil(".parent");
But you may be better using a really structured list for your parent/children relationship
<ul>
<li class="parent"> <span>headertext</span>
<ul>
<li> text </li>
<li> text </li>
<li> text </li>
</ul>
</li>
<li class="parent"> <span>headertext</span>
<ul>
<li> text </li>
<li> text </li>
</ul>
</li>
</ul>
$("li.parent ~ li");
I know this is a very old thread, but Jquery 1.4 has a method called nextUntil, which could be useful for this purpose:
http://api.jquery.com/nextUntil/
<html>
<head>
<script type="text/javascript">
$(document).ready(function() {
$("a").click(function() {
var fred = $("li").not('.parent').text();
$('#result').text(fred);
});
});
</script>
</head>
<body>
Click me
<ul>
<li class="parent"> headertextA </li>
<li> text1 </li>
<li> text2 </li>
<li> text3 </li>
<li class="parent"> headertextB </li>
<li> text4 </li>
<li> text5 </li>
</ul>
<div id="result"></div>
</body>
</html>