Is there a native jQuery function to switch elements?
Asked Answered
E

22

190

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.

Eckmann answered 30/3, 2009 at 17:57 Comment(5)
Can you post you markup and code sample?Celestyn
It's not a matter of jQuery but JavaScript: you cannot swap DOM elements in a single instruction. However, [Paolo's answer](#698386) is a great plugin ;)Cheapen
For people coming here from google: check out lotif's answer, very simple and worked perfectly for me.Velocipede
@Maurice, I changed the accepted answerEckmann
This only swaps the elements if they are beside each other! Please, change the accepted answer.Triturable
S
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"));
Sketchy answered 26/12, 2011 at 18:19 Comment(10)
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)".Rowell
I like this option the best! Simple and nicely compatible. Allthough i like to use el1.insertBefore(el2) and el1.insertAfter(el2) for readability.Velocipede
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 thisVelocipede
this doesn't swap two elements unless the two elements are immediately next to each other.European
This only swaps the elements if they are beside each other, otherwise Paolo's answer is the correct one.Fredricfredrick
This should not be the accepted answer any longer. It does not work in the generalized case.Damiondamita
This seems to remove the first element when you do it?Besiege
Well this answer gives a root for a more general solution - https://mcmap.net/q/134687/-is-there-a-native-jquery-function-to-switch-elementsZuniga
It is worked for me, I use it like this: $('a').eq(0).before($('a').eq(1));Pulsifer
@European Exactly what the OP was looking for (as specified in the last part of the question)...Campbellbannerman
G
82

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);
}
Genitals answered 30/3, 2009 at 18:25 Comment(9)
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.Jerriejerrilee
if i have <div id="div1">1</div><div id="div2">2</div> and call swapNodes(document.getElementById('div1'), document.getElementById('div2')); i get <div id="div1">1</div><div id="div2">1</div>Jerriejerrilee
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.Genitals
I have a very order-sensitive list that needs to retain events and IDs and this works like a charm! Thanks!Metamer
I feel as though you should add a jQuery version to technically satisfy this question's title. :)Damiondamita
Compile fail. "b.parentNode is undefined"Balderas
This works well, but only if you pass in the jQuery element index as well. E.g. swapNodes($('#myDiv')[0], $('#myDiv2')[0]).Kosciusko
Thanks @bobince, this works well, but how to swapping more than 3 elements is that possible too?Tyrr
@David Mulder, this works well, but how to swapping more than 3 elements is that possible too?Tyrr
J
74

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.

Jerriejerrilee answered 30/3, 2009 at 18:13 Comment(5)
is it necessary to clone them?Eckmann
Perhaps you could write them directly using html() ?Embryogeny
@Ed Woodcock If you do, you will lose bound events on those elements I'm pretty sure.Simonsen
Works great when the divs are not next to each other :)Fredricfredrick
This should be the accepted answer. It extends jQuery to provide what was asked for.Gypsy
V
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);
};
Vermicide answered 26/9, 2013 at 16:39 Comment(2)
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.Damiondamita
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.Crymotherapy
M
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/

Magnetochemistry answered 21/7, 2016 at 21:59 Comment(3)
This is the best answer, all others asume the elements are next to each other. This works in every situation.Prebend
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://mcmap.net/q/134687/-is-there-a-native-jquery-function-to-switch-elements is similiar i guess but less explained and little more complex.Denice
@Magnetochemistry 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.Denice
T
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.

Tova answered 4/2, 2010 at 16:44 Comment(2)
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.Allspice
Another problem is that this will remove the events from copy_to.Tiger
E
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');
Ellene answered 26/2, 2012 at 6:0 Comment(2)
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');Magnetochemistry
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 dataMagnetochemistry
A
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);
}
Allotrope answered 27/1, 2011 at 14:34 Comment(2)
This is inefficient, both in the way the code is written and how it will perform.Andante
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!Allotrope
C
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)

Chloechloette answered 14/9, 2014 at 17:10 Comment(1)
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.Zuniga
M
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"));
});
Mania answered 16/3, 2018 at 15:15 Comment(0)
S
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.

Sulfathiazole answered 3/7, 2010 at 21:42 Comment(0)
Z
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>
Zuniga answered 5/5, 2016 at 0:18 Comment(0)
G
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/

Ghislainegholston answered 12/9, 2010 at 9:4 Comment(0)
H
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

Haplosis answered 15/9, 2013 at 9:52 Comment(0)
K
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"));
});
Kamp answered 16/2, 2015 at 17:19 Comment(0)
C
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();
Cryptoclastic answered 14/3, 2015 at 10:3 Comment(0)
S
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.

Sip answered 11/11, 2016 at 3:31 Comment(4)
Not is this just a ripoff of the accepted answere, it contains a bad syntactical error. Did you copypasta this?Cassiopeia
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.Sip
then why is it after$() instead of after($())Cassiopeia
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 companySip
S
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/

Sirius answered 14/4, 2020 at 16:49 Comment(0)
T
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)

Tartar answered 26/8, 2017 at 3:36 Comment(0)
C
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]);
Chung answered 29/8, 2020 at 11:50 Comment(0)
T
-2

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

Takeo answered 30/3, 2009 at 19:3 Comment(1)
this removes any callbacks and bindingsDamiondamita
R
-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!

Roncesvalles answered 10/2, 2013 at 17:11 Comment(1)
this solution was already pointed out and does not work, unless the two elements are immediately next to each other.Unguent

© 2022 - 2024 — McMap. All rights reserved.