jQuery index() in vanilla javascript - 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")

Related

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

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.

getElementsByClassName, recursion, one parameter, no underscore.js

I am trying to write getElementsByClassName() without using the getElementsByClassName method or the _. methods. I want to use recursion with the function having only one parameter, then push each value into an array. Afterword return that array. Below is my code so far. I have tried several versions, what am I doing wrong? Thank you for your help.
I realize that this question has been asked before and answered. However, I am trying to write the function using only one parameter, others use two. I am also using no _. underscore methods, others use underscore. I need to use recursion using no underscore, only one parameter, and can not use the getElementsByClassName method. I have been working on this for quite awhile and would love some help thanks!
var getElementsByClassName = function(className) {
var nodeArray = [];
var fakeNode = [1,2,3,4,5,6,7];
// var variableNode = document.getElementsByClassName(className); // ['element 1', 'element 2', 'element 3']
if(document.nodeName === className) {
var variableNode = document.querySelector(className);
// base case:
if (variableNode.length === 0) {
return [];
// rec case:
} else {
for(var i = 0; i < variableNode.length; i++) {
nodeArray.push(variableNode[i]);
}
getElementsByClassName();
}
}
return nodeArray;
};
In order to do this recursively, you need to recurse through the DOM hierarchy. This means that the function has to take a DOM node to start at, then make recursive calls on all its children. At each level it creates a result array; it adds the current node if it matches the criteria, concatenates the results from the children, and then returns this result.
function getElementsByClassName(className, node = document) {
var nodeArray = [];
if (node.classList && node.classList.contains(className)) {
nodeArray.push(node);
}
if (node.children) {
for (var i = 0; i < node.children.length; i++) {
nodeArray = nodeArray.concat(getElementsByClassName(className, node.children[i]));
}
}
return nodeArray;
}
console.log(getElementsByClassName("a").map(e => e.id));
console.log(getElementsByClassName("b").map(e => e.id));
<div id="foo" class="a">
<div id="bar" class="b">
<span id="span-1" class="a">abc</span>
<span id="span-2" class="b">def</span>
</div>
<h1 id="header-1" class="a">Header</h1>
</div>
The base of the recursion is when you get to a node with no children, because node.childNodes.length will be 0 and it won't go into the loop.
Notice the default value for the node argument. This allows you to call the function with one argument to search the entire document, but it will use the second argument when it recurses.

Using querySelectorAll to get ALL elements with that class name, not only the first

I've ditched jquery about 9(ish) months ago and needed a selector engine (without all the hassle and don't mind ie<7 support) so i made a simplified version of document.querySelectorAll by creating this function:
// "qsa" stands for: "querySelectorAll"
window.qsa = function (el) {
var result = document.querySelectorAll(el)[0];
return result;
};
This works perfectly fine for 95% of the time but I've had this problem for a while now and i have researched mdn, w3c, SO and not to forget Google :) but have not yet found the answer as to why I only get the first element with the requested class.
And I know that only the first element being returned is caused by the "[0]" at the end, but the function won't work if I remove it so I've tried to make a for loop with an index variable that increases in value depending on the length of elements with that class like this:
window.qsa = function (el) {
var result, el = document.querySelectorAll(el);
for(var i = 0; i < el.length; ++i) {
result = el[i];
}
return result;
};
Again that did not work so I tried a while loop like this:
window.qsa = function (el) {
var result, i = 0, el = document.querySelectorAll(el);
while(i < el.length) {
i++;
}
result = el[i];
return result;
};
By now I'm starting to wonder if anything works? and I'm getting very frustrated with document.querySelectorAll...
But my stubborn inner-self keeps going and I keep on failing (tiering cycle) so I know that now is REALLY the time to ask these questions :
Why is it only returning the first element with that class and not all of them?
Why does my for loop fail?
Why does my while loop fail?
And thank you because any / all help is much appreciated.
Why is it only returning the first element with that class and not all of them?
Because you explicitly get the first element off the results and return that.
Why does my for loop fail?
Because you overwrite result with a new value each time you go around the end of loop. Then you return the last thing you get.
Why does my while loop fail?
The same reason.
If you want all the elements, then you just get the result of running the function:
return document.querySelectorAll(el)
That will give you a NodeList object containing all the elements.
Now that does what you say you want, I'm going to speculate about what your real problem is (i.e. why you think it doesn't work).
You haven't shown us what you do with the result of running that function, but my guess is that you are trying to treat it like an element.
It isn't an element. It is a NodeList, which is like an array.
If you wanted to, for instance, change the background colour of an element you could do this:
element.style.backgroundColor = "red";
If you want to change the background colour of every element in a NodeList, then you have to change the background colour of each one in turn: with a loop.
for (var i = 0; i < node_list.length; i++) {
var element = node_list[i];
element.style.backgroundColor = "red";
}
You are returning a single element. You can return the array. If you want to be able to act on all elements at once, jQuery style, you can pass a callback into your function;
window.qsa = function(query, callback) {
var els = document.querySelectorAll(query);
if (typeof callback == 'function') {
for (var i = 0; i < els.length; ++i) {
callback.call(els[i], els[i], i);
}
}
return els;
};
qsa('button.change-all', function(btn) {
// You can reference the element using the first parameter
btn.addEventListener('click', function(){
qsa('p', function(p, index){
// Or you can reference the element using `this`
this.innerHTML = 'Changed ' + index;
});
});
});
qsa('button.change-second', function(btn) {
btn.addEventListener('click', function(){
var second = qsa('p')[1];
second.innerHTML = 'Changed just the second one';
});
});
<p>One</p>
<p>Two</p>
<p>Three</p>
<button class='change-all'>Change Paragraphs</button>
<button class='change-second'>Change Second Paragraph</button>
Then you can call either use the callback
qsa('P', function(){
this.innerHTML = 'test';
});
Or you can use the array that is returned
var pList = qsa('p');
var p1 = pList[0];
This loop
for(var i = 0; i < el.length; ++i) {
result = el[i];
}
overwrites your result variable every time. That's why you always get only one element.
You can use the result outside though, and iterate through it. Kinda like
var result = window.qsa(el)
for(var i = 0; i < result.length; ++i) {
var workOn = result[i];
// Do something with workOn
}

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