JQuery найти следующие / пред элементы элементов определенного класса, но не обязательно братьев и сестер - PullRequest
16 голосов
/ 01 декабря 2009

Методы next, prev, nextAll и prevAll очень полезны, но не в том случае, если элементы, которые вы пытаетесь найти, не находятся в одном родительском элементе. Я хочу сделать что-то вроде этого:

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

При нажатии диапазона с идентификатором click я хочу сопоставить следующий элемент с классом find, который в данном случае не является родственным элементом элемента, по которому щелкнули, поэтому next() или nextAll() не будет работать.

Ответы [ 5 ]

13 голосов
/ 10 сентября 2012

Попробуй это. Он пометит ваш элемент, создаст набор элементов, соответствующий вашему селектору, и соберет все элементы из набора, следующего за вашим элементом.

$.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

гораздо более простое решение с использованием метода индекса jquerys. элемент, из которого вы вызываете метод, должен выбираться тем же селектором, хотя

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

чтобы освободить функцию от этого гандикапа, мы могли бы использовать собственные браузеры 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 ] );
},  

РЕДАКТИРОВАТЬ 2 это намного проще, используя jQuery $ .grep. вот новый код

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

при сжатии имен переменных это становится просто двумя строками.

Редактировать 3 используя побитовые операции, эта функция может быть еще быстрее?

$.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() );
}
8 голосов
/ 08 ноября 2010

Я сам работал над этой проблемой сегодня, вот что я придумал:

/**
 * 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 $([]);
}

Так в вашем примере

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

теперь вы можете найти и манипулировать элементом 'p', используя

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

Я сомневаюсь, что это будет хорошо работать на больших конструкциях, но вот оно:)

3 голосов
/ 03 декабря 2009

Мое решение заключается в том, чтобы немного подправить разметку, чтобы сделать jQuery намного проще. Если это невозможно или не является привлекательным ответом, пожалуйста, игнорируйте!

Я бы обернул 'родительскую' оболочку вокруг того, что вы хотите сделать ...

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

Теперь, чтобы найти find:

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

Это дает вам гибкость, чтобы иметь любую разметку и иерархию, которые вы хотели бы использовать в предложенной мной оболочке, и при этом надежно находить цель.

Удачи!

0 голосов
/ 01 декабря 2009

Следующее выражение должно (исключая синтаксические ошибки!) Найти всех братьев и сестер родителя, которые содержат элемент p.find, а затем найти эти элементы p.find и изменить их цвет на синий.

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

Конечно, если структура вашей страницы такова, что p.find находится на совершенно другом уровне иерархии (например, брат или дедушка), это не сработает.

0 голосов
/ 01 декабря 2009

Я думаю, что единственный способ решить эту проблему - выполнить рекурсивный поиск по элементам после текущего элемента. Простого решения этой проблемы, предоставляемого jQuery, не существует. Если вы хотите найти элементы только в элементах одного и того же элемента вашего родительского элемента (как в вашем примере), нет необходимости выполнять рекурсивный поиск, но вам нужно выполнить несколько запросов.

Я создал пример (на самом деле, он не рекурсивный), который делает то, что вы хотите (я надеюсь). Он выбирает все элементы после текущего выбранного элемента и делает их красными:

<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>
...