jquery next siblings - javascript

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>

Related

Traversing unordered list using Javascript

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>

Nested function calls from the second click

I have multiple <li> elements. When I click to one of this, data from the chosen <li> should populate fields of form below. But my problem is when I click on the list of <li> tags it works from the second click.
<div class='js-delivery-addresses' id='delivery_addresses'>
<ul>
<li> one </li>
<li> two </li>
<li> three </li>
</ul>
</div>
Js code:
events: {
"click .js-delivery-addresses": "chosenAddress"
}
chosenAddress: function() {
...some code here;
$('#delivery_addresses li').unbind().on('click', function() {
...other code that works from second click;
})
}
I expect it will work from the first click.
Is this not much simpler ?
$(document).on("click","#delivery_addresses ul li",function(){
alert($(this).val() + " = " + $(this).text());
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='js-delivery-addresses' id='delivery_addresses'>
<ul>
<li> one </li>
<li> two </li>
<li> three </li>
</ul>
</div>

Adjusting html tags and css styles via JavaScript

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>

How to get specific element javascript based on style

I need to acces an element that has a certain style.
This is my structure
<ul>
<li> Hi </li>
<li> bye </li>
<li> third one </li>
</ul>
The list items are placed on top of each other (last one first) and I can dislike something or like something. Once I do that, it gets a style display:none like following:
<ul>
<li> Hi </li>
<li> bye </li>
<li style:"display:none;"> third one </li>
</ul>
Now after I did that I want to be able to acces the last element that does not have display:none, (the bye) how can I do this?
I was thinking of something in the form of:
var myId = $("#slider > ul li").last().attr("id");
But obviously I always get the ID of the item that is hidden since its still there.
Can I do something like select last where !display:hidden ?
Can I do something like select last where !display:hidden ?
Yes, with jQuery's :visible pseudo-class:
var myId = $("#slider > ul li:visible").last().attr("id");
(Note: Your li elements don't actually have id values, but that's a tweak.)
Live Example:
var listItem = $("#slider > ul li:visible").last();
$("<p>")
.text("Text of last visible item: " + listItem.text())
.appendTo(document.body);
<div id="slider">
<ul>
<li>Hi</li>
<li>bye</li>
<li style="display:none;">third one</li>
</ul>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Can use ':visible' selector
var myId = $("#slider > ul li:visible").last().attr("id");
It should work using:
$("#slider > ul li:visible").last().attr("id");
https://api.jquery.com/visible-selector/
so your inline styling is a bit off it should be
<ul>
<li> Hi </li>
<li> bye </li>
<li style="display:none;"> third one </li>
</ul>
You could do a few different things, best is probably just iterate through and check for where display = none, then go to the previous element:
$('ul').children().each(function(e) {
if($(this)[0].style.display == 'none') {
console.log($(this).prev());
}
})

Javascript get X parent node without jQuery

I'm manually scraping data from some websites just using "Javascript:;" in a browser's
address bar. It's easier than copy/pasting.
I've come across a few instances where I have to do: object.parentNode.parentNode....
to get some information and as it varies from site to site it could be at any level.
Obviously I don't want a loop and traverse it as that would make a simple task a bit more
extensive.
Is there a way to do say: object.parentNode[4] or something such as without jQuery?
I don't think you'll manage to avoid a good ol' loop:
for(var i=0; i<4 && node.parentNode; node=node.parentNode, i++); alert(node);
I wrote a function to do exactly this in Vanilla JavaScript:
https://github.com/ryanpcmcquen/queryparent
Here's the es5 version:
https://github.com/ryanpcmcquen/queryparent/blob/master/index-es5.js
Or if you only need to support modern browsers, you can use the es6 version:
https://github.com/ryanpcmcquen/queryparent/blob/master/index.js
You call it like so:
queryParent(SELECTOR, PARENT);
It will return the raw JavaScript node.
Here's a demo of it getting the exact class selector you would want, even though there is another element with the same selector (that you would not want):
https://jsfiddle.net/ryanpcmcquen/zkw0gdj7/
/*! queryParent.js v1.2.1 by ryanpcmcquen */
/*global module*/
/*jshint esversion:6*/
const d = document;
const qu = 'querySelector';
const queryParent = (s, p) => {
const q = (x) => d[qu](x);
const qa = (y) => d[`${qu}All`](y);
const pa = qa(p);
(typeof s === 'string') && (s = q(s));
return [...pa].filter((n) => {
return (n.contains(s)) ? n : false;
}).pop();
};
//module.exports = queryParent;
console.log(
queryParent('.bar', '.foo')
);
// PSST! Check the console!
<div>
<ul class="foo">
But there is also a <code>foo</code> we don't want here.
</ul>
<ul class="foo">
The <code>foo</code> we want is here.
<li>
<ul>
<li>
<ul></ul>
</li>
<li>
<ul></ul>
</li>
<li>
<ul></ul>
</li>
</ul>
</li>
<li>
<ul>
<li>
<ul></ul>
</li>
<li>
<ul></ul>
</li>
<li>
<ul></ul>
</li>
</ul>
</li>
<li>
<ul>
<li>
<ul></ul>
</li>
<li>
<ul></ul>
</li>
<li>
<ul></ul>
</li>
</ul>
</li>
<li>
<ul>
<li>
<ul></ul>
</li>
<li>
<ul></ul>
</li>
<li>
<ul></ul>
</li>
</ul>
</li>
<li>
<ul>
<li>
<ul></ul>
</li>
<li>
<ul></ul>
</li>
<li>
<ul class="bar"><code>bar</code> is here.</ul>
</li>
</ul>
</li>
</ul>
</div>
The reason I find this more useful, is that it does not rely on a certain number of .parent() calls (or conversely .parentNode in pure JS). For that reason it makes your code more future proof and less likely to stop working should the markup structure change.

Categories