简体   繁体   中英

Detecting “runs” of DOM elements in jQuery

What is the best way to detect runs of dom elements in jQuery?

For instance, if I have the following list of items

<ol>
  <li class="a"></li>
  <li class="foo"></li>
  <li class="a"></li>
  <li class="a"></li>
  <li class="foo"></li>
  <li class="foo"></li>
  <li class="foo"></li>
  <li class="a"></li>
  <li class="foo"></li>
  <li class="foo"></li>
  <li class="foo"></li>
</ol>

Say I want to grab all the li.foo elements and wrap them inside their own <ol> block (or any wrapper for that matter) to end up with something like.

<ol>
  <li class="a"></li>
  <li class="foo"></li>
  <li class="a"></li>
  <li class="a"></li>
  <li><ol>
    <li class="foo"></li>
    <li class="foo"></li>
    <li class="foo"></li>
  </ol></li>
  <li class="a"></li>
  <li><ol>
    <li class="foo"></li>
    <li class="foo"></li>
    <li class="foo"></li>
  </ol></li>
</ol>

As you can see from the example, I only want to wrap "runs" of li.foo dom elements (where there are 2 or more li.foo elements in succession.

I'm not sure of the best/most efficient way to accomplish this via jQuery (or just plain javascript for that matter).

Example: http://jsfiddle.net/kNfxs/1/

$('ol .foo').each(function() {
    var $th = $(this);
    var nextUn = $th.nextUntil(':not(.foo)');
    if(!$th.prev('.foo').length && nextUn.length)
        nextUn.andSelf().wrapAll('<li><ol></ol></li>');
});

Loop over the .foo elements, and if the previous element is not .foo , and it has at least 1 .foo after it, then grab all the next .foo elements using the nextUntil() (docs) method and include the original using the andSelf() (docs) method, then wrap them using the wrapAll() (docs) method.


Update: This will be a little more efficient because it avoids the nextUntil() (docs) method when there's a previous .foo() .

Example: http://jsfiddle.net/kNfxs/2/

$('ol .foo').each(function() {
    var $th = $(this);
    if($th.prev('.foo').length) return; // return if we're not first in the group
    var nextUn = $th.nextUntil(':not(.foo)');
    if( nextUn.length)
        nextUn.andSelf().wrapAll('<li><ol></ol></li>');
});

Use something like this:

$(li).each(function(){
if($(this).next().hasClass('foo')){
---- Recursively check until the end of the run has been found ----
}
});

To recursively check write a function that checks the next element until the end of the run has been found.

Not sure how well this works for performance:

var $foo = $('.foo + .foo');
$foo.add($foo.prev());

$foo will be the set of " .foo runs"

Edit to add:

I thought of a simpler way:

var $foo = $('.foo + .foo').prev('.foo').andSelf();

Working example: http://jsfiddle.net/v8GY8/

For posterity:

// Takes a jQuery collection and returns an array of arrays of contiguous elems
function groupContiguousElements( $elems ){
  var groups = [];
  var current, lastElement;
  $elems.each(function(){
    if ($(this).prev()[0] == lastElement ){
      if (!current) groups.push( current=[lastElement] );
      current.push( this );
    }else{
      current = null;
    }
    lastElement = this;
  });
  return groups;
}

var groups = groupContiguousElements( $('li.foo') );
$.each( groups, function(){
  var wrapper = $('<ol>').insertBefore(this[0]);
  $.each(this,function(){
    wrapper.append(this);
  });
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM