44

What is the perfect way to find all nextSiblings and previousSiblings in JavaScript. I tried few ways but not getting accurate solution. If any element is selected, I need to get length of all next siblings excluding white-space, any spaces or line-breaks.

Also I don't want to use jQuery for this. I am specifically looking something from JavaScript

Machavity
  • 30,841
  • 27
  • 92
  • 100
user504023
  • 553
  • 1
  • 7
  • 12

12 Answers12

42

This is a bit more winded of a solution but allows you to create a filter on how you get siblings.

There are three functions to get only previous, only next, or all. This could be improved but decent starting point if you need more control on what types of siblings you want to collect. Thought it might be worth adding.

Working Example

get all next siblings

//this will start from the current element and get all of the next siblings

function getNextSiblings(elem, filter) {
    var sibs = [];
    while (elem = elem.nextSibling) {
        if (elem.nodeType === 3) continue; // text node
        if (!filter || filter(elem)) sibs.push(elem);
    }
    return sibs;
}

get all previous siblings

//this will start from the current element and get all the previous siblings

function getPreviousSiblings(elem, filter) {
    var sibs = [];
    while (elem = elem.previousSibling) {
        if (elem.nodeType === 3) continue; // text node
        if (!filter || filter(elem)) sibs.push(elem);
    }
    return sibs;
}

get all siblings

//this will start from the first child of the current element's parent and get all the siblings

function getAllSiblings(elem, filter) {
    var sibs = [];
    elem = elem.parentNode.firstChild;
    do {
        if (elem.nodeType === 3) continue; // text node
        if (!filter || filter(elem)) sibs.push(elem);
    } while (elem = elem.nextSibling)
    return sibs;
}

example filter to apply to above functions

// Example filter only counts divs and spans but could be made more complex
function exampleFilter(elem) {
    switch (elem.nodeName.toUpperCase()) {
        case 'DIV':
            return true;
        case 'SPAN':
            return true;
        default:
            return false;
    }
}

HTML and testing output

HTML

<div id='test'>
    <div id='test2'>asdf</div>
    <br /> sdf
    <div>asdfasdf<span>asdf</span></div>
    <div>a</div>
    <span>a</span>
    <br />
    <div>d</div>
    <hr/>
</div>

JavaScript

var elem;
elem = document.getElementById('test2');

//with filter alerts 4
alert( getNextSiblings( elem, exampleFilter ).length );

// no filter, alerts 7
elem = document.getElementById('test2');// put elem back to what it was
alert( getNextSiblings( elem ).length );

// alerts 0
elem = document.getElementById('test2');// put elem back to what it was
alert( getPreviousSiblings( elem, exampleFilter ).length );

// alerts 5
elem = document.getElementById('test2');// put elem back to what it was
alert( getAllSiblings( elem, exampleFilter ).length );
subhaze
  • 8,815
  • 2
  • 30
  • 33
  • 1
    You can get rid of the redundancy in your `getAllSiblings` function by using a `do/while` instead of a `while` loop. A little code reduction might be desirable as well by replacing the `if/else` with an `if` that uses a conditional `||`, as in: `if(!filter || filter( elem )) sibs.push( elem );` – user113716 Dec 07 '10 at 23:43
  • Those are awesome examples. I'm confused though on why you needed the "exampleFilter" function. – ayjay Jun 09 '14 at 02:15
  • It was just a barebones example of what a filter might look like, it's not needed though. – subhaze Jun 09 '14 at 13:11
  • The `getNextSiblings( elem )` should not be returning 15 elements, there are only 7 in the demo. The reason there are 15 is because it is returning text nodes. If you remove the `!filter || ` from each function, it will ignore text nodes. Also, check out [my answer](http://stackoverflow.com/a/37470381/145346) which uses the `matches` DOM method allowing use of class names & attribute selectors. – Mottie May 26 '16 at 20:33
  • oh wow, yeah... nice catch. Now that the web has progressed ~6 years newer APIs do make things easier :P. One things to note about the matches is i believe IE needs a prefix. – subhaze May 26 '16 at 20:54
  • You can use `previousElementSibling` to filter out the non matching node types –  Oct 13 '19 at 02:14
  • Thanks @Catalin I'll try to update this to the modern API when I have a moment. – subhaze Oct 14 '19 at 13:22
33

I'll assume that this takes place inside an event handler where this is a reference to the targeted element whose siblings you want to affect.

If not, adjustments will be needed.

var result = [],
    node = this.parentNode.firstChild;

while ( node ) {
    if ( node !== this && node.nodeType === Node.ELEMENT_NODE ) 
      result.push( node );
    node = node.nextElementSibling || node.nextSibling;
}

// result will contain all type 1 siblings of "this"
sylbru
  • 1,461
  • 1
  • 11
  • 19
user113716
  • 318,772
  • 63
  • 451
  • 440
  • wow this work great. Thanks a lot. one more question. I am trying to apply webkit style to the result, but animation is not working. any reason? result.style.webkitTransform='translate(0px,10px)'; – user504023 Dec 07 '10 at 16:58
  • @user - Unfortunately I don't know much about the CSS3 transform property. Try searching SO, and if you can't find an answer, post another question. (Also, please remember to "accept" answers to your questions.) – user113716 Dec 07 '10 at 17:21
  • i applied loop, but this is working only once. when i clicking again on another object it doesn't work. any reason for(i=0; i – user504023 Dec 07 '10 at 17:21
  • yes sure, your answer really help me a lot. Thanks a lot for the help. I am bit new to java-script. – user504023 Dec 07 '10 at 17:23
  • @user - Accepting an answer means clicking the large checkmark to the left of the answer. With regard to the issue of it working only once, it likely has something to do with how `webkitTransform` works. I'm not much help there. – user113716 Dec 07 '10 at 17:53
  • Try WebkitTransform. A dash in CSS means a capital right after: text-align = textAlign; -webkit-transform = WebkitTransform – mwilcox Dec 07 '10 at 18:17
  • 1
    I don't understand how this is supposed to work: in my case `node` initially contains a text node (probably some whitespace), so the `while` loop is skipped altogether. Even without empty text nodes, this loop stops when `node === this`, effectively fetching only the *previous* siblings, right? – sylbru May 06 '18 at 08:51
  • 1
    Correct, @Niavlys. I have fixed that problem now in an edit. – LOAS Nov 08 '18 at 09:51
  • Thanks @LOAS for your edit. If it's acceptable I will probably make an edit as well to fix the text nodes issue (which most people will face, I guess). – sylbru Nov 08 '18 at 10:42
21

Here is a very short and simple way to do it with ES6:

function getAllSiblings(element, parent) {
        const children = [...parent.children];
        return children.filter(child => child !== element);
    }

This will return all children of the parent node that are not the element.

Stéphane Divin
  • 211
  • 2
  • 2
  • Where is children defined? – Marvin May 07 '19 at 23:47
  • 2
    Nice [Spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax). With it and `children.splice(0,1+children.indexOf(element));` you can get all next siblings. – dakab Jun 21 '20 at 10:32
  • 1
    If you want `ES2015`, just go all the way: `const getSiblings = elm => elm && elm.parentNode && [...elm.parentNode.children].filter(node => node != elm)` – vsync Oct 22 '20 at 13:13
5

A simple solution using ES2015 (spread & indexOf)

const getSiblings = (elm, withTextNodes) => {
  if( !elm || !elm.parentNode ) return
  
  let siblings = [...elm.parentNode[withTextNodes ? 'childNodes' : 'children']],
      idx = siblings.indexOf(elm);
  
  siblings.before = siblings.slice(0, idx)
  siblings.after = siblings.slice(idx + 1)
  
  return siblings
}


// Usage example:

const $elm = document.querySelector('em') // target node 
const elmSiblings = getSiblings($elm)

console.log("Siblings Before <em/>:", print(elmSiblings.before))
console.log("Siblings After <em/>:", print(elmSiblings.after))
console.log("All Siblings of <em/>:", print(elmSiblings))

function print(elms){
  return JSON.stringify(elms.map(n => n.tagName || n.textContent))
}
<div></div>
text node 1
<a></a>
<p></p>
<em></em>
<main></main>
text node 2
<hr/>
<b></b>
vsync
  • 118,978
  • 58
  • 307
  • 400
4

This is an update to @subhaze's answer.

This code uses the matches DOM method which is supported in modern browsers:

Demo

function matches(elem, filter) {
  if (elem && elem.nodeType === 1) {
    if (filter) {
      return elem.matches(filter);
    }
    return true;
  }
  return false;
}

// this will start from the current element and get all of
// the next siblings
function getNextSiblings(elem, filter) {
  var sibs = [];
  while (elem = elem.nextSibling) {
    if (matches(elem, filter)) {
      sibs.push(elem);
    }
  }
  return sibs;
}

// this will start from the current element and get all the
// previous siblings
function getPreviousSiblings(elem, filter) {
  var sibs = [];
  while (elem = elem.previousSibling) {
    if (matches(elem, filter)) {
      sibs.push(elem);
    }
  }
  return sibs;
}

// this will start from the first child of the current element's
// parent and get all the siblings
function getAllSiblings(elem, filter) {
  var sibs = [];
  elem = elem.parentNode.firstChild;
  while (elem = elem.nextSibling) {
    if (matches(elem, filter)) {
      sibs.push(elem);
    }
  } 
  return sibs;
}

Use these functions as follows:

var elem = document.querySelector('#test');

// find all the "div" and "span" siblings
var after = getNextSiblings(elem, 'div, span');

// find previous siblings with ".list-item" class
var index = getPreviousSiblings(elem, '.list-item');

// get all siblings with a title attribute
var allSibs = getAllSiblings(elem, '[title]');
Community
  • 1
  • 1
Mottie
  • 84,355
  • 30
  • 126
  • 241
3

You can get all of the children of the element's parent, and exclude the element itself.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • Yes jquery is javascript :) actually i am working on mobile app and due to its heavy file size i am not using it my code. Actually i have around 50 divs parallel to each other, as per the requirement when i click any div i need to get all next and previous siblings and need to move them to certain position – user504023 Dec 07 '10 at 16:20
  • @user: When minified and gzipped, jQuery is quite small. – SLaks Dec 07 '10 at 16:22
  • yes, in mobile safari there are some compatible issues, touch events and animation wont work properly with jquery. HTML 5, Webkit work awesome in mobile environment – user504023 Dec 07 '10 at 16:25
  • @user: You can use jQuery for selection only, without animation or touch events, and it will still be useful. Or you can do it manullay as I described. – SLaks Dec 07 '10 at 16:46
3

back to 2017:
Maybe there is a better answer but that good and a little bit cleaner

function sibiling(dom, query) {
   var doms = dom.parentElement.querySelectorAll(query);
   return [].slice.call(doms).filter( d => d != dom);
}
pery mimon
  • 7,713
  • 6
  • 52
  • 57
3

this is a more shorter solution :

let allSiblings = [...elem.parentElement.children].filter(child => child !== elem)];
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 11 '21 at 14:19
1

This answer was previously published here in response to a similar question .

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 excellant tool that reveals the jQuery code-base in a very streamlined way. http://james.padolsey.com/jquery/

Community
  • 1
  • 1
abbotto
  • 4,259
  • 2
  • 21
  • 20
1

All previous siblings

// jQuery (optional filter selector)
$el.prevAll($filter);

// Native (optional filter function)
function getPreviousSiblings(elem, filter) {
  var sibs = [];
  while (elem = elem.previousSibling) {
      if (elem.nodeType === 3) continue; // ignore text nodes
      if (!filter || filter(elem)) sibs.push(elem);
  }
  return sibs;
}

All next siblings

// jQuery (optional selector filter)
$el.nextAll($filter);

// Native (optional filter function)
function getNextSiblings(elem, filter) {
        var sibs = [];
        var nextElem = elem.parentNode.firstChild;
        do {
            if (nextElem.nodeType === 3) continue; // ignore text nodes
            if (nextElem === elem) continue; // ignore elem of target
            if (nextElem === elem.nextElementSibling) {
                if (!filter || filter(elem)) {
                    sibs.push(nextElem);
                    elem = nextElem;
                }
            }
        } while(nextElem = nextElem.nextSibling)
        return sibs;
    }

An example of filter function:

function exampleFilter(elem) {
  switch (elem.nodeName.toUpperCase()) {
    case 'DIV':
      return true;
    case 'SPAN':
      return true;
    default:
      return false;
  }
}
Humoyun Ahmad
  • 2,875
  • 4
  • 28
  • 46
1

This specifically helps you select all siblings of a selected item

This one below actually helped me select ALL SIBLINGS (and that originally excludes the selected item itself) through the PARENT (the only person who knows your siblings that you don't know is your parent right, its funny that the only ones YOU know are your immediate elder sibling i.e. previousElementSibling and immediate younger sibling i.e. nextElementSibling). Lol!

The Code

const allSiblings = Array.from(YOUR_SELECTION.parentElement.children)
                         .filter(sibling => sibling.UNIQUE_PropertyName !== (YOUR_SELECTION.COMPARABLE/UNIQUE_PropertyName));

// You don't have to save in a variable if you don't want to

Example

HTML

<div id="mummy">
    <div id="baby_1">Samuel</div>
    <div id="baby_2">Dave</div>
    <div id="baby_3">Shaun</div>
    <div id="baby_4">Michael</div>
    <div id="baby_5" class="selected">Fazlullah</div>
    <div id="baby_6">Samson</div>
    <div id="baby_7">Bais</div>
<div>

Javascript

// I have decide to use the Names of the children as my UNIQUE_Property, So i can get that with the .textContent propertyName 

const selected = document.querySelector('.selected'); // So with "Fazlullah" selected

const allSiblings = Array.from(selected.parentElement.children) // I got to know his mum (.parentElement), then the mum's children(.children)
                         .filter(sibling => sibling.textContent !== selected.textContent); // And this now get's me a list (actually an Array) of his siblings that he doesn't even know.

allSiblings.forEach(sibling => {
    console.log(sibling.textContent);
});

If i decide to use the children "id", my chained filter method would have looked: .filter(sibling => sibling.id !== selected.id);

See Demo

Community
  • 1
  • 1
0

Just my two cents here, I made a couple of functions to get all the previos and the next siblings of any element.

const getPreviousAll = element => {
  const previousAllFound = [];
  const getPrevious = element => {
    if (element !== null) {
      previousAllFound.push(element);
      const previousFound = element.previousElementSibling;
      if (previousFound !== null) {
        getPrevious(previousFound);
      }
    }
  };
  getPrevious(element.previousElementSibling);
  return previousAllFound;
};
const getNextAll = element => {
  const target = element;
  const nextAllFound = [];
  const getAll = element => {
    if (element !== null) {
      nextAllFound.push(element);
      const nextFound = element.nextElementSibling;
      if (nextFound !== null) {
        getAll(nextFound);
      }
    }
  };
  getAll(element.nextElementSibling);
  return nextAllFound;
};

You just have to call this functions with a node that you can get by getElementById.

XAE
  • 646
  • 9
  • 24