jQuery find next/prev elements of a certain class but not necessarily siblings

Gnuffo1 picture Gnuffo1 · Dec 1, 2009 · Viewed 13.9k times · Source

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.

Answer

lordvlad picture lordvlad · Sep 10, 2012

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 $( selector ).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 = this; 
         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() );
}