Search code examples
jqueryjquery-selectorssiblings

Find non-sibling next element


Trying to click on the next "tag" link after the one marked "active" when I click the Next link. My sample html structure below means to my limited jQuery knowledge that .next is not going to work because the tag elements are not siblings. The end result should be that clicking on the Next link should then click the link around the word 'pizza'.

<div class="wrapper">
<p>some <a href="#" class="tag">text</a>here and more <a href="#" class="tag">text</a></p>
<p>some <a href="#" class="tag active">text</a>here</p>
<p>some <a href="#" class="tag">pizza</a>here and more <a href="#" class="tag">text</a></p>
<p>some <a href="#" class="tag">text</a>here and some more <a href="#" class="tag">text</a></p>
</div>

<div class="nav">
<a href="#" class="back">Back</a>
<a href="#" class="next">Next</a>
</div>

Something like this only works within a single paragraph

$(".next").click(function() {
    $(".active").next().click();
});

Solution

  • This is all about giving specific behaviours to the .back, .next and .tag elements.

    To keep the code organised, it's advantageous to do just about everything with event handlers including, for convenience and reusability, custom event handlers as follows :

    • a 'findPrev' event handler to find the previous tag in the set,
    • a 'findNext' event handler to find the next tag in the set.
    $(document).ready(function() {
        $(".nav .back").on('click', function(e) {
            e.preventDefault();
            if(this.href) { $(".wrapper .active").triggerHandler('findPrev').click(); }
        });
        $(".nav .next").on('click', function(e) {
            e.preventDefault();
            if(this.href) { $(".wrapper .active").triggerHandler('findNext').click(); }
        });
    
        $(".tag").on('findPrev', function() { // <<< custom event handler
            var $tags = $(this).closest('.wrapper').find('.tag');
            var index = $tags.index(this);
            return (index > 0) ? $tags.eq(index - 1) : $();
        }).on('findNext', function() { // <<< custom event handler
            var $tags = $(this).closest('.wrapper').find('.tag');
            var index = $tags.index(this);
            return (index < $tags.length) ? $tags.eq(index + 1) : $();
        }).on('click', function(e) {
            e.preventDefault();
            $(".wrapper .tag").filter(".active").removeClass('active').end().filter(this).addClass('active'); // move the 'active' highlight
            // desired click action here
        }).filter(".active").trigger('click');
    });
    

    Demo

    Once you've got your mind round that, as a bonus it's relatively trivial to add a few extra lines to enable/disable the Back and Next buttons in response to clicking the tags. This can include a couple more custom event handlers :

    • an 'enable' event handler for the Back and Next elements,
    • a 'disable' event handler for the Back and Next elements.
    $(document).ready(function() {
        $(".nav .back").on('click', function(e) {
            e.preventDefault();
            if(this.href) { $(".wrapper .active").triggerHandler('findPrev').click(); } // find previous tag and 'click' it.
        });
        $(".nav .next").on('click', function(e) {
            e.preventDefault();
            if(this.href) { $(".wrapper .active").triggerHandler('findNext').click(); } // find next tag and 'click' it.
        });
        $(".nav .back, .nav .next").on('enable', function() { // <<< custom event handler
            $(this).attr('href', '#'); // enable
        }).on('disable', function() { // <<< custom event handler
            $(this).removeAttr('href'); // disable
        });
    
        $(".tag").on('findPrev', function() { // <<< custom event handler
            var $tags = $(this).closest('.wrapper').find('.tag');
            var index = $tags.index(this);
            return (index > 0) ? $tags.eq(index - 1) : $();
        }).on('findNext', function() { // <<< custom event handler
            var $tags = $(this).closest('.wrapper').find('.tag');
            var index = $tags.index(this);
            return (index < $tags.length) ? $tags.eq(index + 1) : $();
        }).on('click', function(e) {
            e.preventDefault();
            $(".wrapper .tag").filter(".active").removeClass('active').end().filter(this).addClass('active'); // move the 'active' highlight
            $(".nav .back").trigger($(this).triggerHandler('findPrev').length ? 'enable' : 'disable'); // manage the back button
            $(".nav .next").trigger($(this).triggerHandler('findNext').length ? 'enable' : 'disable'); // manage the next button
            // desired click action here
        }).filter(".active").trigger('click'); // trigger 'click' to initialize everything
    });
    

    Demo

    Notes:

    • Use of both .trigger() and .triggerHandler() is possibly confusing. The difference lies in what is returned. .trigger() always returns jQuery (for chaining), whereas .triggerHandler() returns whatever the handler returns.
    • Things would simplify slightly with HTML <button> elements for Back and Next instead of hyperlinks. Proper buttons can be inherently disabled/enabled without all that messing about with the href attribute.
    • The custom events could alternatively be phrased as jQuery plugins, which is viable but arguably over-the-top for simple functionality.