99

I want to wrap all the nodes within the #slidesContainer div with JavaScript. I know it is easily done in jQuery, but I am interested in knowing how to do it with pure JS.

Here is the code:

<div id="slidesContainer">
    <div class="slide">slide 1</div>
    <div class="slide">slide 2</div>
    <div class="slide">slide 3</div>
    <div class="slide">slide 4</div>
</div>

I want to wrap the divs with a class of "slide" collectively within another div with id="slideInner".

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
Squadrons
  • 2,467
  • 5
  • 25
  • 36

13 Answers13

97

If your "slide"s are always in slidesContainer you could do this

org_html = document.getElementById("slidesContainer").innerHTML;
new_html = "<div id='slidesInner'>" + org_html + "</div>";
document.getElementById("slidesContainer").innerHTML = new_html;
Michal
  • 13,439
  • 3
  • 35
  • 33
  • 5
    @Squadrons: Are you also using jQuery? If so, you really shouldn't do this. (Even if you're not, you're destroying and recreating this part of the DOM unnecessarily.) – user113716 Jul 27 '11 at 00:38
  • 1
    I am not using jquery. I am trying to get somewhat comfortable with JS before I move to a framework. I am trying out all of the answers in this thread, however. Do you recommend aroth's answer? If so, why over this one? – Squadrons Jul 27 '11 at 02:13
  • 1
    My answer is a bit slower than operating on a node level, however unless you have a lot of nodes(slides) the difference should be imperceptible. If you are using jquery/or have any events wired to the slides you would need to rescan the dom. However this method will work in most browsers without the need to deal with getElementsByClass name incompatibilities. So for simple functionality (and not too complex DOM) this is probably better....Important to say that if you were generating the slides dynamically it is faster to use innerHtml rather than build DOM tree node by node. – Michal Jul 27 '11 at 04:02
  • 27
    Note that replacing innerHTML will remove events from all contained nodes. –  Dec 11 '16 at 15:05
  • 6
    This will nuke any event handlers or what have you on the elements, and is rather slow. I'd prefer @user113716's answer – Ansikt Jan 22 '18 at 17:39
  • 1
    `target.outerHTML = \`
    ${target.outerHTML}
    \` `
    – MNN TNK Jun 09 '20 at 11:22
  • how to do that in `jsx` or react in genral. I mean without adding HTML like a text – Ali Husham Apr 22 '21 at 11:15
  • Worked better for me when using outerHTML – Cybernetic Feb 19 '22 at 00:12
  • This also removes all variable references to the elements, if any exist – Zach Saucier May 10 '23 at 14:01
49

Like BosWorth99, I also like to manipulate the dom elements directly, this helps maintain all of the node's attributes. However, I wanted to maintain the position of the element in the dom and not just append the end incase there were siblings. Here is what I did.

var wrap = function (toWrap, wrapper) {
    wrapper = wrapper || document.createElement('div');
    toWrap.parentNode.insertBefore(wrapper, toWrap);
    return wrapper.appendChild(toWrap);
};
Eliezer Berlin
  • 3,170
  • 1
  • 14
  • 27
user1767210
  • 603
  • 6
  • 5
  • 2
    I'm sure this is a poor question, I'm not as familiar with javascript/query as say CSS, but why would you set this as a variable instead of assigning it as a function? e.g. `function wrap(toWrap, wrapper) {`? – darcher Oct 06 '15 at 18:18
  • I generally function expressions instead of declarations for consistent coding style and to simplify hoisting behavior. https://javascriptweblog.wordpress.com/2010/07/06/function-declarations-vs-function-expressions/ – Phillip Chan Jun 06 '16 at 19:34
  • 2
    This does not answer the question. The OP is wanting to wrap all children not a single element. – Web_Designer Dec 30 '16 at 04:37
  • 13
    If `toWrap` is not the last child, the `wrapper` ends up as the wrong child (it does end up as last child). Better to use `insertBefore()` e.g. replace `toWrap.parentNode.appendChild(wrapper);` with `toWrap.parentNode.insertBefore(wrapper, toWrap);` – MoonLite Nov 05 '21 at 19:26
49

How to "wrap content" and "preserve bound events"?

// element that will be wrapped
var el = document.querySelector('div.wrap_me');

// create wrapper container
var wrapper = document.createElement('div');

// insert wrapper before el in the DOM tree
el.parentNode.insertBefore(wrapper, el);

// move el into wrapper
wrapper.appendChild(el);

or

function wrap(el, wrapper) {
    el.parentNode.insertBefore(wrapper, el);
    wrapper.appendChild(el);
}

// call function
// wrapping element "wrap_me" into a new element "wrapper"
wrap(document.querySelector('div.wrap_me'), document.createElement('div'));

ref

https://plainjs.com/javascript/manipulation/wrap-an-html-structure-around-an-element-28

Bruno
  • 6,623
  • 5
  • 41
  • 47
  • 1
    This is basically the same answer as the one by user1767210 in 2013: https://stackoverflow.com/a/18453767/339803 but maybe a little clearer. – redfox05 Nov 10 '21 at 12:30
  • @redfox05 sure because it's the common pattern - see ref link – Bruno Nov 11 '21 at 21:42
16

A general good tip for trying to do something you'd normally do with jQuery, without jQuery, is to look at the jQuery source. What do they do? Well, they grab all the children, append them to a a new node, then append that node inside the parent.

Here's a simple little method to do precisely that:

const wrapAll = (target, wrapper = document.createElement('div')) => {
  ;[ ...target.childNodes ].forEach(child => wrapper.appendChild(child))
  target.appendChild(wrapper)
  return wrapper
}

And here's how you use it:

// wraps everything in a div named 'wrapper'
const wrapper = wrapAll(document.body) 

// wraps all the children of #some-list in a new ul tag
const newList = wrapAll(document.getElementById('some-list'), document.createElement('ul'))
Ansikt
  • 1,442
  • 11
  • 13
  • I like how the second parameter is optional. You can provide an element, or it creates a div for you. This works great, thanks! – Justin Breen Mar 07 '20 at 17:27
  • 1
    You can now use `parentNode.append` instead of `appendChild`. E.g. `wrapper.append(...target.childNodes)` – Azmisov Jan 28 '21 at 19:53
  • What's the semicolon doing in the first code snippet's second line at the first letter? –  Aug 11 '22 at 16:31
  • 1
    @CodemasterUnited, the short version is: you don't need it. The long version is: you can write JavaScript without semi-colons, with a couple exceptions where the interpreter can get confused. One of those exceptions is if a line starts with an array literal, as it can be seen as indexing an element from the prior line. That wouldn't be a problem here, as that's the first line of the function, but I was using a linter at the time which enforced that rule. – Ansikt Aug 12 '22 at 18:05
16

If you patch up document.getElementsByClassName for IE, you can do something like:

var addedToDocument = false;
var wrapper = document.createElement("div");
wrapper.id = "slideInner";
var nodesToWrap = document.getElementsByClassName("slide");
for (var index = 0; index < nodesToWrap.length; index++) {
    var node = nodesToWrap[index];
    if (! addedToDocument) {
        node.parentNode.insertBefore(wrapper, node);
        addedToDocument = true;
    }
    node.parentNode.removeChild(node);
    wrapper.appendChild(node);
}

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

aroth
  • 54,026
  • 20
  • 135
  • 176
  • @aroth: Yep, works now. You can simplify a bit if you just manage adding the wrapper outside the loop. Also, remember that a DOM node can only be in one place at a time, so you really don't need to do a `removeChild` then an `appendChild`. Just do the `appendChild`, and it will remove it from its current location. http://jsfiddle.net/GkEVm/3/ ;o) – user113716 Jul 27 '11 at 00:46
  • @patrick - Much cleaner, although won't the modified version error out if no elements are matched by the `getElementsByclassName? – aroth Jul 27 '11 at 01:30
  • @aroth: Great point. My way would need to add a `if( nodesToWrap[0] )`. – user113716 Jul 27 '11 at 01:55
  • Would someone mind explaining the var addedToDocument = false? – Squadrons Jul 27 '11 at 02:49
  • 1
    @Squadrons - The `addedToDocument` variable is just a flag that says whether or not the wrapper element has been added to the DOM. The reason for it is that 1) the insertion position of the wrapper div depends upon the matched nodes and that 2) there may be no matched nodes, in which case the wrapper should not be added to the DOM at all and that 3) when there are multiple nodes matched the wrapper should only be added to the DOM once, in front of the first match. The flag just ensures these conditions are all met. – aroth Jul 27 '11 at 04:31
14

I like to manipulate dom elements directly - createElement, appendChild, removeChild etc. as opposed to the injection of strings as element.innerHTML. That strategy does work, but I think the native browser methods are more direct. Additionally, they returns a new node's value, saving you from another unnecessary getElementById call.

This is really simple, and would need to be attached to some type of event to make any use of.

wrap();
function wrap() {
    var newDiv = document.createElement('div');
    newDiv.setAttribute("id", "slideInner");
    document.getElementById('wrapper').appendChild(newDiv);
    newDiv.appendChild(document.getElementById('slides'));
}

jsFiddle

Maybe that helps your understanding of this issue with vanilla js.

Bosworth99
  • 4,206
  • 5
  • 37
  • 52
  • Why does this need to be attached? I tried running it by attaching to a function named prepareSlideshow(), and running it on load, but can't seem to be able to alter the DOM to have it display the required node – Squadrons Jul 27 '11 at 02:01
  • Attached - just so you have control over it, like the onLoad event your calling. I mean - you could just call it too, I just assumed there was a state in which the slides weren't wrapped, and you needed to do alter your element list during runtime. Just run in on any function, and as long as your element id names line up it should work... – Bosworth99 Jul 27 '11 at 03:04
13

To simply wrap a div without the need of the parent:

<div id="original">ORIGINAL</div>

<script>

document.getElementById('original').outerHTML
=
'<div id="wrap">'+
document.getElementById('original').outerHTML
+'</div>'

</script>

Working Example: https://jsfiddle.net/0v5eLo29/

More Practical Way:

const origEle = document.getElementById('original');
origEle.outerHTML = '<div id="wrap">' + origEle.outerHTML + '</div>';

Or by using only nodes:

let original = document.getElementById('original');
let wrapper = document.createElement('div');
wrapper.classList.add('wrapper');
wrapper.append(original.cloneNode(true));

original.replaceWith(wrapper);

Working Example: https://jsfiddle.net/wfhqak2t/

proseosoc
  • 1,168
  • 14
  • 23
4

A simple way to do this would be:

let el = document.getElementById('slidesContainer');
el.innerHTML = `<div id='slideInner'>${el.innerHTML}</div>`;
Ershad Qaderi
  • 441
  • 4
  • 11
1

Note - below answers the title of the question but is not specific to the OP's requirements (which are over a decade old)

Using the range API is making wrapping easy, by creating a Range which selects only the node wished to be wrapped, and then use the surroundContents API to wrap it.

Below code wraps the first (text) node with a <mark> element and the last node with a <u> element:

const wrapNode = (nodeToWrap, wrapWith) => {
  const range = document.createRange();
  range.selectNode(nodeToWrap);
  range.surroundContents(wrapWith);
}

wrapNode(document.querySelector('p').firstChild, document.createElement('mark'))
wrapNode(document.querySelector('p').lastChild, document.createElement('u'))
<p>
  first node
  <span>second node</span>
  third node
</p>
vsync
  • 118,978
  • 58
  • 307
  • 400
0

From what I understand @Michal 's answer is vulnerable to XXS attacks (using innerHTML is a security vulnerability) Here is another link on this.

There are many ways to do this, one that I found and liked is:

function wrap_single(el, wrapper) {
    el.parentNode.insertBefore(wrapper, el);
    wrapper.appendChild(el);
}
let divWrapper;
let elementToWrap;
elementToWrap = document.querySelector('selector');
// wrapping the event form in a row
divWrapper = document.createElement('div');
divWrapper.className = 'row';
wrap_single(elementToWrap, divWrapper);

This works well. However for me, I sometimes want to just wrap parts of an element. So I modified the function to this:

function wrap_some_children(el, wrapper, counter) {
  el.parentNode.insertBefore(wrapper, el);
  if ( ! counter ) {
    counter = el.childNodes.length;
  }
  for(i = 0; i < counter;  i++) {
    wrapper.appendChild( el.childNodes[0] );
  }
}
// wrapping parts of the event form into columns
let divCol1;
let divCol2;
// the elements to wrap
elementToWrap = document.querySelector('selector');
// creating elements to wrap with
divCol1 = document.createElement('div');
divCol1.className = 'col-sm-6';
divCol2 = document.createElement('div');
divCol2.className = 'col-sm-6';
// for the first column
wrap_some_children(elementToWrap, divCol1, 13);  // only wraps the first 13 child nodes
// for the second column
wrap_some_children(elementToWrap, divCol2);

I hope this helps.

Will
  • 169
  • 1
  • 6
0

wrapInner multiple tag content


function wilWrapInner(el, wrapInner) {
  var _el = [].slice.call(el.children);
  var fragment = document.createDocumentFragment();
  el.insertAdjacentHTML('afterbegin', wrapInner);
  var _wrap = el.children[0];
  for (var i = 0, len = _el.length; i < len; i++) {
    fragment.appendChild(_el[i]);
  }
  _wrap.appendChild(fragment);
}

Link Demo Jsbin

viet nam
  • 1
  • 2
0
var x = document.getElementById("myDIV");
   const container = document.createElement('div');
   container.setAttribute('class', 'slidesContainer');
   const children = document.querySelectorAll('.slide');
   children.forEach(function(child) {
       container.appendChild(child);
       x.appendChild(container);
    });
Arshit
  • 118
  • 2
  • 7
  • Please read [answer] and [edit] your answer to contain an explanation as to why this code would actually solve the problem at hand. Always remember that you're not only solving the problem, but are also educating the OP and any future readers of this post. – Adriaan Aug 16 '23 at 09:02
0

Wrapping all the children in one element

const wrapper = document.createElement("div");
wrapper.classList.add("wrapper");
document.querySelector('#slidesContainer').appendChild(wrapper);

// select all the elements we want to wrap
const slides = document.querySelectorAll(".slide");

// loop over the elements
slides.forEach((item, index) => {

  // add the element to the wrapper
  wrapper.appendChild(item);
});
.wrapper {
  border: 1px solid black;
}
<div id="slidesContainer">
  <div class="slide">slide 1</div>
  <div class="slide">slide 2</div>
  <div class="slide">slide 3</div>
  <div class="slide">slide 4</div>
</div>

Wrapping Each one individually

Using querySelectorAll, forEach, createElement, and before

// select all the elements we want to wrap
const slides = document.querySelectorAll(".slide");

// loop over the elements
slides.forEach((item, index) => {
  const wrapper = document.createElement("div");
  wrapper.classList.add("wrapper");
  item.before(wrapper);

  // add the element to the wrapper
  wrapper.appendChild(item);
});
.wrapper {
  border: 1px solid black;
}
<div id="slidesContainer">
  <div class="slide">slide 1</div>
  <div class="slide">slide 2</div>
  <div class="slide">slide 3</div>
  <div class="slide">slide 4</div>
</div>

Wrapping In groups of 2

If you want to wrap multiple items in a wrapper, you can use a reduce loop with a mod to determine if you need a new wrapper element.

// convert html collection into an array
const slides = [...document.querySelectorAll(".slide")];

slides.reduce((wrapper, item, index) => {
  // Every 2 so if we are at a start create a new wrapper element
  // And place it before the html element we want to move
  if (index % 2 === 0) {
    wrapper = document.createElement("div");
    wrapper.classList.add("wrapper");
    item.before(wrapper);
  }
  
  // add the element to the wrapper
  wrapper.appendChild(item);
  
  // keep track of the wrapper
  return wrapper;
}, null);
.wrapper {
  border: 1px solid black;
}
<div id="slidesContainer">
    <div class="slide">slide 1a</div>
    <div class="slide">slide 1b</div>
    <div class="slide">slide 2a</div>
    <div class="slide">slide 2b</div>
    <div class="slide">slide 3a</div>
    <div class="slide">slide 3b</div>
    <div class="slide">slide 4a</div>
    <div class="slide">slide 4b</div>
</div>
epascarello
  • 204,599
  • 20
  • 195
  • 236