Javascript get X parent node without jQuery - javascript

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.

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>

Remove various div according specific text in

I have this DOM and I want to make like a filter removing li according text included in h5 tag.
<div class="total">
<ul>
<li>
<h5>this super heroe is super cool: Clark Kent</h5>
</li>
</ul>
<ul>
<li>
<h5>I always wanted to be Batman</h5>
</li>
</ul>
<ul>
<li>
<h5>Somedays I will be transform as Spiderman </h5>
</li>
</ul>
<ul>
<li>
<h5>This women is incredible Catwoman</h5>
</li>
</ul>
<li>
<ul>
<h5>The worst character is Joker</h5>
</ul>
</li>
<ul>
<li>
<h5>Someone knows about Green Lantern </h5>
</li>
</ul>
</div>
I need to make a filter according string into this array which has not coma
let appDataTab = ["Clark Kent Catwoman Joker"]
My goal is to remove all li that its h5 doesn't content "Clark Kent" "Catwoman" and "Joker"
As you can see the appDataTab content these string with no separation.
Here is the updated code after adding additional code after validating from my end.
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<div class="total">
<ul>
<li>
<h5>this super heroe is super cool: Clark Kent</h5>
</li>
</ul>
<ul>
<li>
<h5>I always wanted to be Batman</h5>
</li>
</ul>
<ul>
<li>
<h5>Somedays I will be transform as Spiderman </h5>
</li>
</ul>
<ul>
<li>
<h5>This women is incredible Catwoman</h5>
</li>
</ul>
<ul>
<li>
<h5>The worst character is Joker</h5>
</li>
</ul>
<ul>
<li>
<h5>Someone knows about Green Lantern </h5>
</li>
</ul>
</div>
<script>
$(function () {
var prohibited = ['Clark Kent', 'Catwoman', 'Joker'];
$("li").each(function (index) {
var wordFound = false;
for (var i = 0; i < prohibited.length; i++) {
if ($(this).text().indexOf(prohibited[i]) > -1) {
wordFound = true;
break;
}
}
if (wordFound == false) {
$(this).parent().remove();
}
});
})
</script>
</body>
</html>
Your should first make it a regular comma seperated array, then look for the element and remove the li containing the element.
Steps are commented one by one:
$(document).ready(function () {
//Your array
var appDataTab = ["Clark Kent Catwoman Joker"];
//Split with more than 1 space (2 space)
var appDataSplit = appDataTab[0].split(" ");
//Remove empty elements
var filtered = appDataSplit.filter(function (el) {
return el != "";
});
//Trim elements from extra spaces
var cleanFiltered = [];
for (i=0; i < filtered.length; i++){
cleanFiltered.push(filtered[i].trim());
}
console.log(cleanFiltered);
//look for any li containing the words in cleanFilter array
$("li").each(function(){
for (i=0; i < cleanFiltered.length;i++){
if ($(this).text().indexOf(cleanFiltered[i]) > -1) {
$(this).remove();
}
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="total">
<ul>
<li>
<h5>this super heroe is super cool: Clark Kent</h5>
</li>
</ul>
<ul>
<li>
<h5>I always wanted to be Batman</h5>
</li>
</ul>
<ul>
<li>
<h5>Somedays I will be transform as Spiderman </h5>
</li>
</ul>
<ul>
<li>
<h5>This women is incredible Catwoman</h5>
</li>
</ul>
<ul>
<li>
<h5>The worst character is Joker</h5>
</li>
</ul>
<ul>
<li>
<h5>Someone knows about Green Lantern </h5>
</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 can i access children using parent id

I have this structure of html. To present the list of two different sets. and i must handle the click event differently.
<div id='nodelist1'>
<ul>
<li class='nodeelem'>first node
<ul>
<li class='nodeelem'>second node
<ul>
<li class='nodeelem'>third node</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
<div id='nodelist2'>
<ul>
<li class='nodeelem'>first node
<ul>
<li class='nodeelem'>second node
<ul>
<li class='nodeelem'>third node</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
I have to access the nodes using div id
$('#nodelist1 li.nodeelem').click(handler);
$('#nodelist2 li.nodeelem').click(handler2);
Is this rightway to access children clicks???
You forgot the hash # for ID selectors (although you corrected this in your edit):
$('#nodelist1 li.nodeelem').click(handler);
$('#nodelist2 li.nodeelem').click(handler2);
Tip: you can make the event more effective by using on() instead for event delegation:
$('#nodelist1').on('click', '.nodeelem', handler);
$('#nodelist2').on('click', '.nodeelem', handler2);

jquery next siblings

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>

Categories