billyonecan
billyonecan

Reputation: 20270

Find next element with a given class, regardless of where it appears in the DOM

I'm having a really hard time trying to figure out something which I believe should be relatively simple.

I need to get the next occurrence of a class (div.content), which could appear anwhere in the DOM

Below is some simplified markup which represents what I have:

<div class="container">
  <div class="content">
    Example content #1    <a href="#" class="next">Next</a>
  </div>

  <div class="content">
    Example content #2    <a href="#" class="next">Next</a>
  </div>
</div>

<div class="container">
  <div class="content">
    Example content #1    <a href="#" class="next">Next</a>
  </div>

  <div class="content">
    Example content #2    <a href="#" class="next">Next</a>
  </div>
</div>

Showing the next content div within a given container works fine, the problem comes when I try to get from the last content div in a container to the first content div in the next container.

A couple of important points:-

The only solution I've managed to come up with so far seems extremely cumbersome, although it does work.

$('.next').click(function() {
    $theContent = $(this).parent('.content');
    $theContent.hide();

    if ($theContent.next('.content').length) {
        $theContent.next('.content').show();
    } else if ($theContent.parent('.container').next('.container')
        .children('.content').length
    ) {
        $theContent.parent('.container').next('.container')
            .children('.content:first').show();
    } else {
        // reached the end or something went wrong
    }
});

The major downside to this is that it relies on having the above DOM structure, I'd convinced myself that a method would exist for selecting the next element with a given class, regardless of where it appeared in the DOM.

Ideally I'd like a solution which doesn't rely on any given DOM structure, if that's not possible, any alternative solutions would be helpful!

Here's a fiddle with the above example

Sorry for the long-winded question!

Upvotes: 1

Views: 2343

Answers (4)

Darren Felton
Darren Felton

Reputation: 2299

jQuery(function() {
    var i = 1;
    jQuery('.content').each(function() {
        jQuery(this).addClass('content-' + i);
        i++;

        jQuery(this).find('a.next').bind('click', function() {
            toggleContentDivs(jQuery(this).parent());
        }); 
    });
});

function toggleContentDivs(oContentDiv)
{
    var nextVisible   = false,
        setVisibility = false;

    jQuery('.content').each(function() {
        if (oContentDiv.attr('class') == jQuery(this).attr('class')) {
            nextVisible = true;
            jQuery(this).hide();
        } else if (nextVisible == true) {
            jQuery(this).show();
            nextVisible   = false;
            setVisibility = true;
        } else {
            jQuery(this).hide();
        }
    });
    // it must have been the last .content element
    if (setVisibility == false) { 
        jQuery('.content:first').show();
    }
}

Only DOM dependency with the existing code, is a.next must be an immediate child of .content.

Working jsFiddle example here

Upvotes: 2

billyonecan
billyonecan

Reputation: 20270

It turns out that this is indeed a very simple task. By passing a selector to jQuery's index() method, you're able to get the index of the current element, relative to its collection:

var idx = $theContent.index('div.content') + 1;

Then the content div can be targeted directly:

$('div.content').eq(idx).show();

Upvotes: 2

Yes Barry
Yes Barry

Reputation: 9876

Turns out a simpler (less verbose) way to write it is exactly how you had it minus the function arguments:

$theContent.parent().next().length

EDIT 2:

I tried all of my solutions extensively but they were flawed. In short I think you pretty have the simplest way of doing it. Unless maybe a jQuery expert could chime in on this and show us a better way.

Here's my last proposal:

$('.next').click(function() {
    $theContent = $(this).parent('.content');
    $theContent.hide(10, function() {
        if (!$theContent.next('.content').length) {
            $theContent.parent('.container').next()
                .find('.content:first').show();
        } else {
            $theContent.next('.content').show();
        }
    });
});

The ideal solution would be to use closest(), but the problem is that it's not behaving the way I expected because of their separation by containers. In jQuery's example they use an unordered list. Well yeah, this is much easier because you can distinguish between node types (e.g. li vs ul) and simply do .next('li'). Here you are using all divs. And for some reason .closest('.content') doesn't work once the end of the first container is reached!

Upvotes: 1

greener
greener

Reputation: 5069

how about this:

if ($theContent.next('.content').length) {
   $theContent.next('.content').show();
} else if ($theContent.parent('.container').next('.container').children('.content').length) {
   $theContent.parent('.container').next('.container').children('.content:first').show();
} else {
   $('.content:first').show();
   //alert('Reached the end or something went wrong!');
}

JSFIDDLE

Upvotes: -1

Related Questions