jQuery find next/prev elements of a certain class

2020-02-09 07:15发布

问题:

The next, prev, nextAll and prevAll methods are very useful, but not if the elements you are trying to find are not in the same parent element. What I want to do is something like this:

<div>
    <span id="click">Hello</span>
</div>
<div>
    <p class="find">World></p>
</div>

When the span with the id click is pressed, I want to match the next element with the class find, which in this case is not a sibling of the clicked element so next() or nextAll() won't work.

回答1:

I was working on this problem myself today, here's what I came up with:

/**
 * Find the next element matching a certain selector. Differs from next() in
 *  that it searches outside the current element's parent.
 *  
 * @param selector The selector to search for
 * @param steps (optional) The number of steps to search, the default is 1
 * @param scope (optional) The scope to search in, the default is document wide 
 */
$.fn.findNext = function(selector, steps, scope)
{
    // Steps given? Then parse to int 
    if (steps)
    {
        steps = Math.floor(steps);
    }
    else if (steps === 0)
    {
        // Stupid case :)
        return this;
    }
    else
    {
        // Else, try the easy way
        var next = this.next(selector);
        if (next.length)
            return next;
        // Easy way failed, try the hard way :)
        steps = 1;
    }

    // Set scope to document or user-defined
    scope = (scope) ? $(scope) : $(document);

    // Find kids that match selector: used as exclusion filter
    var kids = this.find(selector);

    // Find in parent(s)
    hay = $(this);
    while(hay[0] != scope[0])
    {
        // Move up one level
        hay = hay.parent();     
        // Select all kids of parent
        //  - excluding kids of current element (next != inside),
        //  - add current element (will be added in document order)
        var rs = hay.find(selector).not(kids).add($(this));
        // Move the desired number of steps
        var id = rs.index(this) + steps;
        // Result found? then return
        if (id > -1 && id < rs.length)
            return $(rs[id]);
    }
    // Return empty result
    return $([]);
}

So in your example

<div><span id="click">hello</span></div>
<div><p class="find">world></p></div>

you could now find and manipulate the 'p' element using

$('#click').findNext('.find').html('testing 123');

I doubt it will perform well on large structures, but here it is :)



回答2:

Try this. It will mark your Element, create a set of Elements matching your selector and collect all Elements from the set following your element.

$.fn.findNext = function ( selector ) {
    var set = $( [] ), found = false;
    $( this ).attr( "findNext" , "true" );
    $( selector ).each( function( i , element ) {
        element = $( element );
        if ( found == true ) set = set.add( element )
        if ( element.attr("findNext") == "true" ) found = true;
    })
    $( this ).removeAttr( "findNext" )
    return set
}

EDIT

much simpler solution using jquerys index method. the element you call the method from needs to be selectable by the same selector though

$.fn.findNext = function( selector ){
    var set = $( selector );
    return set.eq( set.index( this, ) + 1 )
}

to free the function from this handicap, we could youse the browsers own compareDocumentposition

$.fn.findNext = function ( selector ) {
  // if the stack is empty, return the first found element
  if ( this.length < 1 ) return $(s).first();
  var found,
      that = this.get(0);
  $( selector )
    .each( function () {
       var pos = that.compareDocumentPosition( this );
       if ( pos === 4 || pos === 12 || pos === 20 ){
       // pos === 2 || 10 || 18 for previous elements 
         found = element; 
         return false;
       }    
    })
  // using pushStack, one can now go back to the previous elements like this
  // $("#someid").findNext("div").remove().end().attr("id")
  // will now return "someid" 
  return this.pushStack( [ found ] );
},  

EDIT 2 this is far easier using jQuery's $.grep. here's the new code

   $.fn.findNextAll = function( selector ){
      var that = this[ 0 ],
          selection = $( selector ).get();
      return this.pushStack(
         // if there are no elements in the original selection return everything
         !that && selection ||
         $.grep( selection, function( n ){
            return [4,12,20].indexOf( that.compareDocumentPosition( n ) ) > -1
         // if you are looking for previous elements it should be [2,10,18]
         })
      );
   }
   $.fn.findNext = function( selector ){
      return this.pushStack( this.findNextAll( selector ).first() );
   }

when compressing variable names this becomes a mere two liner.

Edit 3 using bitwise operations, this function may be even faster?

$.fn.findNextAll = function( selector ){
  var that = this[ 0 ],
    selection = $( selector ).get();
  return this.pushStack(
    !that && selection || $.grep( selection, function(n){
       return that.compareDocumentPosition(n) & (1<<2);
       // if you are looking for previous elements it should be & (1<<1);
    })
  );
}
$.fn.findNext = function( selector ){
  return this.pushStack( this.findNextAll( selector ).first() );
}


回答3:

My solution would involve adjusting your markup a bit to make the jQuery much easier. If this is not possible or not an appealing answer, please ignore!

I would wrap a 'parent' wrapper around what you want to do...

<div class="find-wrapper">
    <div><span id="click">hello</span></div>
    <div><p class="find">world></p></div>
</div>

Now, to find the find:

$(function() {
    $('#click').click(function() {
        var $target = $(this).closest('.find-wrapper').find('.find');
        // do something with $target...
    });
});

This gives you the flexibility to have whatever kind of markup and hierarchy you'd like inside the wrapper I suggested, and still reliably find your target.

Good luck!



回答4:

I think the only way to solve this problem is to do a recursive search over the elements after the current element. There is no simple solution to this problem provided by jQuery. If you only want to find elements in the siblings of your parent element (as is the case in your example), it is not required to do a recursive search, but you have to do multiple searches.

I created an example (actually, it isn't recursive) which does what you want (I hope). It selects all elements after the current clicked element and makes them red:

<script type="text/javascript" charset="utf-8">
    $(function () {
        $('#click').click(function() {
            var parent = $(this);
            alert(parent);
            do {
                $(parent).find('.find').css('background-color','red'); 
                parent = $(parent).parent();
            } while(parent !== false);
        });
    });
</script>


回答5:

The following expression should (barring syntax errors!) find all siblings of the parent that contain a p.find element and then find those p.find elements and change their colour to blue.

$(this).parent().nextAll(":has(p.find)").find(".find").css('background-color','blue');

Of course, if your page structure is such that p.find occurs in a totally different level of hierarchy (sibling of a grandparent for example), it won't work.