62

What is the easiest way to swap the order of child nodes?

For example I want childNode[3] to be childNode[4] and vice-versa.

Myforwik
  • 3,438
  • 5
  • 35
  • 42

11 Answers11

101

There is no need for cloning. You can just move one node before the other. The .insertBefore() method will take it from its current location and insert it somewhere else (thus moving it):

childNode[4].parentNode.insertBefore(childNode[4], childNode[3]);

You get the parent of the node. You then call the insertBefore method on the parent and you pass it the childNode[4] node and tell it you want it inserted before childNode[3]. That will give you the result of swapping their order so 4 will be before 3 when it's done.

Reference documentation on insertBefore.

Any node that is inserted into the DOM that is already in the DOM is first removed automatically and then inserted back so there is no need to manually remove it first.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 12
    +1 I would sum it up as saying "a node can only exist in one location in the DOM at a time": thus inserting it elsewhere moves it from where it currently is. –  Mar 16 '12 at 06:28
  • how to achieve the same thing for an unknown node id ? say that it's unknown if there is childNode[3] – armen Sep 30 '14 at 09:38
  • @armen - you have to have some way of getting the two elements you want to swap. There's pretty much always a way of doing that, but how depends upon the particular HTML situation and structure. – jfriend00 Sep 30 '14 at 18:05
  • Idea: You could first get the next sibling of the later item(Item2). Insert item2 before Item1. then insert item1 before the next sibling that you stored earlier. – Chad Mx Nov 09 '15 at 22:01
  • 8
    Works only for adjecent siblings which is not the question. – br4nnigan Jun 12 '17 at 12:57
  • @brannigan - Uhhh, the question was specifically about swapping `childnode[3]` with `childnode[4]` which are certainly adjacent siblings and did apparently work just fine for the OP (since they accepted the answer). This does answer the question the OP intended. – jfriend00 Jun 12 '17 at 13:50
  • 2
    @brannigan - If you want to generically swap any two elements, you can see that code here: [Swap two HTML elements](https://stackoverflow.com/questions/10716986/swap-2-html-elements-and-preserve-event-listeners-on-them/10717422#10717422). – jfriend00 Jun 12 '17 at 13:55
  • To go one step further... the answer here is right along the lines of your answer. https://stackoverflow.com/questions/4793604/how-to-insert-an-element-after-another-element-in-javascript-without-using-a-lib referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); – JSG Nov 08 '17 at 20:23
25

Use .before or .after!

This is vanilla JS!

childNode[3].before(childNode[4]);

or

childNode[4].after(childNode[3]);

For more durability swapping, try:

function swap(node1, node2) {
    const afterNode2 = node2.nextElementSibling;
    const parent = node2.parentNode;
    node1.replaceWith(node2);
    parent.insertBefore(node1, afterNode2);
}

This should work, even if the parents don't match

Can I Use - 95% Jul '21

Gibolt
  • 42,564
  • 15
  • 187
  • 127
  • 4
    That swap function needs a special case when node1 happens to be the successor of node2: `if (node1 === afterNode2) parent.insertBefore(node1, node2); else ...` See http://jsfiddle.net/cayhorstmann/c9uqme45 – cayhorstmann Sep 25 '20 at 06:24
10

Answer by jfriend00 does not really swap elements (it "swaps" only elements which are next to each other and only under the same parent node). This is ok, since this was the question.

This example swaps elements by cloning it but regardless of their position and DOM level:

// Note: Cloned copy of element1 will be returned to get a new reference back
function exchangeElements(element1, element2)
{
    var clonedElement1 = element1.cloneNode(true);
    var clonedElement2 = element2.cloneNode(true);

    element2.parentNode.replaceChild(clonedElement1, element2);
    element1.parentNode.replaceChild(clonedElement2, element1);

    return clonedElement1;
}

Edit: Added return of new reference (if you want to keep the reference, e. g. to access attribute "parentNode" (otherwise it gets lost)). Example: e1 = exchangeElements(e1, e2);

StanE
  • 2,704
  • 29
  • 38
6

I needed a function to swap two arbitrary nodes keeping the swapped elements in the same place in the dom. For example, if a was in position 2 relative to its parent and b was in position 0 relative to its parent, b should replace position 2 of a's former parent and a should replace child 0 of b's former parent.

This is my solution which allows the swap to be in completely different parts of the dom. Note that the swap cannot be a simple three step swap. Each of the two elements need to be removed from the dom first because they may have siblings that would need updating, etc.

Solution: I put in two holder div's to hold the place of each node to keep relative sibling order. I then reinsert each of the nodes in the other's placeholder, keeping the relative position that the swapped node had before the swap. (My solution is similar to Buck's).

function swapDom(a,b) 
{
     var aParent = a.parentNode;
     var bParent = b.parentNode;

     var aHolder = document.createElement("div");
     var bHolder = document.createElement("div");

     aParent.replaceChild(aHolder,a);
     bParent.replaceChild(bHolder,b);

     aParent.replaceChild(b,aHolder);
     bParent.replaceChild(a,bHolder);    
}
AbcAeffchen
  • 14,400
  • 15
  • 47
  • 66
Jack Briner
  • 103
  • 1
  • 5
  • 1
    This should be the accepted answer as this keeps all event listeners on the DOM element – zyrup Apr 22 '23 at 18:15
3

Use a dummy sibling as a temporary position marker and then .before (or .after).
It works for any siblings (not only adjacent) and also maintains event handlers.

function swap(a, b) {
    let dummy = document.createElement("span")
    a.before(dummy)
    b.before(a)
    dummy.replaceWith(b)
}
<div id="div1">A</div>
<div id="div2">B</div>
<p> parent<div id="div3">C</div>
</p>

<button onclick="swap(div1, div3)">swap</button>

Just like temporary variables are used to swap variables, if more sophicated methods are missing.

  • Interestingly, this answer is the only one that worked with my use case ([jsfiddle](https://jsfiddle.net/plantface/x3qL7adb/1/)), where I am reversing marked-only elements in a parent. – Benny Bottema May 01 '22 at 09:42
3

Best way to do this is to create a temporary node

  function swapNodes(node1, node2) {
      const temp = document.createComment('')
      node2.replaceWith(temp)
      node1.replaceWith(node2)
      temp.replaceWith(node1)
  }
MNN TNK
  • 322
  • 2
  • 6
2

For a real Swap of any nodes without cloneNode:

    <div id="d1">D1</div>
    <div id="d2">D2</div>
    <div id="d3">D3</div>

With SwapNode function (using PrototypeJS):

function SwapNode(N1, N2)  {
N1 = $(N1);
N2 = $(N2);

if (N1 && N2) {
    var P1 = N1.parentNode;
    var T1 = document.createElement("span");    
    P1.insertBefore(T1, N1);

    var P2 = N2.parentNode;
    var T2 = document.createElement("span");
    P2.insertBefore(T2, N2);

    P1.insertBefore(N2, T1);
    P2.insertBefore(N1, T2);

    P1.removeChild(T1);
    P2.removeChild(T2);
}
}
SwapNode('d1', 'd2');
SwapNode('d2', 'd3');

Will produce:

<div id="d3">D3</div>
<div id="d1">D1</div>
<div id="d2">D2</div>
Buck
  • 29
  • 1
0

Try this method:

  1. Get the parent element
  2. Store the two elements you want to swap
  3. Store the .nextSibling of the node that is last in order eg: [1,2,3,4] => we want to swap 3 & 2 then store nextSibling of 3, '4'.

  4. .insertBefore(3,2);

  5. .insertBefore(2,nextSibling);
Chad Mx
  • 1,266
  • 14
  • 17
0

Code Explanation

  • val & val2 are the 2 nodes/elements to be swapped
  • equiv(index) gets the present node/element in DOM at index passed as the paramter

NOTE: It will count comment & text elements so take care xD

Hopes this helps :)

function equiv(index){
      return Array.prototype.slice.call( document.querySelectorAll("*"))[index];
}

function swap (val,val2){
    let _key = val.key;
    let _key_ = val2.key;
    _key_ = _key < _key_ ? _key_+1:_key_;
    let _parent_ = val2.parentElement.valueOf();
    if (val.parentElement.children.length ==1)
        val.parentElement.appendChild(val2);
    else
        val.parentElement.insertBefore(val2,val);
    if (_parent_.children.length ==0)
        _parent_.appendChild(val);
    else{
        let _sibling_ = equiv(_key_);
        _parent_.insertBefore(val,_sibling_);}
}
0

A solution that works without cloning, given the indices of the two elements to swap:

function swapChildren(parentElement, index1, index2) {

    if (index1 === index2)
        return

    if (index1 > index2) {

        const temp = index1

        index1 = index2
        index2 = temp
    }

    const { [index1]: element1, [index2]: element2 } = parentElement.childNodes

    if (index2 === index1 + 1) {
        parentElement.insertBefore(element2, element1)
    } else {

        const reference = element2.nextSibling

        parentElement.replaceChild(element2, element1)
        parentElement.insertBefore(element1, reference)
    }
}
Davide Cannizzo
  • 2,826
  • 1
  • 29
  • 31
0

You can swap a DOM element with its next sibling like that:

el.parentNode.insertBefore(el, el.nextElementSibling)

Or with its previous sibling like this:

el.parentNode.insertBefore(el, el.previousElementSibling)

And if your content is dynamic, you might want to check that el.nextElementSibling or el.previousElementSibling is not null.

clem
  • 389
  • 1
  • 5
  • 9