2

I've added a click event to a parent #carousel-thumbs.

carouselThumbsContainer.onclick = function(ev) {
    var target = ev.target;    // which child was actually clicked
}

<ul id="carousel-thumbs" class="l-grid">
    <li class="active"><a class="active" href="#"><img src="img/carousel-1-th.jpg" /></a></li>
    <li><a href="#"><img src="img/carousel-2-th.jpg" /></a></li>
    <li><a href="#"><img src="img/carousel-3-th.jpg" /></a></li>
    <li><a href="#"><img src="img/carousel-4-th.jpg" /></a></li>
</ul>

I would like to return the index of the clicked element relative to its parent. So if the user clicked on the second

  • I would get 1.

    /////////////////////////////////////////// Current solution which works but I was hoping to simplify:

        //Add a click event to each thumn in the thumbs container
    for (var j = 0, len = carouselThumbsContainer.children.length; j < len; j++){
        (function(index){
            carouselThumbsContainer.children[j].onclick = function(){
                  console.log(index);
            }    
        })(j);
    }
    

    Not knowing a ton about Javascript I thought there must be an easier way but perhaps not.

  • melMPLS
    • 325
    • 5
    • 14

    3 Answers3

    5

    Try this (also read: What is DOM Event delegation?):

    carouselThumbsContainer.onclick = function (e) {
        var tgt = e.target, i = 0, items;
        if (tgt === this) return;
        items = children(this);
        while (tgt.parentNode !== this) tgt = tgt.parentNode;
        while (items[i] !== tgt) i++;
        alert(i);
    };
    
    function children(el) {
        var i = 0, children = [], child;
        while (child = el.childNodes[i++]) {
            if (child.nodeType === 1) children.push(child);
        }
        return children;
    }
    

    Here is a demo:

    var hit = false,
        ul = document.getElementsByTagName('ul')[0],
        addButton = document.getElementsByTagName('a')[0],
        toggleButton = document.getElementsByTagName('a')[1],
        active = null;
    
    ul.onclick = function (e) {
        var i = 0, tgt = e.target, items;
        if (tgt === this) return;
        items = children(ul);
        while (tgt.parentNode !== this) tgt = tgt.parentNode;
        while (items[i] !== tgt) i++;
        hit = true; // for debug purposes only
        if (active) active.className = '';
        (active = tgt).className = 'active';
        output('index : ' + i);
    };
    
    addButton.onclick = function () {
        var li = document.createElement('li'),
            n = children(ul).length + 1;
        li.innerHTML = '<a href="#">item ' + n + '</a>';
        li.innerHTML += '<ul><li><a href="#">sub item</a></li></ul>';
        ul.appendChild(li);
        hit = true;
    };
    
    toggleButton.onclick = function () {
        ul.className = ul.className ? '' : 'sublists';
        hit = true;
    };
    
    document.onclick = function (e) {
        e.preventDefault();
        if (hit) hit = false;
        else output('index : none');
    };
    
    // populate the UL
    
    var i = 0;
    while (i++ < 5) addButton.onclick();
    hit = false;
    
    // helpers
    
    function children(el) {
        var i = 0, children = [], child;
        while (child = el.childNodes[i++]) {
            if (child.nodeType === 1) children.push(child);
        }
        return children;
    }
    
    function output(s) {
        document.getElementsByTagName('span')[0].innerHTML = s;
    }
    body { font-family: Arial; }
    div { width: 210px; padding-left: .5em; }
    p a { float: right; color: blue; margin-left: .5em; }
    ul { border: 1px solid black; padding: 1em 1em 1em 2.5em; }
    ul ul { display: none; }
    ul.sublists ul { display: block; }
    li a { display: block; color: inherit; text-decoration: none; }
    li a { border-right: 90px solid transparent; }
    li a:hover { border-right-color: blue; }
    li.active a { border-right-color: black; }
    li li a { border-right-width: 18px; }
    <div>
        <p>
            <a href="#" title="add a new item">add</a>
            <a href="#" title="toggle sub lists">toggle</a>
            <span><i>click any item</i></span>
        </p>
        <ul></ul>
    </div>

    The click handler line by line

    The actors

    var tgt = e.target, i = 0, items; // and `this`
    

    this is the UL itself. e.target is the DOM element that initiated the event. It can be any descendant of the UL or the UL itself (in this case e.target = this). i holds the index of the clicked item. items stands for LIs that are direct children of the UL.

    The story

    Exit the function if the target is the UL itself:

    if (tgt === this) return;
    

    Get LIs that are direct children of the UL:

    items = children(this);
    

    Bubble up through target's ancestors until reaching the uppermost LI:

    while (tgt.parentNode !== this) tgt = tgt.parentNode;
    

    Increment the index until the target matches one of the LIs:

    while (items[i] !== tgt) i++;
    

    Alert the index:

    alert(i);
    
    leaf
    • 14,210
    • 8
    • 49
    • 79
    2

    You can repurpose the array indexOf function and find the index of the target in the parent's children:

    var el = e.target;
    while (el.tagName.toLowerCase() != "li") {
        el = el.parentNode;
    }
    
    [].indexOf.call(el.parentNode.children, el);
    

    Or just use a for-loop and iterate over children.

    Demo: http://jsfiddle.net/SFke7/2/

    Dennis
    • 30,518
    • 9
    • 59
    • 75
    • Always get zero when I plugged this is: carouselThumbsContainer.onclick = function(e) { //var target = ev.target; // which child was actually clicked alert([].indexOf.call(e.target.parentNode.children, e.target)); } – melMPLS Dec 28 '13 at 20:13
    • Yeah, I realized after you posted the code. You need to traverse up the tree to get the `li`, then compare it to *its* parent. Otherwise, you get the index of the `a` element which will always be zero since it is the only element at that level. – Dennis Dec 28 '13 at 20:14
    • I'm pretty new to Javascript, if you would be kind enough to provide an example that would be awesome. – melMPLS Dec 28 '13 at 20:15
    • Just about there. How can I specify the ul of a specific ID? The fiddle is looking at the first ul instance on the page. In Jquery I would look for the ul with id, not sure how to chain that in Javascript. – melMPLS Dec 28 '13 at 20:31
    • .children only works in IE >= 9. https://developer.mozilla.org/en-US/docs/Web/API/ParentNode.children – Jacob Lauritzen Dec 28 '13 at 20:32
    • 1
      @JacobLauritzen Good to know. Should have mentioned I needed support for IE7+. I have a solution that works but was looking for something more elegant. Will post my original solution above. Thanks for all the help all. – melMPLS Dec 28 '13 at 20:36
    • @JacobLauritzen `children` works in old-IE as well, according to the link you posted. Just as long as OP doesn't include comments in the HTML. `indexOf` is an issue, but can be shimmed. – Dennis Dec 28 '13 at 20:54
    • @Dennis oh, you're right. About the `indexOf` issue I posted a work-around in my answers comments. – Jacob Lauritzen Dec 28 '13 at 20:57
    1

    Works for IE >= 9

    var index = Array.prototype.indexOf.call(this.children, ev.target);
    

    You can see it work here: http://jsfiddle.net/mBg98/

    Jacob Lauritzen
    • 1,940
    • 14
    • 15