Vanilla JavaScript equivalent of jQuery(…).parent().nextAll(selector) - javascript

I’m trying to have a vanilla JavaScript selector (without using jQuery) to get the same selection as shown below:
$('[data-automationid=pictureupload_1]').parent().nextAll('input[type=text]')
Can someone please help me? I’ve been struggling with it.

There is no nextAll method in DOM as far as I know, so it is a bit tricky to do that without using jQuery.
We can use a generator to iterate and filter nextElementSibling like this:
function* myNextAll(e, selector) {
while (e = e.nextElementSibling) {
if ( e.matches(selector) ) {
yield e;
}
}
}
let e1 = document.querySelector("[data-automationid=pictureupload_1]").parentElement;
let siblings = [...myNextAll(e1, "input[type=text]")];

Use document.querySelector('[data-automationid=pictureupload_1]') to select the starting-node.
Get with parentElement.children all the siblings from the parent-node (including the parent itself).
Iterate through the siblings until the parent-node is founded.
Look for all other siblings if they are INPUT-nodes and from type text (=1).
Collect them in an array.
For demonstration iterate over the result and change the background via test-class.
If you want try: https://jsfiddle.net/7p8wt4km/2/
let result = [];
let par = document.querySelector('[data-automationid=pictureupload_1]').parentElement;
let sibling = par.parentElement.children;
let found = false;
for (let i=0; i< sibling.length; i++) {
if (!found && sibling[i] ===par) {
found = true;
continue;
} else if (found) {
let sib = sibling[i];
if (sib.nodeName !== 'INPUT' || sib.nodeType!= 1) continue;
result.push(sib);
}
}
result.forEach(el => { el.classList.add('test');});
.test { background: green; }
<div>
<div>
Sibling 0
</div>
<div>
Parent
<div data-automationid='pictureupload_1'>
pictureupload_1
</div>
</div>
<input type='text'>
<div type='text'>
Sibling 2
</div>
<input type='test'>
<input type='checkbox'>
</div>

You can try my code get with index element
const getIndex = (node, groupNode) => {
return [...groupNode].indexOf(node);
}
Element.prototype.nextAll = function(){
var allChildren = this.parentNode.children;
var index = getIndex(this, allChildren);
allChildren = [...allChildren].filter((item) => {
return getIndex(item, allChildren) > index;
});
return allChildren;
}

The normal jQuery selector function and the parent method should be clear:
document.querySelector("[data-automationid=pictureupload_1]").parentNode
nextAll doesn’t currently have a vanilla JavaScript equivalent, but we can make one out of native DOM methods.
There are two ways of approaching it:
Select all children first, then filter all matching siblings after a certain element, or
using loops.
1. Select all children first, then filter all matching siblings after a certain element
Getting the set of siblings can be partially achieved with .parentNode.children.
But don’t worry about selecting the parent element itself, because filtering by DOM order is easy.
This uses Node.prototype.compareDocumentPosition and Element.prototype.matches.
I’ve also included some optional chaining so that if parentReference === document, parentReference.parentNode.children won’t throw an error.
It defaults to an empty array, then.
const selectParentMatchingNextSiblings = (jQuerySelector, siblingSelector) => {
const parentReference = document.querySelector(jQuerySelector).parentNode;
return Array.from(parentReference.parentNode?.children ?? [])
.filter((element) => parentReference.compareDocumentPosition(element) & Document.DOCUMENT_POSITION_FOLLOWING && element.matches(siblingSelector));
};
selectParentMatchingNextSiblings("[data-automationid=pictureupload_1]", "input[type=text]");
Alternative way with Ranges
The Range API can also be used for document order comparison.
const selectParentMatchingNextSiblings = (jQuerySelector, siblingSelector) => {
const parentReference = document.querySelector(jQuerySelector).parentNode,
nextSiblings = document.createRange();
nextSiblings.setStartAfter(parentReference);
nextSiblings.setEndAfter(parentReference.parentNode?.lastElementChild ?? parentReference);
return Array.from(parentReference.parentNode?.children ?? [])
.filter((element) => nextSiblings.comparePoint(element, 0) === 0 && element.matches(siblingSelector));
};
selectParentMatchingNextSiblings("[data-automationid=pictureupload_1]", "input[type=text]");
Note that this one won’t work with "html" as the first selector.
2. Using loops
const selectMatchingNextSiblings = (jQuerySelector, siblingSelector) => {
const parentReference = document.querySelector(jQuerySelector).parentNode,
result = [];
let currentElement = parentReference.nextElementSibling;
while(currentElement){
if(currentElement.matches(siblingSelector)){
result.push(currentElement);
}
currentElement = currentElement.nextElementSibling;
}
return result;
};
selectParentMatchingNextSiblings("[data-automationid=pictureupload_1]", "input[type=text]");
This should be self-explanatory.
The options from the other answers are also fine.

Related

How to not repeat myself when assigning several properties

I have an event listener with the parameter of the callback function event, assigned to an element. Is there any way how I could select that element's 5th parent without needing to write down this code?
event.target.parentNode.parentNode.parentNode.parentNode.parentNode;
Absolutely, there are a few ways you can achieve this.
I. The JQuery Method:
$('#element1').parents().eq(5);
II. Raw Javascript
Function returnNthParent(n, element)
{
parent = element;
for (i = 0; i < n; i++)
{
parent = parent.parentNode;
}
return parent;
}
There is no nth parent method so you have to either do a bunch of parentNode's or use closest(selector) which will walk the DOM tree until it finds a match. So you find something unique about it and use that
document.querySelector('input').addEventListener("input", function(evt) {
const row = evt.target.closest('.row');
console.log(row);
});
<div class="row">
<div>
<div>
<div>
<input />
</div>
</div>
</div>
</div>
If you know the exact number of the parent elements chain you need to go through, then you may implement simple helper
const takeParent = (target, count) => {
while (--count >= 0) {
if (target === document) {
break;
}
target = target.parentNode;
}
return target;
};
takeParent(myElement, 5); // give 5th parent of myElement or "document" if there are less than 5 parents

Elegant way of checking if one of the parentNodes has a certain class

I have a menu that expands and retracts on hover. The problem is the menu has many elements and to trigger my expand function I need to write something like below. My actual code includes more code and I was wondering if there would be a better way to do this.
var e = event.target
if(
e.parentNode.className.split(" ")[0] === "main-section" ||
e.parentNode.parentNode.className.split(" ")[0] === "main-section" ||
e.parentNode.parentNode.parentNode.className.split(" ")[0] === "main-section"){
//do somehtings}
In modern environments you can use the DOM's closest method:
if (e.closest(".main-section")) {
// One was found...
}
It looks at the current element to see if it matches the selector, then its parent element, then its parent, etc. to the root of the tree. It returns the element it finds, or null if it doesn't find one.
For slightly older environments, Element#closest can be polyfilled. Or if you don't like polyfilling, you can give yourself a utility function instead that uses closest if it exists, or uses matches if not:
function closest(el, selector) {
if (el.closest) {
return el.closest(selector);
}
var matches = el.matches || el.matchesSelector;
while (el) {
if (matches.call(el, selector)) {
return el;
}
el = el.parentNode;
}
return null;
}
...which you'd use like this:
if (closest(e, ".main-section")) {
// One was found...
}
Method closest() is not supported in some browsers, so I took this function for you from this answer
function findAncestor (el, sel) {
while ((el = el.parentElement) && !((el.matches || el.matchesSelector).call(el,sel)));
return el;
}
Use classList with a recursive function like so.
const start = document.getElementById("start");
function recursiveCheck(ele, className, limit = 3, current = 0){
return ele.classList.contains(className) ? true : current >= limit ? false : recursiveCheck(ele.parentNode, className, limit, current + 1);
}
console.log(
recursiveCheck(start, "test")
);
<div class="test">
<div>
<div id="start"><div>
</div>
</div>

jQuery index() in vanilla javascript

As per the jQuery api, the complementary operation to .get(), which accepts an index and returns a DOM node, .index() can take a DOM node and returns an index. Suppose we have a simple unordered list on the page:
<ul>
<li id="foo">foo</li>
<li id="bar">bar</li>
<li id="baz">baz</li>
</ul>
.index() will return the position of the first element within the set of matched elements in relation to its siblings:
alert('Index: ' + $('#bar').index();
We get back the zero-based position of the list item:
Index: 1
I just want to know, how can we do the same using JavaScript??
You can build your own function :
function indexInParent(node) {
var children = node.parentNode.childNodes;
var num = 0;
for (var i=0; i<children.length; i++) {
if (children[i]==node) return num;
if (children[i].nodeType==1) num++;
}
return -1;
}
Demonstration (open the console)
I've modified Travis J answer to not include TextNodes and made a function out of it.
You can run it in the console and see (on stackoverflow).
Classic way:
function getNodeIndex( elm ){
var c = elm.parentNode.children,
i = c.length;
while(i--)
if( c[i] == elm )
return i
}
With ES2015:
const getNodeIndex = elm => [...elm.parentNode.children].findIndex(c => c == elm)
// or
const getNodeIndex = elm => [...elm.parentNode.children].indexOf(elm)
Demo:
const getNodeIndex = elm => [...elm.parentNode.children].indexOf(elm)
<button onclick="console.log( getNodeIndex(this) )">get index</button>
<button onclick="console.log( getNodeIndex(this) )">get index</button>
<button onclick="console.log( getNodeIndex(this) )">get index</button>
I also want to point to another thread on the same matter, which has a great answer (for people seeking older IE support)
No loops needed, call Array#indexOf on .parentElement.children:
const element = document.querySelector('#baz');
[].indexOf.call(element.parentElement.children, element);
// => 2
You can even call it on a random list of elements, just like you can in jQuery:
const list = document.querySelectorAll('li');
const element = document.querySelector('#baz');
[].indexOf.call(list, element);
// => 2
You can find this information out by going up the dom tree using previousElementSibling and incrementing.
var getElementIndex(element) {
if (!element) {
return -1;
}
var currentElement = element,
index = 0;
while(currentElement.previousElementSibling) {
index++;
currentElement = currentElement.previousElementSibling;
}
return index
}
getElementIndex(document.getElementById('#bar'));
here is the browser support for previousElementSibling:
https://developer.mozilla.org/en-US/docs/Web/API/NonDocumentTypeChildNode/previousElementSibling
Index can be found in this way
Array.from(children).findIndex(element => element.id === "bar")

How to get child element by class name?

I'm trying to get the child span that has a class = 4. Here is an example element:
<div id="test">
<span class="one"></span>
<span class="two"></span>
<span class="three"></span>
<span class="four"></span>
</div>
The tools I have available are JS and YUI2. I can do something like this:
doc = document.getElementById('test');
notes = doc.getElementsByClassName('four');
//or
doc = YAHOO.util.Dom.get('#test');
notes = doc.getElementsByClassName('four');
These do not work in IE. I get an error that the object (doc) doesn't support this method or property (getElementsByClassName). I've tried a few examples of cross browser implementations of getElementsByClassName but I could not get them to work and still got that error.
I think what I need is a cross browser getElementsByClassName or I need to use doc.getElementsByTagName('span') and loop through until I find class 4. I'm not sure how to do that though.
Use querySelector and querySelectorAll
var testContainer = document.querySelector('#test');
var fourChildNode = testContainer.querySelector('.four');
IE9 and upper
Use doc.childNodes to iterate through each span, and then filter the one whose className equals 4:
var doc = document.getElementById("test");
var notes = null;
for (var i = 0; i < doc.childNodes.length; i++) {
if (doc.childNodes[i].className == "4") {
notes = doc.childNodes[i];
break;
}
}
​
The accepted answer only checks immediate children. Often times we're looking for any descendants with that class name.
Also, sometimes we want any child that contains a className.
For example: <div class="img square"></div> should match a search on className "img", even though it's exact className is not "img".
Here's a solution for both of these issues:
Find the first instance of a descendant element with the class className
function findFirstChildByClass(element, className) {
var foundElement = null, found;
function recurse(element, className, found) {
for (var i = 0; i < element.childNodes.length && !found; i++) {
var el = element.childNodes[i];
var classes = el.className != undefined? el.className.split(" ") : [];
for (var j = 0, jl = classes.length; j < jl; j++) {
if (classes[j] == className) {
found = true;
foundElement = element.childNodes[i];
break;
}
}
if(found)
break;
recurse(element.childNodes[i], className, found);
}
}
recurse(element, className, false);
return foundElement;
}
Use element.querySelector().
Lets assume:
'myElement' is the parent element you already have.
'sonClassName' is the class of the child you are looking for.
let child = myElement.querySelector('.sonClassName');
For more info, visit: https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector
Modern solution
const context = document.getElementById('context');
const selected = context.querySelectorAll(':scope > div');
documentation
You could try:
notes = doc.querySelectorAll('.4');
or
notes = doc.getElementsByTagName('*');
for (var i = 0; i < notes.length; i++) {
if (notes[i].getAttribute('class') == '4') {
}
}
To me it seems like you want the fourth span. If so, you can just do this:
document.getElementById("test").childNodes[3]
or
document.getElementById("test").getElementsByTagName("span")[3]
This last one ensures that there are not any hidden nodes that could mess it up.
Use the name of the id with the getElementById, no # sign before it. Then you can get the span child nodes using getElementsByTagName, and loop through them to find the one with the right class:
var doc = document.getElementById('test');
var c = doc.getElementsByTagName('span');
var e = null;
for (var i = 0; i < c.length; i++) {
if (c[i].className == '4') {
e = c[i];
break;
}
}
if (e != null) {
alert(e.innerHTML);
}
Demo: http://jsfiddle.net/Guffa/xB62U/
But be aware that old browsers doesn't support getElementsByClassName.
Then, you can do
function getElementsByClassName(c,el){
if(typeof el=='string'){el=document.getElementById(el);}
if(!el){el=document;}
if(el.getElementsByClassName){return el.getElementsByClassName(c);}
var arr=[],
allEls=el.getElementsByTagName('*');
for(var i=0;i<allEls.length;i++){
if(allEls[i].className.split(' ').indexOf(c)>-1){arr.push(allEls[i])}
}
return arr;
}
getElementsByClassName('4','test')[0];
It seems it works, but be aware that an HTML class
Must begin with a letter: A-Z or a-z
Can be followed by letters (A-Za-z), digits (0-9), hyphens ("-"), and underscores ("_")
In my opinion, each time you can, you should use Array and its methods. They are much, much faster then looping over the whole DOM / wrapper, or pushing stuff into empty array. Majority of solutions presented here you can call Naive as described here (great article btw):
https://medium.com/#chuckdries/traversing-the-dom-with-filter-map-and-arrow-functions-1417d326d2bc
My solution: (live preview on Codepen: https://codepen.io/Nikolaus91/pen/wEGEYe)
const wrapper = document.getElementById('test') // take a wrapper by ID -> fastest
const itemsArray = Array.from(wrapper.children) // make Array from his children
const pickOne = itemsArray.map(item => { // loop over his children using .map() --> see MDN for more
if(item.classList.contains('four')) // we place a test where we determine our choice
item.classList.add('the-chosen-one') // your code here
})
using querySelector
var doc=document.getElementById("test");
console.log(doc.querySelector('.two').innerHTML)
<div id="test">
<span class="one"></span>
<span class="two">two</span>
<span class="three"></span>
<span class="four"></span>
</div>
Using querySelectorAll
var doc=document.getElementById("test");
console.log(doc.querySelectorAll('*')[1].innerHTML)
<div id="test">
<span class="one"></span>
<span class="two">two</span>
<span class="three"></span>
<span class="four"></span>
</div>
using getElementsByTagNames
var doc=document.getElementById("test");
console.log(doc.getElementsByTagName("SPAN")[1].innerHTML);
<div id="test">
<span class="one"></span>
<span class="two">two</span>
<span class="three"></span>
<span class="four"></span>
</div>
<span>ss</span>
Using getElementsByClassName
var doc=document.getElementById("test");
console.log(doc.getElementsByClassName('two')[0].innerHTML)
<div id="test">
<span class="one"></span>
<span class="two">two</span>
<span class="three"></span>
<span class="four"></span>
</div>
I believe this would answer your question best
document.querySelector('* > span.four')
This will match the first child element (of any parent) it finds that is a span and also has a class "four" set to it
However since in your example you also had a parent element which you are able to retrieve by id, you could also use this instead
document.querySelector('#test > span.four')
If you have a parent element saved in a variable like in your example, and you wish to search the subtree of that element, using :scope, as Billizzard has mentioned already, is probably your best choice
doc.querySelector(':scope > span.four');
Little extra: If the child element you are looking for isn't a direct child descendent, but somewhere further down the subtree, you can actually just omit the > like so
document.querySelector('#test span.four')
The way i will do this using jquery is something like this..
var targetedchild = $("#test").children().find("span.four");
You can fetch the parent class by adding the line below. If you had an id, it would be easier with getElementById. Nonetheless,
var parentNode = document.getElementsByClassName("progress__container")[0];
Then you can use querySelectorAll on the parent <div> to fetch all matching divs with class .progress__marker
var progressNodes = progressContainer.querySelectorAll('.progress__marker');
querySelectorAll will fetch every div with the class of progress__marker
Another way
const result = [...(parentElement.children)].find(child => {
return child.classList.contains('some-class-name');
});
First we spread the elements of the NodeList to turn it into an Array so we can make use of the find() method. Lastly, find() will return to us the first element whose classList property contains the given class name.
Here is a relatively simple recursive solution. I think a breadth-first search is appropriate here. This will return the first element matching the class that is found.
function getDescendantWithClass(element, clName) {
var children = element.childNodes;
for (var i = 0; i < children.length; i++) {
if (children[i].className &&
children[i].className.split(' ').indexOf(clName) >= 0) {
return children[i];
}
}
for (var i = 0; i < children.length; i++) {
var match = getDescendantWithClass(children[i], clName);
if (match !== null) {
return match;
}
}
return null;
}
I know this question is a few years old and there have been a few answers to this but I thought I would add my solution just in case it helps anyone. It's in the same vein as the answer given by user2795540 and involves an array iterator.
If you're just wanting to get the first child that has the four class then you could use the find array iterator. Your browser will need to be able to support ES6 or you can use Babel to compile your JS into something all browsers will support. IE will not support this without a polyfill.
Using the same details you provided in your question it could look something like this:
const parentNode = document.getElementById('test');
const childNode = Array.from(parentNode.childNodes).find(({ className }) => className === 'four');
The above solution will return the node you want to target and store it in the childNode variable.
You can find out more about the find array iterator at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
June 2018 update to ES6
const doc = document.getElementById('test');
let notes = null;
for (const value of doc) {
if (value.className === '4') {
notes = value;
break;
}
}
let notes = document.querySelector('#test .four')
YUI2 has a cross-browser implementation of getElementsByClassName.
Here is how I did it using the YUI selectors. Thanks to Hank Gay's suggestion.
notes = YAHOO.util.Dom.getElementsByClassName('four','span','test');
where four = classname, span = the element type/tag name, and test = the parent id.
Use YAHOO.util.Dom.getElementsByClassName() from here.

How do I find if an element contains a specific class with jQuery?

I need to check if an element contains a certain child class using JQUERY.
I tried:
if ($('#myElement').has('.myClass')) {
do work son
}
Didn't work.
My html code is laid out like this:
<div id="myElement">
<img>
<span>something</span>
<span class="myClass">Hello</span>
</div>
The easiest way would be to search for .myClass as a child of #myElement:
if($('#myElement .myClass')).length > 0)
If you only want first level children, you'd use >
if($('#myElement > .myClass')).length > 0)
Another way would be passing a selector to find and checking for any results:
if($('#myElement').find('.myClass').length > 0)
Or for first level children only:
if($('#myElement').children('.myClass').length > 0)
Just use QS
var hasClass = document.getElementById("myElement").querySelector(".myClass");
or you could recurse over the children
var element = document.getElementById("myElement");
var hasClass = recursivelyWalk(element.childNodes, function hasClass(node) {
return node.classList.contains("myClass");
});
function recursivelyWalk(nodes, cb) {
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
var ret = cb(node);
if (ret) {
return ret;
}
if (node.childNodes && node.childNodes.length) {
var ret = recursivelyWalk(node.childNodes, cb);
if (ret) {
return ret;
}
}
}
}
Using recursivelyWalk and .classList (which can be shimmed).
Alternatively you can use jQuery
$("#myElement .myClass").hasClass("myClass");
or if you want composite operations without jQuery then try NodeComposite
NodeComposite.$("#myElement *").classList.contains("myClass");
Try:
if($('#myElement').children('.myClass').length) {
// Do what you need to
}
The jQuery object returns an array, which has the .length property. The above code checks if there are any .myClass children in #myElement and, if there are (when .length isn't 0), executes the code inside the if() statement.
Here's a more explicit version:
if($('#myElement').children('.myClass').length > 0) {
// Do what you need to
}
You could always use $('#myElement .myClass').length too, but $.children() is clearer to some. To find elements that aren't direct children, use $.find() in place of $.children().
if($.contains($('#myElement'), $('.myClass'))){
alert("True");
}
else{alert("False")};

Categories