Search code examples
javascriptjquerywrapall

jQuery wrap multiple elements independent of level


This is my first post on stackoverflow. So far I could always find an answer here, but this time I couldn't.

Here is my DOM structure:

<div id="elementA">
    <div id="elementB"></div>
    <div id="elementC">
         <div id="elementD"></div>
    </div>
    <div id="elementE"></div>
</div>

How can I wrap 2 or more selected "random" elements into a wrapper container? This should also work if the selected elements are on different level and if there are other elements in between. The DOM structure of all other elements shouldn't be effected.

Following a few examples:

Example 1:

I though of something like this:

var element1 = $('#elementB');
var element2 = $('#elementE');

??? $(element1, element2).myWrap(".wrapper"); ???

Result should look like this:

<div id="elementA">
    <div class="wrapper">
        <div id="elementB"></div>    
        <div id="elementC">
             <div id="elementD"></div>
        </div>
        <div id="elementE"></div>
    </div>
</div>

2 Example:

Elements are in different levels.

var element1 = $('#elementD');
var element2 = $('#elementE');

??? $(element1, element2).myWrap(".wrapper"); ???

Result:

<div id="elementA">
    <div id="elementB"></div>
    <div class="wrapper">
        <div id="elementC">
             <div id="elementD"></div>
        </div>
        <div id="elementE"></div>
    </div>
</div>

Example 3: More than 2 elements:

var element1 = $('#elementD');
var element2 = $('#elementC');
var element3 = $('#elementA');

??? $(element1, element2, element3).myWrap(".wrapper"); ???

<div class="wrapper">
    <div id="elementA">
        <div id="elementB"></div>    
        <div id="elementC">
             <div id="elementD"></div>
        </div>
        <div id="elementE"></div>
    </div>
</div>

Example 4: Different trees:

var element1 = $('#elementD');
var element2 = $('#elementF');

??? $(element1, element2).myWrap(".wrapper"); ???


<div id="elementA">
    <div id="elementB"></div>    
    <div class="wrapper">
        <div id="elementC">
             <div id="elementD"></div>
        </div>
        <div id="elementE">
             <div id="elementF"></div>
        </div>
    </div>
</div>

Solution

  • As was pointed out in the comments above, the first example is different to the others in that when the specified children are all direct decendants, then all children in the common parent should be wrapped.

    Using this logic, the following solution works.

    jQuery.fn.myWrap = function(options) {
        var e = this;
    
        // find most nested
        var max = null;
        var $mostNested = null;
    
        $(e).each(function(i, elem) {
            var parents = $(elem).parents().length;
            if (parents > max || max == null) {
                max = parents;
                $mostNested = $(elem);
            }
        })
    
        // find common parent
        var found = false;
        $parent = $mostNested.parent();
        while($parent != null && !found) {
            if ($parent.find(e).length == e.length) {
                // Right Level
                found = true;
                var toWrap = [];
                var numDirect = 0;
                $.each($parent.children(), function(i, item) {
                    var direct = $(e).index(item) >= 0;
                    var sibling = $(item).find(e).length > 0;
                    if (direct) numDirect++;
                    if (direct || sibling) toWrap.push(item);
                })
                if (numDirect == e.length) {
                    // All direct! (Example 1)
                    $parent.children().wrapAll("<div class='wrapper'></div>");
                } else {
                    // Other Examples
                    $(toWrap).wrapAll("<div class='wrapper'></div>");
                }
            }
            $parent = $parent.parent();    
        }
    };
    
    
    $(document).ready(function() {
        // Example 1
        $('#elementB, #elementE').myWrap();
        // Example 2
        //$('#elementD, #elementE').myWrap();
        // Example 3
        //$('#elementD, #elementC, #elementA').myWrap();
        // Example 4
        //$('#elementD, #elementF').myWrap();
    })
    

    See my fiddle.