¿Existe una función nativa de jQuery para cambiar elementos?

174

¿Puedo intercambiar fácilmente dos elementos con jQuery?

Estoy buscando hacer esto con una línea si es posible.

Tengo un elemento de selección y tengo dos botones para mover hacia arriba o hacia abajo las opciones, y ya tengo el seleccionado y los selectores de destino en su lugar, lo hago con un if, pero me preguntaba si hay una manera más fácil.

juan
fuente
¿Puedes publicar tu marcado y muestra de código?
Konstantin Tarkus
No es una cuestión de jQuery sino de JavaScript: no puede intercambiar elementos DOM en una sola instrucción. Sin embargo, [la respuesta de Paolo] (# 698386) es un gran complemento;)
Seb
1
Para las personas que vienen de google: mira la respuesta de lotif, muy simple y funcionó perfectamente para mí.
Maurice
1
@Maurice, cambié la respuesta aceptada
juan
1
¡Esto solo intercambia los elementos si están uno al lado del otro! Por favor, cambie la respuesta aceptada.
Serhiy

Respuestas:

233

He encontrado una forma interesante de resolver esto usando solo jQuery:

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

o

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

:)

lotif
fuente
2
Sí, esto debería funcionar: la documentación dice: "Si un elemento seleccionado de esta manera se inserta en otro lugar, se moverá antes del destino (no clonado)".
Ridcully
12
¡Me gusta más esta opción! Simple y muy compatible. Aunque me gusta usar el1.insertBefore (el2) y el1.insertAfter (el2) para facilitar la lectura.
Maurice
66
Sin embargo, una nota al margen ... (que supongo que se aplica a todas las soluciones en esta página) ya que estamos manipulando el DOM aquí. Eliminar y agregar no funciona bien cuando hay un iframe presente dentro del elemento que está moviendo. El iframe se volverá a cargar. También se volverá a cargar en su url original en lugar de la actual. Muy molesto pero relacionado con la seguridad. No he encontrado una solución para esto
Maurice
202
esto no intercambia dos elementos a menos que los dos elementos estén inmediatamente uno al lado del otro.
BonyT
12
Esta ya no debería ser la respuesta aceptada. No funciona en el caso generalizado.
thekingoftruth
79

Paulo tiene razón, pero no estoy seguro de por qué está clonando los elementos en cuestión. Esto no es realmente necesario y perderá cualquier referencia u oyentes de eventos asociados con los elementos y sus descendientes.

Aquí hay una versión sin clonación que usa métodos DOM simples (ya que jQuery realmente no tiene ninguna función especial para facilitar esta operación en particular):

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);
}
bobince
fuente
2
docs.jquery.com/Clone : pasarlo "verdadero" también clona los eventos. Lo intenté sin clonarlo primero, pero estaba haciendo lo que el suyo es actualmente: intercambiar el primero con el segundo y dejar el primero como está.
Paolo Bergantino
si tengo <div id = "div1"> 1 </div> <div id = "div2"> 2 </div> y llamo swapNodes (document.getElementById ('div1'), document.getElementById ('div2') ); me sale <div id = "div1"> 1 </div> <div id = "div2"> 1 </div>
Paolo Bergantino
3
Ah, hay un caso de esquina donde el próximo hermano de a es b en sí. Corregido en el fragmento anterior. jQuery estaba haciendo exactamente lo mismo.
bobince
¡Tengo una lista muy sensible al orden que necesita conservar eventos e ID y esto funciona de maravilla! ¡Gracias!
Teekin
1
Siento que debería agregar una versión jQuery para satisfacer técnicamente el título de esta pregunta. :)
thekingoftruth
70

No, no lo hay, pero podrías preparar uno:

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);
    });
};

Uso:

$(selector1).swapWith(selector2);

Tenga en cuenta que esto solo funciona si los selectores solo coinciden con 1 elemento cada uno, de lo contrario podría dar resultados extraños.

Paolo Bergantino
fuente
2
¿Es necesario clonarlos?
juan
¿Quizás podría escribirlos directamente usando html ()?
Ed James
2
@Ed Woodcock Si lo hace, perderá eventos vinculados en esos elementos, estoy bastante seguro.
alex
2
Funciona muy bien cuando los divs no están uno al lado del otro :)
Elmer
Esta debería ser la respuesta aceptada. Extiende jQuery para proporcionar lo que se solicitó.
Alice Wonder
40

Hay muchos casos extremos para este problema, que no se manejan con la respuesta aceptada o la respuesta de bobince. Otras soluciones que implican la clonación están en el camino correcto, pero la clonación es costosa e innecesaria. Estamos tentados a clonar, debido al antiguo problema de cómo intercambiar dos variables, en el que uno de los pasos es asignar una de las variables a una variable temporal. La asignación, (clonación), en este caso no es necesaria. Aquí hay una solución basada en jQuery:

function swap(a, b) {
    a = $(a); b = $(b);
    var tmp = $('<span>').hide();
    a.before(tmp);
    b.before(a);
    tmp.replaceWith(b);
};
usuario2820356
fuente
77
Esta debería ser la respuesta aceptada. La clonación elimina cualquier evento desencadenante y no es necesario. Hago uso de este método exacto en mi código.
thekingoftruth
1
Esta es la mejor respuesta! Sucedí tener un ul como hijo de mis elementos intercambiados, que era un elemento ordenable jquery. Después de intercambiar con la respuesta de Paolo Bergantino, los elementos de la lista ya no se podían soltar. Con la respuesta del usuario 2820356, todo funcionó bien.
agoldev
20

Muchas de estas respuestas son simplemente incorrectas para el caso general, otras son innecesariamente complicadas si de hecho incluso funcionan. JQuery .beforey los .aftermétodos hacen la mayor parte de lo que desea hacer, pero necesita un tercer elemento de la forma en que funcionan muchos algoritmos de intercambio. Es bastante simple: crea un elemento DOM temporal como marcador de posición mientras mueves las cosas. No hay necesidad de mirar a los padres o hermanos, y ciertamente no hay necesidad de clonar ...

$.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.after($that).remove();

  return $this;
}

1) poner el div temporal tempantesthis

2) muévete thisantesthat

3) moverse thatdespuéstemp

3b) eliminar temp

Entonces simplemente

$(selectorA).swapWith(selectorB);

MANIFESTACIÓN: http://codepen.io/anon/pen/akYajE

robisrob
fuente
2
Esta es la mejor respuesta, todos los demás suponen que los elementos están uno al lado del otro. Esto funciona en todas las situaciones.
brohr
11

No debería necesitar dos clones, uno lo hará. Tomando la respuesta de Paolo Bergantino tenemos:

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

Debería ser más rápido. Pasar el más pequeño de los dos elementos también debería acelerar las cosas.

Matthew Wilcoxson
fuente
44
El problema con esto, y el de Paolo, es que no pueden intercambiar elementos con una ID ya que las ID deben ser únicas, por lo que la clonación no funciona. La solución de Bobince funciona en este caso.
Ruud
Otro problema es que esto eliminará los eventos de copy_to.
Jan Willem B
8

Usé una técnica como esta antes. Lo uso para la lista de conectores en 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
fuente
Esta es la mejor solución de la OMI, pero no es necesario end()si no continúa la cadena. ¿qué tal$('element1').clone().before('element2').end().replaceWith('element2');
robisrob
1
acabo de notar clone()que no conserva ningún jquery a data()menos que especifique clone(true) , una distinción importante si está ordenando para no perder todos sus datos
robisrob
3

He creado una función que le permite mover múltiples opciones seleccionadas hacia arriba o hacia abajo

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

Dependencias:

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

Primero verifica si la primera / última opción seleccionada puede moverse hacia arriba / abajo. Luego recorre todos los elementos y llama

swapWith (element.next () o 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
fuente
Esto es ineficiente, tanto en la forma en que se escribe el código como en su rendimiento.
Blaise
No era una característica importante de nuestra aplicación y solo la uso con pequeñas cantidades de opciones. Solo quería algo rápido y fácil ... ¡Me encantaría que pudieras mejorar esto! ¡Eso seria genial!
Tom Maeckelberghe
3

otro sin clonar:

Tengo un elemento real y un elemento nominal para intercambiar:

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

entonces insert <div> beforey removedespués solo son necesarios, si no puede asegurarse, que siempre hay un elemento antes (en mi caso, es)

medio bit
fuente
O simplemente puede verificar .prev()la longitud de, y si es 0, entonces .prepend()a .parent():) De esa manera, no necesita crear elementos adicionales.
jave.web
2

eche un vistazo al complemento jQuery "Swapable"

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

está construido en "Sortable" y parece ordenable (arrastrar y soltar, marcador de posición, etc.) pero solo intercambia dos elementos: arrastrado y soltado. Todos los demás elementos no se ven afectados y permanecen en su posición actual.

vadimk
fuente
2

Esta es una respuesta basada en la lógica de respuesta de @lotif , pero un poco más generalizada

Si agrega / antepone después / antes de que los elementos se muevan realmente
=> no se necesita clonación
=> eventos guardados

Hay dos casos que pueden suceder.

  1. Un objetivo tiene algo " .prev()ious" => podemos poner al otro objetivo .after()que.
  2. Un objetivo es el primer hijo de its .parent()=> podemos .prepend()el otro objetivo a padre.

El código

Este código podría hacerse aún más corto, pero lo mantuve así para facilitar la lectura. Tenga en cuenta que la prescripción de los padres (si es necesario) y elementos anteriores es obligatoria.

$(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 );

});

... no dude en envolver el código interno en una función :)

El violín de ejemplo tiene eventos de clic adicionales adjuntos para demostrar la preservación de eventos ...
Violín de ejemplo: https://jsfiddle.net/ewroodqa/

... funcionará para varios casos, incluso uno como:

<div>
  <div id="one">ONE</div>
</div>
<div>Something in the middle</div>
<div>
  <div></div>
  <div id="two">TWO</div>
</div>
jave.web
fuente
2

Esta es mi solución para mover varios elementos secundarios hacia arriba y hacia abajo dentro del elemento primario. Funciona bien para mover las opciones seleccionadas en el cuadro de lista (<select multiple></select> )

Ascender:

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

Mover hacia abajo:

$($(parent).find("childrenSelector").get().reverse()).each((idx, child) => {
    $(opt).insertAfter($(child).next().not("childrenSelector"));
});
pistipanko
fuente
1

Quería una solución que no use clone () ya que tiene efectos secundarios con eventos adjuntos, esto es lo que terminé haciendo

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;
};

no debe usarse con objetos jQuery que contengan más de un elemento DOM

Místico
fuente
1

Si tiene varias copias de cada elemento, debe hacer algo en un bucle de forma natural. Tuve esta situación recientemente. Los dos elementos repetidos que necesitaba cambiar tenían clases y un contenedor div así:

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

El siguiente código me permitió recorrer todo e invertir ...

$( ".container " ).each(function() {
  $(this).children(".item2").after($(this).children(".item1"));
});
AdamJones
fuente
1

Lo he hecho con este fragmento.

// 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
fuente
1

Hice una tabla para cambiar el orden de obj en la base de datos utilizada .after () .before (), así que esto es de lo que tengo experimento.

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

Es insertar obj1 antes de obj2 y

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

hacer lo contrario

Entonces, si obj1 es después de obj3 y obj2 después de obj4, y si desea cambiar de lugar obj1 y obj2, lo hará como

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

Esto debería hacerlo, por cierto, puede usar .prev () y .next () para encontrar obj3 y obj4 si aún no tiene algún tipo de índice para él.

Tran Vu Dang Khoa
fuente
No es solo una estafa de la respuesta aceptada, sino que contiene un error sintáctico incorrecto. ¿Copiaste esto?
clockw0rk
ehh no ?? Este es el código de trabajo para mi trabajo en mi empresa anterior. Además, me aseguré de señalar cómo usar y en qué situación. ¿Por qué necesito estafar a alguien? También doy cómo obtener el índice del objeto, razón por la cual la respuesta aceptada se comentó como no completada.
Tran Vu Dang Khoa
entonces, ¿por qué es después de $ () en lugar de después de ($ ())
clockw0rk
oh si no lo vi, gracias. Lo ingreso de memoria, no tengo el derecho de guardar ese código después de dejar la compañía
Tran Vu Dang Khoa
0

si nodeA y nodeB son hermanos, le gustan dos <tr>en el mismo <tbody>, solo puede usarlos $(trA).insertAfter($(trB))o $(trA).insertBefore($(trB))intercambiarlos, funciona para mí. y no necesita llamar $(trA).remove()antes, de lo contrario debe volver a vincular algunos eventos de clic en$(trA)

Spark.Bao
fuente
0
$('.five').swap('.two');

Cree una función jQuery como esta

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

Gracias a Yannick Guinness en https://jsfiddle.net/ARTsinn/TVjnr/

Aireado
fuente
-2

La mejor opción es clonarlos con el método clone ().

user84771
fuente
1
esto elimina cualquier devolución de llamada y enlaces
thekingoftruth
-2

Creo que puedes hacerlo muy simple. Por ejemplo, supongamos que tiene la siguiente estructura: ...

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

y el resultado debería ser

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

jquery:

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

¡Espero que ayude!

Jernej Gololicic
fuente
2
esta solución ya se señaló y no funciona, a menos que los dos elementos estén uno al lado del otro.
BernaMariano