187

Can I easily swap two elements with jQuery?

I'm looking to do this with one line if possible.

I have a select element and I have two buttons to move up or down the options, and I already have the selected and the destination selectors in place, I do it with an if, but I was wondering if there is an easier way.

juan
  • 80,295
  • 52
  • 162
  • 195

22 Answers22

254

Here's an interesting way to solve this using only jQuery (if the 2 elements are next to each other):

$("#element1").before($("#element2"));

or

$("#element1").after($("#element2"));
ashleedawg
  • 20,365
  • 9
  • 72
  • 105
lotif
  • 3,401
  • 2
  • 19
  • 18
  • 2
    Yeah, this should work: The documentation says: "If an element selected this way is inserted elsewhere, it will be moved before the target (not cloned)". – Ridcully Jan 11 '12 at 15:55
  • 15
    I like this option the best! Simple and nicely compatible. Allthough i like to use el1.insertBefore(el2) and el1.insertAfter(el2) for readability. – Maurice Feb 01 '12 at 13:43
  • 6
    One sidenote though.. (which I guess applies to all solutions on this page) since we're manipulating the DOM here. Removing and adding does not work well when there is an iframe present within the element you are moving. The iframe will reload. Also it will reload to it's original url instead of the current one. Very annoying but security-related. I have not found a solution for this – Maurice Feb 01 '12 at 15:54
  • 214
    this doesn't swap two elements unless the two elements are immediately next to each other. – BonyT Jul 31 '12 at 10:16
  • 3
    This only swaps the elements if they are beside each other, otherwise Paolo's answer is the correct one. – Elmer Oct 02 '13 at 01:59
  • 13
    This should not be the accepted answer any longer. It does not work in the generalized case. – thekingoftruth Jul 23 '14 at 19:13
  • This seems to remove the first element when you do it? – Nathan Dec 03 '15 at 18:05
  • Well this answer gives a root for a more general solution - http://stackoverflow.com/a/37040255/1835470 – jave.web May 05 '16 at 00:19
  • It is worked for me, I use it like this: `$('a').eq(0).before($('a').eq(1));` – Umut Çağdaş Coşkun Jul 11 '18 at 07:24
  • @BonyT Exactly what the OP was looking for (as specified in the last part of the question)... – Shockwaver Jun 08 '21 at 14:17
83

Paulo's right, but I'm not sure why he's cloning the elements concerned. This isn't really necessary and will lose any references or event listeners associated with the elements and their descendants.

Here's a non-cloning version using plain DOM methods (since jQuery doesn't really have any special functions to make this particular operation easier):

function swapNodes(a, b) {
    var aparent = a.parentNode;
    var asibling = a.nextSibling === b ? a : a.nextSibling;
    b.parentNode.insertBefore(a, b);
    aparent.insertBefore(b, asibling);
}
David Mulder
  • 26,123
  • 9
  • 51
  • 114
bobince
  • 528,062
  • 107
  • 651
  • 834
  • 2
    http://docs.jquery.com/Clone - passing it "true" clones the events too. I tried without cloning it first but it was doing what yours currently is: it's swapping the first one with the 2nd one and leaving the first one as is. – Paolo Bergantino Mar 30 '09 at 18:35
  • if i have
    1
    2
    and call swapNodes(document.getElementById('div1'), document.getElementById('div2')); i get
    1
    1
    – Paolo Bergantino Mar 30 '09 at 18:44
  • 3
    Ah, there's a corner case where a's next sibling is b itself. Fixed in the above snippet. jQuery was doing the exact same thing. – bobince Mar 30 '09 at 18:54
  • I have a very order-sensitive list that needs to retain events and IDs and this works like a charm! Thanks! – Teekin May 27 '11 at 14:46
  • 2
    I feel as though you should add a jQuery version to technically satisfy this question's title. :) – thekingoftruth Jul 23 '14 at 19:11
  • Compile fail. "b.parentNode is undefined" – HoldOffHunger May 24 '16 at 18:20
  • This works well, but only if you pass in the jQuery element index as well. E.g. `swapNodes($('#myDiv')[0], $('#myDiv2')[0])`. – user1779775 Oct 31 '18 at 15:28
  • Thanks @bobince, this works well, but how to swapping more than 3 elements is that possible too? – maranR Oct 02 '22 at 19:18
  • @David Mulder, this works well, but how to swapping more than 3 elements is that possible too? – maranR Oct 02 '22 at 19:25
72

No, there isn't, but you could whip one up:

jQuery.fn.swapWith = function(to) {
    return this.each(function() {
        var copy_to = $(to).clone(true);
        var copy_from = $(this).clone(true);
        $(to).replaceWith(copy_from);
        $(this).replaceWith(copy_to);
    });
};

Usage:

$(selector1).swapWith(selector2);

Note this only works if the selectors only match 1 element each, otherwise it could give weird results.

Paolo Bergantino
  • 480,997
  • 81
  • 517
  • 436
50

There are a lot of edge cases to this problem, which are not handled by the accepted answer or bobince's answer. Other solutions that involve cloning are on the right track, but cloning is expensive and unnecessary. We're tempted to clone, because of the age-old problem of how to swap two variables, in which one of the steps is to assign one of the variables to a temporary variable. The assignment, (cloning), in this case is not needed. Here is a jQuery-based solution:

function swap(a, b) {
    a = $(a); b = $(b);
    var tmp = $('<span>').hide();
    a.before(tmp);
    b.before(a);
    tmp.replaceWith(b);
};
user2820356
  • 501
  • 4
  • 2
  • 10
    This should be the accepted answer. Cloning removes any event triggers and is not necessary. I make use of this exact method in my code. – thekingoftruth Jul 23 '14 at 19:07
  • 3
    This is the better answer! I happend to have a ul as a child of my swapped elements, which was a jquery sortable element. After swapping with Paolo Bergantino's answer the list items couldn't be dropped anymore. With user2820356's answer everything still worked fine. – agoldev Nov 24 '15 at 21:45
27

The jQuery .before method can be used to swap elements by adding a temporary 3rd element as a bookmark - make a temporary DOM element as a placeholder while you move things around. .

$.fn.swapWith = function(that) {
  var $this = this;
  var $that = $(that);
  
  // create temporary placeholder
  var $temp = $("<div>");
  
  // 3-step swap
  $this.before($temp);
  $that.before($this);
  $temp.before($that).remove();
        
  return $this;
}
  1. put the temporary div temp before this

  2. move this before that

  3. move that before temp

3b) remove temp

Then use it like this

$(selectorA).swapWith(selectorB);

DEMO: https://jsfiddle.net/7t1hz94y/

robisrob
  • 940
  • 12
  • 23
  • 4
    This is the best answer, all others asume the elements are next to each other. This works in every situation. – brohr Jan 23 '18 at 15:40
  • thanks, this solution applyes for all the cases and also is explained with detail and simple, it may be marked as the correct answer as well. This https://stackoverflow.com/a/61212946/903998 is similiar i guess but less explained and little more complex. – Victor Oct 15 '20 at 15:55
  • 1
    @robisrob and for other readers may notice that `3) move that after temp` could be as well `3) move that before temp`, as temp will be removed after being used as an anchor point. – Victor Oct 15 '20 at 18:08
11

You shouldn't need two clones, one will do. Taking Paolo Bergantino answer we have:

jQuery.fn.swapWith = function(to) {
    return this.each(function() {
        var copy_to = $(to).clone(true);
        $(to).replaceWith(this);
        $(this).replaceWith(copy_to);
    });
};

Should be quicker. Passing in the smaller of the two elements should also speed things up.

Matthew Wilcoxson
  • 3,432
  • 1
  • 43
  • 48
  • 4
    The problem with this, and Paolo's, is that they cannot swap elements with an ID as IDs must be unique so cloning does not work. Bobince's solution does work in this case. – Ruud Aug 04 '10 at 15:58
  • Another problem is that this will remove the events from copy_to. – Jan Willem B Apr 13 '11 at 05:52
8

I used a technique like this before. I use it for the connector list on http://mybackupbox.com

// clone element1 and put the clone before element2
$('element1').clone().before('element2').end();

// replace the original element1 with element2
// leaving the element1 clone in it's place
$('element1').replaceWith('element2');
Eric Warnke
  • 1,325
  • 12
  • 18
  • this is the best solution imo, but no need to `end()` if you're not continuing the chain. how about `$('element1').clone().before('element2').end().replaceWith('element2');` – robisrob Nov 10 '15 at 21:28
  • 1
    just noticed `clone()` doesn't preserve any jquery `data()` unless you specify `clone(true)` - an important distinction if you're sorting so you don't lose all your data – robisrob Nov 10 '15 at 21:42
3

an other one without cloning:

I have an actual and a nominal element to swap:

            $nominal.before('<div />')
            $nb=$nominal.prev()
            $nominal.insertAfter($actual)
            $actual.insertAfter($nb)
            $nb.remove()

then insert <div> before and the remove afterwards are only needed, if you cant ensure, that there is always an element befor (in my case it is)

halfbit
  • 3,773
  • 2
  • 34
  • 47
  • Or you could just check for `.prev()`'s length, and if 0, then `.prepend()` to `.parent()` :) That way you don't need to create extra elements. – jave.web May 04 '16 at 23:47
3

I've made a function which allows you to move multiple selected options up or down

$('#your_select_box').move_selected_options('down');
$('#your_select_boxt').move_selected_options('up');

Dependencies:

$.fn.reverse = [].reverse;
function swapWith() (Paolo Bergantino)

First it checks whether the first/last selected option is able to move up/down. Then it loops through all the elements and calls

swapWith(element.next() or element.prev())

jQuery.fn.move_selected_options = function(up_or_down) {
  if(up_or_down == 'up'){
      var first_can_move_up = $("#" + this.attr('id') + ' option:selected:first').prev().size();
      if(first_can_move_up){
          $.each($("#" + this.attr('id') + ' option:selected'), function(index, option){
              $(option).swapWith($(option).prev());
          });
      }
  } else {
      var last_can_move_down = $("#" + this.attr('id') + ' option:selected:last').next().size();
      if(last_can_move_down){
        $.each($("#" + this.attr('id') + ' option:selected').reverse(), function(index, option){
            $(option).swapWith($(option).next());
        });
      }
  }
  return $(this);
}
Tom Maeckelberghe
  • 1,969
  • 3
  • 21
  • 24
  • This is inefficient, both in the way the code is written and how it will perform. – Blaise Jun 25 '11 at 11:53
  • It wasn't a major feature of our app and I just use it with small amounts of options. I just wanted something quick & easy... I would love it if you could improve this! That would be great! – Tom Maeckelberghe Jul 18 '11 at 09:52
3

This is my solution to move multiple children elements up and down inside the parent element. Works well for moving selected options in listbox (<select multiple></select>)

Move up:

$(parent).find("childrenSelector").each((idx, child) => {
    $(child).insertBefore($(child).prev().not("childrenSelector"));
});

Move down:

$($(parent).find("childrenSelector").get().reverse()).each((idx, child) => {
    $(opt).insertAfter($(child).next().not("childrenSelector"));
});
pistipanko
  • 745
  • 5
  • 9
2

take a look at jQuery plugin "Swapable"

http://code.google.com/p/jquery-swapable/

it's built on "Sortable" and looks like sortable (drag-n-drop, placeholder, etc.) but only swap two elements: dragged and dropped. All other elements are not affected and stay on their current position.

Mike Shultz
  • 1,368
  • 1
  • 15
  • 32
vadimk
  • 1,461
  • 15
  • 11
2

This is an answer based on @lotif's answer logic, but bit more generalized

If you append/prepend after/before the elements are actually moved
=> no clonning needed
=> events kept

There are two cases that can happen

  1. One target has something " .prev() ious" => we can put the other target .after() that.
  2. One target is the first child of it's .parent() => we can .prepend() the other target to parent.

The CODE

This code could be done even shorter, but I kept it this way for readability. Note that prestoring parents (if needed) and previous elements is mandatory.

$(function(){
  var $one = $("#one");
  var $two = $("#two");
  
  var $onePrev = $one.prev(); 
  if( $onePrev.length < 1 ) var $oneParent = $one.parent();

  var $twoPrev = $two.prev();
  if( $twoPrev.length < 1 ) var $twoParent = $two.parent();
  
  if( $onePrev.length > 0 ) $onePrev.after( $two );
    else $oneParent.prepend( $two );
    
  if( $twoPrev.length > 0 ) $twoPrev.after( $one );
    else $twoParent.prepend( $one );

});

...feel free to wrap the inner code in a function :)

Example fiddle has extra click events attached to demonstrate event preservation...
Example fiddle: https://jsfiddle.net/ewroodqa/

...will work for various cases - even one such as:

<div>
  <div id="one">ONE</div>
</div>
<div>Something in the middle</div>
<div>
  <div></div>
  <div id="two">TWO</div>
</div>
Community
  • 1
  • 1
jave.web
  • 13,880
  • 12
  • 91
  • 125
1

I wanted a solution witch does not use clone() as it has side effect with attached events, here is what I ended up to do

jQuery.fn.swapWith = function(target) {
    if (target.prev().is(this)) {
        target.insertBefore(this);
        return;
    }
    if (target.next().is(this)) {
        target.insertAfter(this);
        return
    }

    var this_to, this_to_obj,
        target_to, target_to_obj;

    if (target.prev().length == 0) {
        this_to = 'before';
        this_to_obj = target.next();
    }
    else {
        this_to = 'after';
        this_to_obj = target.prev();
    }
    if (jQuery(this).prev().length == 0) {
        target_to = 'before';
        target_to_obj = jQuery(this).next();
    }
    else {
        target_to = 'after';
        target_to_obj = jQuery(this).prev();
    }

    if (target_to == 'after') {
        target.insertAfter(target_to_obj);
    }
    else {
        target.insertBefore(target_to_obj);
    }
    if (this_to == 'after') {
        jQuery(this).insertAfter(this_to_obj);
    }
    else {
        jQuery(this).insertBefore(this_to_obj);
    }

    return this;
};

it must not be used with jQuery objects containing more than one DOM element

Mistic
  • 1,377
  • 2
  • 13
  • 27
1

If you have multiple copies of each element you need to do something in a loop naturally. I had this situation recently. The two repeating elements I needed to switch had classes and a container div as so:

<div class="container">
  <span class="item1">xxx</span>
  <span class="item2">yyy</span>
</div> 
and repeat...

The following code allowed me to iterate through everything and reverse...

$( ".container " ).each(function() {
  $(this).children(".item2").after($(this).children(".item1"));
});
AdamJones
  • 652
  • 1
  • 15
  • 38
1

I have done it with this snippet

// Create comments
var t1 = $('<!-- -->');
var t2 = $('<!-- -->');
// Position comments next to elements
$(ui.draggable).before(t1);
$(this).before(t2);
// Move elements
t1.after($(this));
t2.after($(ui.draggable));
// Remove comments
t1.remove();
t2.remove();
zveljkovic
  • 444
  • 4
  • 16
1

If you're wanting to swap two items selected in the jQuery object, you can use this method

http://www.vertstudios.com/blog/swap-jquery-plugin/

1

I did a table for changing order of obj in database used .after() .before(), so this is from what i have experiment.

$(obj1).after($(obj2))

Is insert obj1 before obj2 and

$(obj1).before($(obj2)) 

do the vice versa.

So if obj1 is after obj3 and obj2 after of obj4, and if you want to change place obj1 and obj2 you will do it like

$(obj1).before($(obj4))
$(obj2).before($(obj3))

This should do it BTW you can use .prev() and .next() to find obj3 and obj4 if you didn't have some kind of index for it already.

  • Not is this just a ripoff of the accepted answere, it contains a bad syntactical error. Did you copypasta this? – clockw0rk Aug 30 '18 at 12:55
  • ehh no?? This is working code for my work at my previous company. Beside i made sure to point out how to use and in which situation. Why i need to rip off someone? I also give how to get index of object, which is why the accepted answer was commented as not completed. – Tran Vu Dang Khoa Aug 31 '18 at 10:00
  • then why is it after$() instead of after($()) – clockw0rk Sep 03 '18 at 11:15
  • oh yeah i didn't see it, thanks. I input it from memory, don't have the right to keep that code after I leave the company – Tran Vu Dang Khoa Oct 02 '18 at 10:06
1
$('.five').swap('.two');

Create a jQuery function like this

$.fn.swap = function (elem) 
{
    elem = elem.jquery ? elem : $(elem);
    return this.each(function () 
    {
        $('<span></span>').insertBefore(this).before(elem.before(this)).remove();
    });
};

Thanks to Yannick Guinness at https://jsfiddle.net/ARTsinn/TVjnr/

Airy
  • 5,484
  • 7
  • 53
  • 78
0

if nodeA and nodeB are siblings, likes two <tr> in the same <tbody>, you can just use $(trA).insertAfter($(trB)) or $(trA).insertBefore($(trB)) to swap them, it works for me. and you don't need to call $(trA).remove() before, else you need to re-bind some click events on $(trA)

Spark.Bao
  • 5,573
  • 2
  • 31
  • 36
0

No need to use jquery for any major browser to swap elements at the moment. Native dom method, insertAdjacentElement does the trick no matter how they are located:

var el1 = $("el1");
var el2 = $("el2");
el1[0].insertAdjacentElement("afterend", el2[0]);
abeyaz
  • 3,034
  • 1
  • 16
  • 20
-2

I think you can do it very simple. For example let's say you have next structure: ...

<div id="first">...</div>
<div id="second">...</div>

and the result should be

<div id="second">...</div>
<div id="first">...</div>

jquery:

$('#second').after($('#first'));

I hope it helps!

DarkSideOfTheMoon83
  • 702
  • 1
  • 8
  • 17
  • 2
    this solution was already pointed out and does not work, unless the two elements are immediately next to each other. – BernaMariano Apr 09 '13 at 03:19
-2

The best option is to clone them with clone() method.

user84771
  • 13
  • 2