Duncano
Duncano

Reputation: 23

Finding groups of elements and nesting them in a new element using Javascript

I'm just getting started with Javascript and am struggling to find a way to:

  1. Find one or more consecutive elements of the same type, e.g. <p>…</p> or <p>…</p><p>…</p>
  2. Surround each group with a new element, e.g. <dd><p>…</p></dd> or <dd><p>…</p><p>…</p></dd>

I'd also like to limit the script so that it applies only to a particular part of the page, e.g. <div id="relevantbit"></div>, but perhaps that counts as a separate question.

Thanks in advance for any help.

Upvotes: 2

Views: 249

Answers (2)

Sinetheta
Sinetheta

Reputation: 9449

I'm not sure how you would translate these gymnastics to native javascript, but using jQuery you could make your own method: jsFiddle

jQuery.fn.wrapConsecutive = function(desiredTagName, wrapTag) {
    $(this).find(desiredTagName).each(function(i, element){
        var $target = $(element);
        $consecutive = $target;
        if(!$target.prev().length || $target.prev().get(0).tagName!==desiredTagName){
            while($target.next().length && $target.next().get(0).tagName===desiredTagName){
                $target = $target.next();
                $consecutive = $consecutive.add($target);
            }
            $consecutive.wrapAll('<' + wrapTag + ' />');
        }
    });
}
$('#relevantbit').wrapConsecutive('P','dd');​
  1. Grab each of the element type that we are hoping to wrap in groups
  2. Check each one to see if it is the start of a new group
  3. Check if there is a next sibling, and if so whether it also matches our type
  4. Wrap the finished group

PS: Thanks for the comments, this answer has been heavily revised.

Upvotes: 1

jfriend00
jfriend00

Reputation: 707716

Here's is a native javascript function that will wrap consecutive tags at the same level in whatever type of tag you want it wrapped in. It even works recursively so it will find them to any level. As written, when detecting consecutive tags, it ignores non-elements such as text nodes or comment nodes, but it could easily be modified if that was not the desired behavior (that detail wasn't specified).

You can see a working demo here: http://jsfiddle.net/jfriend00/Bp97p/.

And here's the code:

function wrapConsecutive(parent, desiredTagName, wrapTag) {

    desiredTagName = desiredTagName.toUpperCase();
    if (typeof parent === "string") {
        parent = document.getElementById(parent);
    }

    function wrapNodes(nodeBegin, nodeEnd) {
        // create and insert the wrap node
        var wrapNode = document.createElement(wrapTag);
        nodeBegin.parentNode.insertBefore(wrapNode, nodeBegin);
        // now move the matched nodes into the wrap node
        var node = nodeBegin, next;
        while (node) {
            next = node.nextSibling;
            wrapNode.appendChild(node);
            if (node === nodeEnd) {
                break;
            }
            node = next;
        }
    }

    function wrapChildren(parent) {
        var next = parent.firstChild;
        var firstInSeries = null;
        var lastInSeries;
        while (next) {
            // only look at element nodes
            if (next.nodeType === 1) {
                wrapChildren(next);
                if (next.tagName === desiredTagName) {
                    // found a matching tagName
                    // if we don't have a series yet, start one
                    // if we do have a series, just keep going
                    if (!firstInSeries) {
                        firstInSeries = next;
                    }
                    lastInSeries = next;
                } else {
                    // did not find a matching tagName
                    // if we have a series, then end the series and process it
                    // if we didn't have a series yet, then keep looking for one
                    if (firstInSeries) {
                        // wrap from firstInSeries to next.previousSibling
                        wrapNodes(firstInSeries, lastInSeries);
                        firstInSeries = null;
                    }
                }
            }
            next = next.nextSibling;
        }
        if (firstInSeries) {
            wrapNodes(firstInSeries, lastInSeries);
        }
    }
    wrapChildren(parent);
}

Upvotes: 2

Related Questions