jQuery: First (Matching) Sibling

I wanted to select the next sibling element after the current one for an event handler, using jQuery. The desired behavior is to be able to click a heading (<h3>, in this case) and locate the very next <div>, <ol>, or <ul> (with an intervening <a> tag) and toggle its visibility. It took me several tries to get this right, so figured someone else may have an issue with it.

(Spoiler: use $('h3').nextAll('div, ol, ul').first().)

HTML:

<h3><a name="notes"></a>Notes</h3> <!-- click this H3 tag ... -->
<a class="toclink" href="#top">Back to top</a>
<ul> <!-- ... this collapses/expands -->
  <li><a href="#somewhere">Somewhere</a></li>
  <li><a href="#somewhere-else">Somewhere Else</li>
</ul>

I couldn’t use $('h3').next(), because that would just get the <a> tag, and ignore everything else. Plus, passing a selector, e.g., $('...').next('div, ol, ul'), will not work: $.next() will only select the next adjacent sibling; passing a selector can at best prevent getting the next element because it’s not what you wanted, not skip over it for something better.

$('h3').nextUntil('...') got everything except what I wanted. $('h3').siblings('...') had its own set of problems – primarily selecting every <div>, <ol>, or <ul> at the same level as the clicked <h3>, which was slightly humorous, but not what I needed.

I found that I could use $('h3').nextAll('div, ol, ul').first() to get what I was looking for:

jQuery:

$(function () {
  $('h3')
    .css({cursor: 'pointer'})  // because it's clickable...
    .click(function () {
      if ($(this).data('accordionState') == 'collapsed') {
        $(this).data({accordionState: 'expanded'})
          .removeClass('collapsed').addClass('expanded')
          .nextAll('div, ol, ul').first()
          .slideDown();
      } else {
        $(this).data({accordionState: 'collapsed'})
          .removeClass('expanded').addClass('collapsed')
          .nextAll('div, ol, ul').first()
          .slideUp();
      }
    })
    .addClass('expanded');
 
  // how that named anchor actually gets added, btw
  $('a[name]')
    .parents('h3')
    .after('<a class="toclink" href="#top">Back to top</a>');
});

In case you’re curious, the pertinent styles I used for his poor man’s accordion:

.collapsed:before {  content: " > "; }
.expanded:before {  content: " v "; }
 
.toclink {
  font-size: 9px;
  margin: 0; padding: 0;
  position: relative;
  top: -50px; left: 520px;
}
.toclink + * { /* nudge up the next sibling to .toclink */
  margin-top: -0.5em;
}

I put together a bare-bones demo of the above for your playment.

I did look into the jQueryUI accordion widget, but it was overkill for what I needed. Depending upon your needs, it may actually serve you better than my home-grown solution.

About pyrolupus

Writer, father, teacher, programmer, performer. And I make some mean pancakes, too.
This entry was posted in jQuery and tagged , , , . Bookmark the permalink.

Comments are closed.