Search code examples
javascriptjqueryjquery-selectorsthis

jQuery: "$(this).next().next()" works, but "$(this).next('.div')" does not


Okay, I am trying to get this set of information to hide individually.

This works:

$(".arrow").click(function() {
  $(this).next().next().slideToggle();
});
<img class="arrow" src="https://via.placeholder.com/40">
<H2>More Information</H2>

<div class="box">
  <h2>Bibendum Magna Lorem</h2>
  <p>Cras mattis consectetur purus sit amet fermentum.</p>
</div>

<img class="arrow" src="https://via.placeholder.com/40">
<H2>A Second Group of Information</H2>

<div class="box">
  <h2>Bibendum Magna Lorem</h2>
  <p>Cras mattis consectetur purus sit amet fermentum.</p>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>

This doesn't:

$(".arrow").click(function() {
  $(this).next('.box').slideToggle();
});
<img class="arrow" src="https://via.placeholder.com/40">
<H2>More Information</H2>

<div class="box">
  <h2>Bibendum Magna Lorem</h2>
  <p>Cras mattis consectetur purus sit amet fermentum.</p>
</div>

<img class="arrow" src="https://via.placeholder.com/40">
<H2>A Second Group of Information</H2>

<div class="box">
  <h2>Bibendum Magna Lorem</h2>
  <p>Cras mattis consectetur purus sit amet fermentum.</p>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>

What is happening that makes the second option not work? I've been at it for days and can't bloody figure it out! I appreciate your input!


Solution

  • The Problem

    If you look at the documentation for .next(selector), it does not "find" the next sibling that matches the selector. Rather, it looks at JUST the next sibling and ONLY returns that element if it matches the selector which is not what you want.

    Here's what the doc for .next() says:

    Description: Get the immediately following sibling of each element in the set of matched elements. If a selector is provided, it retrieves the next sibling only if it matches that selector.

    So, you can see that .next(".box") will look at the h2 element that immediately follows your .arrow element (that's the next sibling element) and then compare that to the .box selector and since they don't match, it will return an empty jQuery object.


    A Solution Using .nextAll()

    If you want the next sibling that matches a selector, you can use this:

    $(this).nextAll(".box").eq(0).slideToggle();
    

    This finds all the siblings that follow that match the selector and then extracts just the first one.


    Create Your Own .findNext() Method

    I've so often wondered why jQuery doesn't have a method for this that I've made one myself:

    // get the next sibling that matches the selector
    // only processes the first item in the passed in jQuery object
    // designed to return a jQuery object containing 0 or 1 DOM elements
    jQuery.fn.findNext = function(selector) {
        return this.eq(0).nextAll(selector).eq(0);
    }
    

    And, then you would just use:

    $(this).findNext(".box").slideToggle();
    

    Option: Add More Structure to the HTML to Make Things Simpler and More Flexible

    FYI, a common approach to problems like this is to put a containing div around each set of DOM elements like this:

    <div class="container">
        <img class="arrow" src="images/navigation/arrowright.png">
        <H2>More Information</H2>
        <div class="box">
                <h2>Bibendum Magna Lorem</h2>
                <p>Cras mattis consectetur purus sit amet fermentum.</p>
        </div>
    </div>
    
    <div class="container">
         <img class="arrow" src="images/navigation/arrowright.png">
         <H2>A Second Group of Information</H2>
         <div class="box">
                <h2>Bibendum Magna Lorem</h2>
                <p>Cras mattis consectetur purus sit amet fermentum.</p>
         </div>    
    </div>
    

    Then, you can use code that is a little less sensitive to exact positioning of elements:

    $(".arrow").click(function() {
        $(this).closest(".container").find(".box").slideToggle();
    });
    

    This goes up to the containing and common parent using .closest() and then uses .find() to find the .box element in that group.