114

Imagine:

<div id="old-parent">
    <span>Foo</span>
    <b>Bar</b>
    Hello World
</div>
<div id="new-parent"></div>

What JavaScript can be written to move all the child nodes (both elements, and text nodes) from old-parent to new-parent without jQuery?

I don't care about whitespace between nodes, though I expect to catch the stray Hello World, they would need to be migrated as-is too.

EDIT

To be clear, I want to end up with:

<div id="old-parent"></div>
<div id="new-parent">
    <span>Foo</span>
    <b>Bar</b>
    Hello World
</div>

The answer of the question from which this is a proposed duplicate would result in:

<div id="new-parent">
    <div id="old-parent">
        <span>Foo</span>
        <b>Bar</b>
        Hello World
    </div>
</div>
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • 3
    possible duplicate of ["Cut and Paste" - moving nodes in the DOM with Javascript](http://stackoverflow.com/questions/324303/cut-and-paste-moving-nodes-in-the-dom-with-javascript) – Marc B Jan 03 '14 at 18:18
  • 6
    @MarcB, this is not a duplicate. The question you link to does not deal with moving _all_ children, nor does any answer on that question address my need to move different types of HTML node (such as text nodes.) – Drew Noakes Jan 03 '14 at 18:24
  • 2
    the answer is exactly what you need. dom is a tree. move one node, all the children of that node move along with it. – Marc B Jan 03 '14 at 18:54
  • @MarcB sometimes you don't want the parent object in the destination. Consider for example moving all the elements of a TODO list from the 'pending' to 'complete' parent elements. – Drew Noakes May 10 '19 at 06:09

5 Answers5

152

Basically, you want to loop through each direct descendent of the old-parent node, and move it to the new parent. Any children of a direct descendent will get moved with it.

var newParent = document.getElementById('new-parent');
var oldParent = document.getElementById('old-parent');

function move() {
  while (oldParent.childNodes.length > 0) {
    newParent.appendChild(oldParent.childNodes[0]);
  }
}
#old-parent {
  background-color: red;
}

#new-parent {
  background-color: green;
}
<div id="old-parent">
  <span>Foo</span>
  <b>Bar</b> Hello World
</div>
<div id="new-parent"></div>
<br>
<button onclick="move()" id="button">Move childs</button>

External link

Teocci
  • 7,189
  • 1
  • 50
  • 48
crush
  • 16,713
  • 9
  • 59
  • 100
  • 12
    To be really efficient: `while (oldParent.childNodes.length) { newParent.appendChild(oldParent.firstChild); }` – Gibolt Jul 02 '15 at 23:14
  • 4
    Note, that childNodes[] usually contains empty text nodes, too (one for each line break in the source code). – Dercsár Aug 11 '15 at 19:32
  • 1
    @Gibolt I'm not sure that's more efficient, but it may be cleaner. I'll run a JSPerf on it and see if there is much difference. – crush Aug 13 '15 at 14:40
  • 1
    @Dercsár They aren't technically empty, then ;) – crush Aug 13 '15 at 14:43
  • 5
    @Gibolt I created a [JSPerf](http://jsperf.com/childnodes-array-access-vs-firstchild) test and it seems to indicate for me that the array access is faster than calling `firstChild`. The difference is negligible at best. It probably will vary from browser to browser as well. I think `firstChild` is more readable, though. – crush Aug 13 '15 at 14:52
  • 18
    `while (oldParent.hasChildNodes()) newParent.appendChild(oldParent.firstChild);` and `while (oldParent.firstChild) newParent.appendChild(oldParent.firstChild);` work 2x faster in Chromium and 6x to 10x faster in Firefox. [Your modified JSPerf](https://jsperf.com/childnodes-array-access-vs-firstchild/2) – user Jan 09 '16 at 09:51
  • 1
    Also worth mentioning, you really should check that `(newParent !== oldParent)`, otherwise you'll get an infinite loop – Gibolt Jul 03 '16 at 17:59
  • @Gibolt That would be impossible unless you are misusing the `id` attribute. – crush Jul 04 '16 at 15:35
  • 3
    It might be beneficial to use DocumentFragment https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment and move all elements at once if you are after performance. – Íhor Mé Mar 22 '17 at 18:41
  • When iterating the child nodes using something like `[].forEach.call(oldParent.childNodes, item => newParent.appendChild(item));` you are changing the length of the child nodes, so it causes an error. The proper solution is moving them like in the snippet of this solution. – Romel Pérez Jun 13 '18 at 16:05
  • 1
    With ```DocumentFragment``` be careful, if you use ```cloneNode```, you'll lose all events attached on nested elements. – mikiqex Jan 24 '20 at 15:23
  • First witch work. Thank you very much. I worked nearly 7 hours to transfer all child elements from a div to another div. – ru4ert Jun 17 '20 at 16:53
  • For me, @user 's comment worked although instead of ```firstChild```, I used ```firstElementChild```. – Karl Jul 01 '20 at 12:10
44

Modern way:

newParent.append(...oldParent.childNodes);
  1. .append is the replacement for .appendChild. The main difference is that it accepts multiple nodes at once and even plain strings, like .append('hello!')
  2. oldParent.childNodes is iterable so it can be spread with ... to become multiple parameters of .append()

It's supported by every browser except IE.

fregante
  • 29,050
  • 14
  • 119
  • 159
8

Here's a simple function:

function setParent(el, newParent)
{
    newParent.appendChild(el);
}

el's childNodes are the elements to be moved, newParent is the element el will be moved to, so you would execute the function like:

var l = document.getElementById('old-parent').childNodes.length;
var a = document.getElementById('old-parent');
var b = document.getElementById('new-parent');
for (var i = l; i >= 0; i--)
{
    setParent(a.childNodes[0], b);
}

Here is the Demo

Cilan
  • 13,101
  • 3
  • 34
  • 51
  • How would you use this with his code? This would move the `old-parent` element too if you passed that as `el`. – crush Jan 03 '14 at 18:19
  • 1
    That would render HTML: `
    ...
    `
    – crush Jan 03 '14 at 18:22
  • 1
    That won't work either because as you move the child, the length changes. So, you will end up only moving half of the nodes. You need to iterate backwards, or use a while loop like I did. – crush Jan 03 '14 at 18:25
  • Still won't work. You need to do: `for (var i = l; i >= 0; i--)` or `while (document.getElementById('old-parent').length > 0)`. Also, your demo has errors in it. – crush Jan 03 '14 at 18:27
  • @crush look at edit, sry - `while (document.getElementById('old-parent').length > 0) { setParent(document.getElementById('old-parent')[i], document.getElementById('new-parent')); }` – Cilan Jan 03 '14 at 18:28
  • Now you have the same answer as me, except you moved appendChild to a function, which seems pretty unnecessary (no offense). Oh wait, you have an undefined variable `i`...change that to 0, if you are going to use the `while` loop... – crush Jan 03 '14 at 18:29
  • @crush Was rushing, using for loop and a variable, seems different to me! – Cilan Jan 03 '14 at 18:30
  • That will work. Don't forget to add your `setParent` function in your demo - and update your answer. – crush Jan 03 '14 at 18:32
  • Looking in the demo, I don't see the elements having moved (using dev tools.) Also, you should avoiding looking up the elements by ID so many times. Do that once outside your loop. Also, I think that by looping in reverse, you are reversing the order of the elements. – Drew Noakes Jan 03 '14 at 18:34
  • @DrewNoakes He just needs to reference element `0` instead of using `i` for the index of the element. Also, he forgot to add his `setParent` function to his demo which is why it isn't working. I agree on the constant ID lookups. The `for` loop of this fashion is basically synonymous to the `while` loop in my example. – crush Jan 03 '14 at 18:35
  • `var l = document.getElementById('old-parent').length;` should be `var l = document.getElementById('old-parent').childNodes.length;` – crush Jan 03 '14 at 18:37
  • @crush I edited my answer with the new fiddle. Also, there's nothing in your question that says not to reverse the order of elements. – Cilan Jan 03 '14 at 18:38
  • `a[i], b` should be `a.childNodes[0], b` (use 0 to not reverse the elements) – crush Jan 03 '14 at 18:38
  • Slow down take your time =] Looks good now, and is working. Good job. +1 for fixing your mistakes. – crush Jan 03 '14 at 18:41
  • What's the point? It's better to just use the DOM API directly rather than through this wrapper. It adds nothing of value at all. – ZzZombo Sep 15 '20 at 10:27
  • @ZzZombo Unfortunately, I posted this answer six years ago and I don't remember JavaScript, or even understand the terminology you're using, so I can't tell you. Sorry – Cilan Sep 16 '20 at 23:52
0

This is shalow solution - switch nodes ids and change their sequence. If you not use - in id's names then you can do this

oldParent.id='xxx';
newParent.id='oldParent';
xxx.id='newParent';
oldParent.parentNode.insertBefore(oldParent,newParent);
#newParent { color: red }
<div id="oldParent">
    <span>Foo</span>
    <b>Bar</b>
    Hello World
</div>
<div id="newParent"></div>
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
  • This isn't moving the children, it's just **swapping** the parents in a 1999-kinda way. If you add more attributes to the oldParent HTML, this will become obvious. – fregante Mar 12 '23 at 10:29
-2

This answer only really works if you don't need to do anything other than transferring the inner code (innerHTML) from one to the other:

// Define old parent
var oldParent = document.getElementById('old-parent');

// Define new parent
var newParent = document.getElementById('new-parent');

// Basically takes the inner code of the old, and places it into the new one
newParent.innerHTML = oldParent.innerHTML;

// Delete / Clear the innerHTML / code of the old Parent
oldParent.innerHTML = '';

Hope this helps!

ItsJonQ
  • 202
  • 3
  • 3
  • 1
    This works, though I don't really want to serialise/deserialise via HTML strings. It does work though :) – Drew Noakes Jan 03 '14 at 18:19
  • 12
    It won't copy any of the objects and events attached to the elements – Gregory Magarshak Feb 26 '14 at 09:30
  • 4
    This regenerate DOM, it dont move existing nodes/events and it is probably way more slow than a loop on all children – Jordan Dec 17 '15 at 09:34
  • I ran some simple tests and this method is still slower than the accepted answer, and it has disadvantages as already explained. – lepe Aug 06 '20 at 02:55