Is there a way to select sibling nodes?
Asked Answered
B

18

149

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?

Blackface answered 9/5, 2009 at 0:9 Comment(0)
G
184

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);
}
Girardo answered 9/5, 2009 at 0:12 Comment(7)
Note that jquery.siblings() excludes the current node from the result set.Jowett
Which is a very nice feature!Girardo
Actually the equivalent of node.parentNode.childNodes[] in jquery is really $(node).parent().children() not $(node).siblings(). It can be a nice feature but it can also be annoying. It depends on what you want.Jowett
You'll wanna check for ELEMENT_NODE nodes with node.parentNode.childNodes, cause it will give you white space text nodes too.Celanese
Nothing, I think it was a goof -- sorry about that (removed)Girardo
it is bad. only gets siblings that are next to your element using nextSibling. what about when your element is bang in the middle of the element group and you'd want to get all of the siblings.Quixote
@MaciejSitko That's why it calls n. parentNode. firstChild before doing the nextSibling loop.Jacal
C
138
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.

Candace answered 2/11, 2011 at 8:35 Comment(3)
next/previousElementSibling isn't supported in IE8Eastbourne
Starting from IE9 it is. See also #5198325Candace
nextElementSibling works!Strephonn
O
32

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)
Ovate answered 18/7, 2017 at 4:43 Comment(4)
Nice solution. Although, I would check for nodeType to discard text node [ ...n.parentNode.children ].filter(c => c.nodeType == 1 && c != n)Popedom
How would you modify this for typescript?Tally
@Popedom Element.children directly returns only the element children, so filtering by node type is redundant.Apocynthion
Building on this, if you specifically want to emulate CSS ~ (subsequent siblings), you can use [...n.parentNode.children].filter((c, i, a) => c.nodeType == 1 && i > a.indexOf(n))Tableware
A
16

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;
Attu answered 8/9, 2017 at 21:38 Comment(2)
It should return all siblings, not only next or previous. Specifying exactly going backward or forward does not help much in this case.Pergola
why not. if you take next and previous you can get the whole picture. that's answer comes to show a new way to do things. and contribute something to readers. just to go parent and go each element and filter the current one everyone can doAttu
T
8

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.

Teetotaler answered 15/8, 2012 at 14:52 Comment(0)
W
5

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/

Wolfson answered 1/4, 2014 at 23:51 Comment(0)
P
5

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.

Pituri answered 10/3, 2019 at 8:44 Comment(0)
H
2

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>
Heartland answered 20/6, 2016 at 19:25 Comment(0)
Q
2

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);
}
Quixote answered 7/8, 2016 at 10:4 Comment(0)
L
2

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
);
Looker answered 16/8, 2019 at 0:51 Comment(0)
N
0
var childNodeArray = document.getElementById('somethingOtherThanid').childNodes;
Nedneda answered 9/5, 2009 at 0:40 Comment(3)
window.document.documentElement.childNodes[1].childNodes[4] picked up from howtocreate.co.uk/tutorials/javascript/dombasicsNedneda
is it any solution of the question at all?Fortaleza
This does not solve the problem at hand, or at least not clearly enough to be useful.Tangram
Q
0

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
    }
Quagga answered 18/4, 2019 at 4:52 Comment(0)
R
0

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 */
   }
 })
Roveover answered 23/3, 2021 at 16:16 Comment(0)
S
0

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>
Speer answered 26/5, 2022 at 6:16 Comment(0)
G
0

BEST SOLUTION:

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;
Grossularite answered 26/2, 2023 at 12:55 Comment(0)
V
0

2023 solutions

This solutions are much more shorter than in accepted answer.

ES5 solution

We can use Element.children property, then convert it to an Array and then delete the target from this array.

Element.children property gets only nodes from type Element in all modern browsers (IE9 inclusive). So filtering by node type is redundant.

For best browser compability I do not use here the function Array.from because it is not supported in IE11.

[].slice.call(HTMLCollection) – converts HTMLCollection to Array.

function getSiblings_ES5(node)
{
  var childs = [].slice.call(node.parentNode.children);
  childs.splice(childs.indexOf(node), 1);
  return childs
}

var child = document.querySelector('ul li:nth-child(3)'),
    sibls = getSiblings_ES5(child),
    i;
   
for(i in sibls)
  console.log(sibls[i].innerText);
<ul>
  <li>1</li>
  <li>2</li>
  <li style="color:red">3</li>
  <li>4</li>
</ul>

ES6 solution

var getSiblings_ES6 = node => 
    [...node.parentElement.children]
    .filter(el => el != node);
   
var child = document.querySelector('ul li:nth-child(3)'),
    sibls = getSiblings_ES6(child),
    i;
   
for(i in sibls)
  console.log(sibls[i].innerText);
<ul>
  <li>1</li>
  <li>2</li>
  <li style="color:red">3</li>
  <li>4</li>
</ul>

Documentation

Velasco answered 20/5, 2023 at 21:41 Comment(0)
C
0

This is the modernized solution as a function

function getSiblings(el) {
  const childrenArray = [ ...el.parentNode.children ];
  const siblings = childrenArray.filter(child => child !== el);

  return siblings;
}
Chessy answered 24/2 at 18:53 Comment(0)
J
-2
x1 = document.getElementById('outer')[0]
      .getElementsByTagName('ul')[1]
      .getElementsByTagName('li')[2];
x1.setAttribute("id", "buyOnlineLocationFix");
Justinn answered 24/3, 2016 at 21:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.