Looking for a jQuery function similar to wrapAll that will only wrap consecutive elements
Asked Answered
H

3

5

Is there a version of wrapAll that will only wrap consecutive elements? So this:

<p>foo</p>
<p>foo</p>
<h2>bar</h2>
<p>foo</p>

turns into this:

<div>
    <p>foo</p>
    <p>foo</p>
</div>
<h2>bar</h2>
<div>
    <p>foo</p>
</div>

When this is run?

$('p').wrapAll2('<div />')
Hensel answered 18/11, 2011 at 6:3 Comment(0)
D
6

Here's one way to do it:

$(function(){
    var cWrap=$('<div />');
    $('p').each(function(){
        var o = $(this).next('p').length;
        $(this).replaceWith(cWrap).appendTo(cWrap);
        if (!o) cWrap=$('<div />');
    });
});

or as a fully fledged plugin:

(function($){
    $.fn.wrapAll2 = function(wrapper){
        if (!this.length) return this;
        var cWrap=$(wrapper),
            tag = this.get(0).nodeName.toLowerCase();
        return this.each(function(){
            var o = $(this).next(tag).length;
            $(this).replaceWith(cWrap).appendTo(cWrap);
            if (!o) cWrap = $(wrapper);
        });
    };
})(jQuery);
$(function(){
    $('p').wrapAll2('<div />');
});

*Note that it presumes you will call it on homogeneous collections (as least all the same node type)

EDIT

Experimentally, I've looked into jQuery's innards and found that it stores the selector used to create a collection! [at least on instantiation], so with that in mind I've created another version that at least works with the current version of jQuery, and accepts any selector!

(function($) {
    $.fn.wrapAll2 = function(wrapper) {
        if (!this.length) return this;
        var wrap = $(wrapper),
            sel = this.selector || this.get(0).nodeName.toLowerCase();
        return this.each(function() {
            var more = $(this).next(sel).length;
            console.log(this,more);
            $(this).replaceWith(wrap).appendTo(wrap);
            if (!more) wrap = $(wrapper);
        });
    }
})(jQuery);
$(function() {
    $('p, span').wrapAll2('<div />');
});

It does not work with things like this though: $('p').add('span').wrapAll2('<div />');

Downright answered 18/11, 2011 at 6:41 Comment(0)
H
3

I expounded on Shad's excellent answer and created some code that would take any selector, not just node types:

(function($){
    $.wrapAll2 = function(sel, wrapSel) {
        var wrap = $(wrapSel);
        $(sel).each(function() {
            var more = $(this).next(sel).length;
            $(this).replaceWith(wrap).appendTo(wrap);
            if (!more) wrap = $(wrapSel);
        });
    };
})(jQuery);
$(function() {
    $.wrapAll2('p', '<div/>');
});

Wish it could be passed a JQuery object instead of a selector string, but this works well enough for my purposes now.

Hensel answered 18/11, 2011 at 17:53 Comment(1)
Thanks =), with that consideration in mind, I've added another version.Downright
G
1

Here is a generic solution that works for any jQuery selector, and also works when the collection has been modified, such as using add() or andSelf() or not():

Demo: http://jsfiddle.net/wujX6/

(function($){
  $.fn.wrapConsecutive = function(wrapper){
    if (!this.length) return this;
    var thisRun,prev;
    this.each(function(){
      if (!prev || $(this).prev()[0]!=prev){
        if (thisRun) thisRun.wrapAll(wrapper);
        thisRun = $();
      }
      thisRun = thisRun.add(this);
      prev = this;
    });
    if (thisRun) thisRun.wrapAll(wrapper);
    return this;
  };
})(jQuery);

Demo showing it working based on manipulated collections: http://jsfiddle.net/wujX6/1/


The only caveat is that intervening Text nodes between consecutive elements are not included in the wrapping. So, for example, calling $('p').wrapConsecutive('<div/>') on this HTML:

<p>foo</p>
<p>bar</b>
jim
<p>baz</p>
jar

… will result in this output:

<div><p>foo</p><p>bar</b><p>baz</p></div>
jim
jar
Gentlemanatarms answered 14/8, 2012 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.