176

Let's say I have this markup:

<ul id="wizard">
    <li>Step 1</li>
    <li>Step 2</li>
</ul>

And I have this jQuery:

$("#wizard li").click(function () {
    // alert index of li relative to ul parent
});

How can I get the index of the child li relative to it's parent, when clicking that li?

For example, when you click "Step 1", an alert with "0" should pop up.

Liam
  • 27,717
  • 28
  • 128
  • 190

6 Answers6

271
$("#wizard li").click(function () {
    console.log( $(this).index() );
});

However rather than attaching one click handler for each list item it is better (performance wise) to use delegate which would look like this:

$("#wizard").delegate('li', 'click', function () {
    console.log( $(this).index() );
});

In jQuery 1.7+, you should use on. The below example binds the event to the #wizard element, working like a delegate event:

$("#wizard").on("click", "li", function() {
    console.log( $(this).index() );
});
Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114
redsquare
  • 78,161
  • 20
  • 151
  • 159
  • 3
    hi redsquare.. I'm practicing with jQuery, I'm kinda n00b about delegate :D .. why delegate is better than attaching events directly on the elements? – stecb Feb 14 '11 at 18:52
  • 1
    @steweb - Hey - because one event handler is much preferable to potentially many. Attaching events to the dom can be expensive if you have large lists so as a rule I try to use the above where possible rather than individual handlers on each element. One article that goes deeper is http://briancrescimanno.com/2008/05/19/an-introduction-to-javascript-event-delegation/ - also google 'javascript event delegation' – redsquare Feb 14 '11 at 18:55
  • ok, so, managing events in this way it's something lighter than the classic 'dom attachment' :) ..thanks! – stecb Feb 14 '11 at 19:01
  • @steweb - It is also lighter because jquery doesnt need to do an initial lookup of all the li elements. It just looksup the container and listens at that level to see if the clicked object was an li. – redsquare Feb 14 '11 at 19:02
  • This .index method is super helpful to me. I'm using jQuery Sortable to do sorting, but it stores the semantic ranking info in the DOM. With .index(), I can pull the ranking back out of the dom really cleanly. Thanks! – SimplGy Jun 05 '13 at 00:52
  • I've updated the answer with the on() event handler with the same delegate principle usage. – Dan Atkinson Aug 19 '13 at 11:53
  • @dan atkinson hey, small world, follow you on twitter....SO falls down with versioning a bit! – redsquare Aug 19 '13 at 16:26
  • Noob question here: do that performance hint still makes sense today, or was that already abstracted out by the "compiler"? :P – cregox May 06 '15 at 23:01
  • Just found out it works differently with ES6 arrow function "() => {", the old one is more compatible, good to know – PauAI Apr 25 '22 at 18:47
45

something like:

$("ul#wizard li").click(function () {
  var index = $("ul#wizard li").index(this);
  alert("index is: " + index)
});
Mash
  • 1,339
  • 7
  • 8
11

There's no need to require a big library like jQuery to accomplish this, if you don't want to. To achieve this with built-in DOM manipulation, get a collection of the li siblings in an array, and on click, check the indexOf the clicked element in that array.

const lis = [...document.querySelectorAll('#wizard > li')];
lis.forEach((li) => {
  li.addEventListener('click', () => {
    const index = lis.indexOf(li);
    console.log(index);
  });
});
<ul id="wizard">
    <li>Step 1</li>
    <li>Step 2</li>
</ul>

Or, with event delegation:

const lis = [...document.querySelectorAll('#wizard li')];
document.querySelector('#wizard').addEventListener('click', ({ target }) => {
  // Make sure the clicked element is a <li> which is a child of wizard:
  if (!target.matches('#wizard > li')) return;
  
  const index = lis.indexOf(target);
  console.log(index);
});
<ul id="wizard">
    <li>Step 1</li>
    <li>Step 2</li>
</ul>

Or, if the child elements may change dynamically (like with a todo list), then you'll have to construct the array of lis on every click, rather than beforehand:

const wizard = document.querySelector('#wizard');
wizard.addEventListener('click', ({ target }) => {
  // Make sure the clicked element is a <li>
  if (!target.matches('li')) return;
  
  const lis = [...wizard.children];
  const index = lis.indexOf(target);
  console.log(index);
});
<ul id="wizard">
    <li>Step 1</li>
    <li>Step 2</li>
</ul>
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 2
    $(...).indexOf is not a function – Kareem Apr 08 '20 at 22:39
  • 3
    None of the code snippets on my answer produce that error - you can press "Run code snippet" to see them working. If you're getting that error, you're using different code - it sounds like you're using jQuery, but my answer is showing how to accomplish this sort of thing *without* jQuery – CertainPerformance Apr 08 '20 at 22:41
  • Fair enough. Yes, I am using JQuery. Thanks – Kareem Apr 09 '20 at 05:20
9

Take a look at this example.

$("#wizard li").click(function () {
    alert($(this).index()); // alert index of li relative to ul parent
});
Paul Sheldrake
  • 7,505
  • 10
  • 38
  • 50
Kimtho6
  • 6,154
  • 9
  • 40
  • 56
3

Delegate and Live are easy to use but if you won't have any more li:s added dynamically you could use event delagation with normal bind/click as well. There should be some performance gain using this method since the DOM won't have to be monitored for new matching elements. Haven't got any actual numbers but it makes sense :)

$("#wizard").click(function (e) {
    var source = $(e.target);
    if(source.is("li")){
        // alert index of li relative to ul parent
        alert(source.index());
    }
});

You could test it at jsFiddle: http://jsfiddle.net/jimmysv/4Sfdh/1/

jimmystormig
  • 10,672
  • 1
  • 29
  • 28
  • 1
    To what are you referring to when you say this 'DOM won't have to be monitored'? jq delegate does not monitor the dom. – redsquare Feb 14 '11 at 21:03
  • @redsquare I was under the impression that Delegate is just a more efficient variant of Live. This is from http://api.jquery.com/delegate/: "Attach a handler to one or more events for all elements that match the selector, now or in the future, based on a specific set of root elements." It must be monitoring to bind in the future? – jimmystormig Feb 14 '11 at 22:12
  • nope it just uses delegation on the container like your code above. If you insert a new li element into the container the container click event is still fired and everything still works. No monitoring required. – redsquare Feb 15 '11 at 00:50
  • @redsquare Yes, you are correct. My solution works even if there are new elements added after the DOM has loaded. Since Delegate uses Live internally (see http://stackoverflow.com/questions/2954932/difference-between-jquery-click-bind-live-delegate-trigger-functions-with-e/2954951#2954951) it still feels like this is more optimized but less convenient. But as I said, I have no numbers. – jimmystormig Feb 15 '11 at 09:48
2

Yet another way

$("#wizard li").click(function () 
{
    $($(this),'#wizard"').index();
});

Demo https://jsfiddle.net/m9xge3f5/

h0mayun
  • 3,466
  • 31
  • 40