Here I have a parent which contains a few children.
I want to display the index (number) of each child on the console by clicking on it.
i tried to use forEach method to detect the clicked child, but when i try to get the index of the child that i clicked it does not work. I tried indexOf() method but it shows an error
let parent = document.querySelector('.parent');
let children = document.querySelectorAll('.child');
children.forEach(child => {
child.onclick = function () {
console.log( /* children.indexOf(child) */ )
// this is the method i tried but it didn't worked
console.log( /*here i want to display the index of the clicked child */ );
}
});
<div class="parent">
<div class="child">a</div>
<div class="child">b</div>
<div class="child">c</div>
<div class="child">d</div>
</div>
You just need to convert children from a NodeList into an array (using Array.from) to use indexOf:
let parent = document.querySelector('.parent');
let children = document.querySelectorAll('.child');
children.forEach(child => {
child.onclick = function () {
console.log(Array.from(children).indexOf(child));
}
});
<div class="parent">
<div class="child">a</div>
<div class="child">b</div>
<div class="child">c</div>
<div class="child">d</div>
</div>
HTML
<div id="divID">
<label></label>
<textarea></textarea>
</div>
JavaScript
function invisible(){
let div = document.getElementById("divID");
let children = div.childNodes;
for (let i = 0; i < children.length; i++) {
children[i].style.display = "none";
}
}
After calling the function nothing happens. What I'm doing wrong?
You have to set divID to your div tag.
<div id="divID">
<label></label>
<textarea></textarea>
</div>
And then you have to use div.children in invisible function.
function invisible(){
let div = document.getElementById("divID");
let children = div.children;
for (let i = 0; i < children.length; i++) {
children[i].style.display = "none";
}
}
<input type="button" onClick=" invisible()" value="Remove" />
<div id="divID">
<label></label>
<textarea></textarea>
</div>
You can make the function more reusable by accepting the element whose children are to be hidden as the first argument. It currently only works for the element with id of "divID".
function invisible(parent){
for(const child of parent.children) child.style.display = 'none';
}
invisible(document.querySelector('div'))
<div>
<label>Label</label>
<textarea>Textarea</textarea>
</div>
The problem seems to be your use of childNodes instead of children as other answers have pointed out.
This answer attempts to give some more context on what is happening in addition to the other answers.
let div = document.getElementById("divID");
console.log(div.childNodes)
// Returns something like
NodeList(5) [text, label, text, textarea, text]
console.log(div.children)
// Returns something like
HTMLCollection(2) [label, textarea]
So childNodes returns a NodeList and children returns an HTMLCollection.
The definition children on mdn explains the difference between children and childNodes:
Element.children includes only element nodes. To get all child nodes, including non-element nodes like text and comment nodes, use Node.childNodes.
The problem is that these text nodes don't have a style property. So it returns undefined. Trying to access the display property on style then causes a TypeError. Since the first element is a text element the loop fails immediately and the label and textarea are never hidden.
use children instead of childNodes:
function invisible(){
let div = document.getElementById("divID");
let children = div.children; //<== children instead of childNodes
for (let i = 0; i < children.length; i++) {
children[i].style.display = "none";
}
}
//just to test after 1.5 sec all children of divID will be removed(display=none)
setTimeout(invisible, 1500)
<div id='divID'>
<label>test1</label>
<textarea>test2</textarea>
</div>
There is a root element in the DOM tree and there is another element inside this root element nested somewhere. How do I calculate how nested is this another element inside the root element?
What I would like to know is essentially how many times I have to get the parent element of the nested element until I get to the root element. So I can solve this problem by iterating on the parents until I get to the root element, like in this fiddle.
const first = document.getElementById('search-target-1');
let parent = first.parentElement;
let level = 0;
do {
parent = parent.parentElement;
level++;
}
while (!parent.classList.contains('root'))
console.log(`The first element is ${level} levels deep inside the root div.`);
const second = document.getElementById('search-target-2');
parent = second.parentElement;
level = 0;
do {
parent = parent.parentElement;
level++;
}
while (!parent.classList.contains('root'));
console.log(`The second element is ${level} level deep inside the root div.`);
<div class="root">
<div class="first-level">
<div class="second-level" id="search-target-1">
<!-- How deep is this element? -->
</div>
</div>
<div class="first-level"></div>
<div class="first-level">
<div class="second-level">
<div class="third-level" id="search-target-2">
<!-- And this one? -->
</div>
</div>
</div>
</div>
Is there a better way of achieving this? I am looking for a javascript api to get the same result.
The element.matchesSelector does not solve my problem, as I know the target element is inside the root element, but I don't know how deep it is.
You could use jQuery's .parentsUntil() function to accomplish this:
var searchDistance_1 = $("#search-target-1").parentsUntil(".root").length; // 1
var searchDistance_2 = $("#search-target-2").parentsUntil(".root").length; // 2
That gives you the number of parents in between the child and root you are looking for. If you're looking for the number of jumps up the hierarchy needed to get to the parent, you can just add 1.
If you need to do this in vanilla JS, you could look through the source code for this function on GitHub.
Your code works, but as Niet the Dark Absol said you need to take care of cases where the descendent isn't a descendent, e.g.
function getLevel(parent, child) {
let level = 0;
while (child && parent != child) {
level++;
child = child.parentNode;
}
return child? level : null;
}
var parent = document.getElementById('parent');
var child = document.getElementById('child');
var notChild = document.getElementById('notChild');
console.log(getLevel(parent, child)); // 3
console.log(getLevel(parent, notChild)); // null
<div id="parent">
<div>
<div>
<div id="child"></div>
</div>
</div>
</div>
<div id="notChild"></div>
Without the guarding condition to stop when the loop runs out of parnetNodes, it will throw an error if child isn't a descendant of parent.
I need to get user entered markup (tables) and if the table does not contain a div with a the class="table", I need to add the div & class. I need to ignore any other children, such as p, span, and only target elements with tables.
<div class="parent">
<div class="table"><table></table></div>
<div class="table"><table></table></div>
<table></table>
<div><table></table></div>
<div class="table"><table></table></div>
<p></p>
<span></span>
</div>
You can see in the nodelist above, node index 2,3 both need a wrapper div with the class="table", but ignore the p and span.
[].map.call(table, (node) => {
if (!node.parentNode.classList.contains('table')) {
const parent = document.getElementsByClassName('parent');
[].map.call(parent, (nodeChild) => {
const addWrap = document.createElement('div');
addWrap.classList.add('table');
addWrap.appendChild(node);
nodeChild.append(addWrap);
});
}
});
I have tried this, but it appends the node with a wrapper div at the bottom of the index. How do I get the nodes to append in their correct order with the wrapper div? Thanks.
You're better off using a for/loop (using map here is an anti-pattern) to iterate over the child nodes of the parent node and replace the current child node with the new created element.
childNodes
replaceChild
Inspect the output in dev tools to see the change.
const parent = document.querySelector('.parent');
const { childNodes } = parent;
for (let i = 0; i < childNodes.length; i++) {
const el = childNodes[i];
// If the node type is an element
if (el.nodeType === 1) {
// If element is a div add a class
// if it's missing
if (el.tagName === 'DIV' && !el.classList.contains('table')) {
el.classList.add('table');
}
if (el.tagName === 'TABLE') {
const div = document.createElement('div');
div.classList.add('table');
div.appendChild(el);
// Replace the child node with the new div node
parent.replaceChild(div, childNodes[i]);
}
}
};
<div class="parent">
<div class="table"><table></table></div>
<div class="table"><table></table></div>
<table></table>
<div><table></table></div>
<div class="table"><table></table></div>
<p></p>
<span></span>
</div>
I think I understood what you're trying to do. This should find child nodes without a "table" class and wrap them in a div with a "table" class. When you run the snippet it won't show any thing as your elements don't have any content but you should be able to inspect them to see the changes.
// get all parent divs
var parents = document.getElementsByClassName("parent");
// loop over parent divs
for (var i = 0; i < parents.length; i++) {
// get a single parent div
var parent = parents[i];
// loop over child nodes
for (var j = 0; j < parent.childNodes.length; j++) {
// get a single child node
var childNode = parent.childNodes[j];
// if this child node is type 1 (element node)
// and does not have a class of table
if (
childNode.nodeType === 1 &&
!childNode.classList.contains("table")
) {
// create a new div element
var wrap = document.createElement('div');
// give it a class of table
wrap.classList.add("table");
// append a clone of the child node
wrap.appendChild(childNode.cloneNode());
// replace the old child node with the wrapped child node
parent.replaceChild(wrap, childNode);
}
}
}
<div class="parent">
<div class="table"></div>
<div class="table"></div>
<table></table>
<div></div>
<div class="table"></div>
</div>
For some performance reasons, I am trying to find a way to select only sibling nodes of the selected node.
For example,
<div id="outer">
<div id="inner1"></div>
<div id="inner2"></div>
<div id="inner3"></div>
<div id="inner4"></div>
</div>
If I selected inner1 node, is there a way for me to access its siblings, inner2-4 nodes?
Well... sure... just access the parent and then the children.
node.parentNode.childNodes[]
or... using jQuery:
$('#innerId').siblings()
Edit: Cletus as always is inspiring. I dug further. This is how jQuery gets siblings essentially:
function getChildren(n, skipMe){
var r = [];
for ( ; n; n = n.nextSibling )
if ( n.nodeType == 1 && n != skipMe)
r.push( n );
return r;
};
function getSiblings(n) {
return getChildren(n.parentNode.firstChild, n);
}
var sibling = node.nextSibling;
This will return the sibling immediately after it, or null no more siblings are available. Likewise, you can use previousSibling.
[Edit] On second thought, this will not give the next div tag, but the whitespace after the node. Better seems to be
var sibling = node.nextElementSibling;
There also exists a previousElementSibling.
Quick:
var siblings = n => [...n.parentElement.children].filter(c=>c!=n)
https://codepen.io/anon/pen/LLoyrP?editors=1011
Get the parent's children as an array, filter out this element.
Edit:
And to filter out text nodes (Thanks pmrotule):
var siblings = n => [...n.parentElement.children].filter(c=>c.nodeType == 1 && c!=n)
From 2017:
straightforward answer: element.nextElementSibling for get the right element sibling. also you have element.previousElementSibling for previous one
from here is pretty simple to got all next sibiling
var n = element, ret = [];
while (n = n.nextElementSibling){
ret.push(n)
}
return ret;
have you checked the "Sibling" method in jQuery?
sibling: function( n, elem ) {
var r = [];
for ( ; n; n = n.nextSibling ) {
if ( n.nodeType === 1 && n !== elem ) {
r.push( n );
}
}
return r;
}
the n.nodeType == 1 check if the element is a html node and n!== exclude the current element.
I think you can use the same function, all that code seems to be vanilla javascript.
There are a few ways to do it.
Either one of the following should do the trick.
// METHOD A (ARRAY.FILTER, STRING.INDEXOF)
var siblings = function(node, children) {
siblingList = children.filter(function(val) {
return [node].indexOf(val) != -1;
});
return siblingList;
}
// METHOD B (FOR LOOP, IF STATEMENT, ARRAY.PUSH)
var siblings = function(node, children) {
var siblingList = [];
for (var n = children.length - 1; n >= 0; n--) {
if (children[n] != node) {
siblingList.push(children[n]);
}
}
return siblingList;
}
// METHOD C (STRING.INDEXOF, ARRAY.SPLICE)
var siblings = function(node, children) {
siblingList = children;
index = siblingList.indexOf(node);
if(index != -1) {
siblingList.splice(index, 1);
}
return siblingList;
}
FYI: The jQuery code-base is a great resource for observing Grade A Javascript.
Here is an excellent tool that reveals the jQuery code-base in a very streamlined way.
http://james.padolsey.com/jquery/
The following function will return an array containing all the siblings of the given element.
const getSiblings = node => [...node.parentNode.children].filter(c => c !== node)
// get "c" element siblings (excluding itself)
const siblingsToC = getSiblings(document.querySelector('.c'))
console.log( siblingsToC )
<ul>
<li class='a'>a</li>
<li class='b'>b</li>
<li class='c'>c</li>
<li class='d'>d</li>
<li class='e'>e</li>
</ul>
Just pass the selected element into the getSiblings() function as it's only parameter.
Here's how you could get previous, next and all siblings (both sides):
function prevSiblings(target) {
var siblings = [], n = target;
while(n = n.previousElementSibling) siblings.push(n);
return siblings;
}
function nextSiblings(target) {
var siblings = [], n = target;
while(n = n.nextElementSibling) siblings.push(n);
return siblings;
}
function siblings(target) {
var prev = prevSiblings(target) || [],
next = nexSiblings(target) || [];
return prev.concat(next);
}
Use document.querySelectorAll() and Loops and iteration
function sibblingOf(children,targetChild){
var children = document.querySelectorAll(children);
for(var i=0; i< children.length; i++){
children[i].addEventListener("click", function(){
for(var y=0; y<children.length;y++){children[y].classList.remove("target")}
this.classList.add("target")
}, false)
}
}
sibblingOf("#outer >div","#inner2");
#outer >div:not(.target){color:red}
<div id="outer">
<div id="inner1">Div 1 </div>
<div id="inner2">Div 2 </div>
<div id="inner3">Div 3 </div>
<div id="inner4">Div 4 </div>
</div>
jQuery
$el.siblings();
Native - latest, Edge13+
[...el.parentNode.children].filter((child) =>
child !== el
);
Native (alternative) - latest, Edge13+
Array.from(el.parentNode.children).filter((child) =>
child !== el
);
Native - IE10+
Array.prototype.filter.call(el.parentNode.children, (child) =>
child !== el
);
var childNodeArray = document.getElementById('somethingOtherThanid').childNodes;
1) Add selected class to target element 2) Find all children of parent element excluding target element 3) Remove class from target element
<div id = "outer">
<div class="item" id="inner1">Div 1 </div>
<div class="item" id="inner2">Div 2 </div>
<div class="item" id="inner3">Div 3 </div>
<div class="item" id="inner4">Div 4 </div>
</div>
function getSiblings(target) {
target.classList.add('selected');
let siblings = document.querySelecttorAll('#outer .item:not(.currentlySelected)')
target.classList.remove('selected');
return siblings
}
You can access the following sibling nodes, with the currentNode.nextSibiling property.
This is how you can do in the event delegation way, which is a dynamic way to add event listeners
document.addEventListener('click', (event) => {
if (event.target.matches("#inner1")) {
console.log(event.targert.nextSibling); //inner2 div
console.log(event.targert.nextSibling.nextSibling); //inner3 div
/* The more of the property you keep appending the further it goes to
the next sibling */
}
})
My use case was different. I had to select a few spans which didn't have any id/classes (nor their parents), just an entry point (#target). Once you have that, run a querySelectorAll on its parent with the appropriate selector, using :scope as you can't simply use > div or > span or > .foo.
Note that this approach ALSO selects the target element, if it matches the selector. In the below example, I'd have to use :scope > span:not(#target) to avoid selecting the entry point.
const spanSiblings = document.getElementById("target").parentNode.querySelectorAll(":scope > span");
console.log([...spanSiblings].map(e => e.innerText));
<div>
<span>One</span>
<span id="target">Target</span>
<div>A</div>
<span>Two</span>
<div>B</div>
<div>Hey</div>
</div>
BEST SOLUTION:
This is the best solution according my opinion:
let inner2 = event.target.parentNode.querySelector(`#inner2`)
/*Or if you have already stored the inner1 node to a variable called: inner1*/
let inner2 = inner1.parentNode.querySelector(`#inner2`)
At the first line the event.target will be the inner1 node, if we click on that. The parentNode will be the "outer" node, and on the partent node we start a search ( .querySelector(#inner2) ) to select the inner2 node.
OTHER SOLUTIONS:
I list other possible options, but they are not that flexible, since at them the sequence of the nodes are matter, which makes the code fragile, if we later add another node to the parent the whole code will break, what we want to avoid:
2)
This selects the first child (this index starts from 1, and NOT from 0)
node.parentNode.childNodes[1]
3) Assume that you have already selected inner1Node to a variable, the next sibling you can get:
let inner2Node = inner1Node.nextElementSibling;
4) The previous sibling you can get:
let inner1NodeAGAIN = inner2Node.previousElementSibling;
x1 = document.getElementById('outer')[0]
.getElementsByTagName('ul')[1]
.getElementsByTagName('li')[2];
x1.setAttribute("id", "buyOnlineLocationFix");