60

I want find the index of a given DOM node. It's like the inverse of doing

document.getElementById('id_of_element').childNodes[K]

I want to instead extract the value of K given that I already have the reference to the child node and the parent node. How do I do this?

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900

7 Answers7

104

The shortest possible way, without any frameworks, in all versions of Safari, FireFox, Chrome and IE >= 9:

var i = Array.prototype.indexOf.call(e.childNodes, someChildEl);

Matthew
  • 15,464
  • 2
  • 37
  • 31
  • Also would've been my answer :) +1! – Van Coding Feb 16 '12 at 17:36
  • 17
    Note: replace `e.childNodes` with `e.children` to find the index of an element within its sibling _elements_ - as opposed to sibling _nodes_, which include text and comment nodes. – Matthew Feb 17 '12 at 02:39
  • a similar example is given on the MDN: https://developer.mozilla.org/en-US/docs/DOM/NodeList#Workarounds – Lloyd Aug 21 '12 at 16:12
  • 3
    Why can't we do something like `e.parentNode.childNodes.indexOf(e)`? Edit: I just tested it. `NodeList` is not a real `Array` so we have to pull in `Array`'s impl of `indexOf` through its prototype. It is surprising to me that `Array`'s `indexOf` functions on `NodeList`s and whether this is intended or coincidental. – Steven Lu Jan 15 '13 at 23:44
  • 2
    @StevenLu Your first question: indexOf on a NodeList is not defined in the DOM spec, not even in DOM4. Your last point: it's intended. Array methods are generic enough to function on almost anything with a length property. – Matthew Jan 16 '13 at 17:13
  • 4
    @StevenLu It is intentional. See [ECMA-262 Edition 5](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) page 244 (145 in the pdf), the last note of section 15.4.4.14: "The `indexOf` function is intentionally generic; it does not require that its _this_ value be an **Array** object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the `indexOf` function can be applied successfully to a host object is implementation-dependent." – some Apr 28 '13 at 03:43
  • 3
    At the expense of an array allocation it can be shortened to `[].indexOf.call(children, el)`, unless that doesn't hold with the browser compatibility statement (I'd find that surprising though). – JMM May 05 '16 at 14:02
  • Nowadays you could do `[... e.childNodes].indexOf(elem)`. –  Jun 09 '16 at 03:34
  • @pat You **could**... However, it has the problem as in [JMMs comment](http://stackoverflow.com/questions/378365/finding-dom-node-index#comment61652497_9132575). – yckart Mar 11 '17 at 17:34
  • 1
    So, you could also do `Array.from(e.childNodes).indexOf(elem)` – yckart Mar 11 '17 at 17:43
  • 1
    Please note that `[].indexOf.call` allocates a new empty array which is after that is discarded. `[ ...e.childNodes].indexOf(elem)` allocates whole new array with the child nodes, which is then discarded. Original solution, on the contrary, does not explicitly allocate any new data anywhere, it applies existing function to existing data. – hijarian Jul 28 '17 at 10:26
23

A little shorter, expects the element to be in elem, returns k.

for (var k=0,e=elem; e = e.previousSibling; ++k);

After a comment from Justin Dearing I reviewed my answer and added the following:

Or if you prefer "while":

var k=0, e=elem;
while (e = e.previousSibling) { ++k;}

The original question was how to find the index of an existing DOM element. Both of my examples above in this answer expects elem to be an DOM element and that the element still exists in the DOM. They will fail if you give them an null object or an object that don't have previousSibling. A more fool-proof way would be something like this:

var k=-1, e=elem;
while (e) {
    if ( "previousSibling" in e ) {
        e = e.previousSibling;
        k = k + 1;
    } else {
        k= -1;
        break;
    }
}   

If e is null or if previousSibling is missing in one of the objects, k is -1.

Community
  • 1
  • 1
some
  • 48,070
  • 14
  • 77
  • 93
  • @some, I had to modify the example, and it looks like you have to move e = e.previousSibling to the miffle of the for loop and do a null check in firefox 3.5. – Justin Dearing Aug 26 '09 at 20:55
  • @Justin: "e=e.previousSibling" is in the middle... The for-loop declares k=0 and e=elem. Then it execute e=e.previousSibling until e===null. For every successful loop it increments k by one. And it works in FF3.5.2. Please explain what you think is wrong. – some Aug 27 '09 at 18:05
  • @Justin: Added some more examples. – some Aug 27 '09 at 19:06
  • some - If I may suggest, as above/below are relative specifiers, that you might consider inserting "Both of the examples above ([1](http://stackoverflow.com/questions/378365/text_removed_to_cause_answer_highlighting/378386#378386) & [2](http://stackoverflow.com/questions/378365/text_removed_to_cause_answer_highlighting/378405#378405)) expects __elem__" in place of the similar text in your answer, so if ever relevant, someone knows which two previous examples (assuming I am right) you are talking about. – user66001 Mar 07 '13 at 23:07
  • Just FYI, the fastest way of doing it is using the "for" loop, apparently. Source: http://jsperf.com/fastest-way-to-get-index-of-element-in-a-nodelist – brunoais Apr 27 '13 at 09:37
  • 1
    @brunoais In the `for` example you are using `previousElementSibling` and get the result 24, but in the `while` you are using `previousSibling` with the result of 49. I created a new [testcase](http://jsperf.com/fastest-way-to-get-index-of-element-in-a-nodelist/2) where they both use `previousElementSibling` and then there isn't that much of a difference anymore in my limited testing at least. Actually the `while` was a little faster in FF20 on Win7. – some Apr 28 '13 at 03:12
  • @brunoais and in IE10 `for` and `while` is about 40% slower than `indexOf`... The way you are getting the elements is in a live HTMLCollection. By converting that to an array instead `Array.prototype.slice.call(main.children,0)` that is the fastest too in FF20. – some Apr 28 '13 at 03:28
  • @user66001 Sorry that I didn't notice you comment until now. Actually, I was referring to my own examples in my answer, where they are above the note. Sorry if that wasn't clear. – some Apr 28 '13 at 03:34
  • @brunoais For future reference: At the machine level there is little to no difference between a for-loop and a while-loop. If you compare two functions where one uses _for_ and the other _while_ and you get a significant difference in time, you are probably doing something wrong. – some Apr 28 '13 at 14:47
  • @some Actually, that is not true for any scripting language, because the translation to machine code is different and the compiling can also be different. A "for" like the one I did can be seen as a single "block" that is recognized by the internals and already has algorithms that optimize it. For the while, it might be considered in 2 steps. Variable creation and the while. So, in C or java, or C#, etc... it's exactly the same, but for php or javascript, it is quite different. – brunoais Apr 28 '13 at 15:59
  • @some - NP. It wasn't clear to me, given the start of the sentence was talking about the original question. Thanks for clarifying :) – user66001 Apr 29 '13 at 05:49
  • @some - Thanks. Much better i.m.o, but ultimately, hopefully this will help others :) – user66001 May 02 '13 at 19:34
4

RoBorg's answer works... or you could try...

var k = 0;
while(elem.previousSibling){
    k++;
    elem = elem.previousSibling;
}
alert('I am at index: ' + k);
scunliffe
  • 62,582
  • 25
  • 126
  • 161
  • 3
    Can't you replace `while(elem.previousSibling){k++;elem = elem.previousSibling;}` with `while(elem=elem.previousSibling){k++;}`? – user66001 Mar 11 '13 at 02:35
3

A modern native approach might include Array.from(e.children).indexOf(theChild)

No IE support, but Edge works: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from

calipoop
  • 792
  • 9
  • 31
1

As with the original poster, I was trying to

find the index of a given DOM node

but one that I had just use a click handler on, and only in relation to its siblings. I couldn't end up getting the above to work (because of noobness undoubtably, i tried subbing in 'this' for elem but it didn't work).

My solution was to use jquery and use:

var index = $(this).parent().children().index(this);

It works without having to specify the type of the element ie:'h1' or an id etc.

Andrew Plummer
  • 1,150
  • 1
  • 12
  • 21
0

I think the only way to do this is to loop through the parent's children until you find yourself.

var K = -1;
for (var i = myNode.parent.childNodes.length; i >= 0; i--)
{
    if (myNode.parent.childNodes[i] === myNode)
    {
        K = i;
        break;
    }
}

if (K == -1)
    alert('Not found?!');
Greg
  • 316,276
  • 54
  • 369
  • 333
-1

using a framework like prototype you could use this :

$(el).up().childElements().indexOf($(el))
lezardo
  • 7
  • 1