Existe uma função jQuery nativa para alternar elementos?

174

Posso trocar facilmente dois elementos com o jQuery?

Eu estou olhando para fazer isso com uma linha, se possível.

Eu tenho um elemento de seleção e dois botões para mover para cima ou para baixo nas opções, e já tenho os seletores selecionados e de destino no lugar, faço-o com um if, mas queria saber se existe uma maneira mais fácil.

juan
fonte
Você pode postar sua marcação e amostra de código?
Konstantin Tarkus
Não é uma questão de jQuery, mas de JavaScript: você não pode trocar elementos DOM em uma única instrução. No entanto, [a resposta de Paolo] (# 698386) é um ótimo plugin;)
Seb
1
Para as pessoas que vêm do google: confira a resposta da lotif, muito simples e funcionou perfeitamente para mim.
181 Maurice
1
@Maurice, mudei a resposta aceita #
juan
1
Isso apenas troca os elementos se eles estiverem um ao lado do outro! Por favor, altere a resposta aceita.
Serhiy

Respostas:

233

Eu encontrei uma maneira interessante de resolver isso usando apenas jQuery:

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

ou

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

:)

lotif
fonte
2
Sim, isso deve funcionar: A documentação diz: "Se um elemento selecionado dessa maneira for inserido em outro lugar, ele será movido antes do destino (não clonado)".
Ridcully
12
Eu gosto desta opção o melhor! Simples e bem compatível. Apesar de eu gostar de usar el1.insertBefore (el2) e el1.insertAfter (el2) para facilitar a leitura.
Maurice
6
Uma nota lateral .. (que eu acho que se aplica a todas as soluções nesta página), pois estamos manipulando o DOM aqui. Remover e adicionar não funciona bem quando há um iframe presente no elemento que você está movendo. O iframe será recarregado. Também será recarregado no seu URL original, em vez do atual. Muito chato, mas relacionado à segurança. Eu não encontrei uma solução para este
Maurice
202
isso não troca dois elementos, a menos que os dois elementos estejam imediatamente próximos um do outro.
BonyT
12
Esta não deve ser mais a resposta aceita. Não funciona no caso generalizado.
thekingoftruth
79

Paulo está certo, mas não sei por que ele está clonando os elementos em questão. Isso não é realmente necessário e perderá referências ou ouvintes de eventos associados aos elementos e seus descendentes.

Aqui está uma versão sem clonagem usando métodos DOM simples (já que o jQuery realmente não possui funções especiais para facilitar essa operação em 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
fonte
2
docs.jquery.com/Clone - passar "true" também clona os eventos. Tentei sem cloná-lo primeiro, mas ele estava fazendo o que é seu atualmente: ele está trocando o primeiro pelo segundo e deixando o primeiro como está.
Paolo Bergantino
se eu tiver <div id = "div1"> 1 </div> <div id = "div2"> 2 </div> e chamar swapNodes (document.getElementById ('div1'), document.getElementById ('div2') ); eu recebo <div id = "div1"> 1 </div> <div id = "div2"> 1 </div>
Paolo Bergantino
3
Ah, há um caso de canto em que o próximo irmão de a é b em si. Corrigido no trecho acima. O jQuery estava fazendo exatamente a mesma coisa.
30990 bobince
Eu tenho uma lista muito sensível à ordem que precisa reter eventos e IDs e isso funciona como um encanto! Obrigado!
Teekin 27/05
1
Sinto que você deve adicionar uma versão do jQuery para satisfazer tecnicamente o título dessa pergunta. :)
thekingoftruth
70

Não, não há, mas você pode preparar um:

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

Observe que isso só funciona se os seletores corresponderem apenas a 1 elemento cada, caso contrário, poderá gerar resultados estranhos.

Paolo Bergantino
fonte
2
é necessário cloná-los?
juan
Talvez você possa escrevê-los diretamente usando html ()?
Ed James
2
@ Ed Woodcock Se fizer isso, você perderá eventos vinculados a esses elementos, tenho certeza.
12289 alex
2
Funciona muito bem quando as divs não estão ao lado do outro :)
Elmer
Essa deve ser a resposta aceita. Estende o jQuery para fornecer o que foi solicitado.
Alice maravilha
40

Existem muitos casos extremos para esse problema, que não são tratados pela resposta aceita ou pela resposta de bobince. Outras soluções que envolvem a clonagem estão no caminho certo, mas a clonagem é cara e desnecessária. Somos tentados a clonar, devido ao antigo problema de como trocar duas variáveis, em que uma das etapas é atribuir uma das variáveis ​​a uma variável temporária. A atribuição (clonagem), neste caso, não é necessária. Aqui está uma solução baseada em jQuery:

function swap(a, b) {
    a = $(a); b = $(b);
    var tmp = $('<span>').hide();
    a.before(tmp);
    b.before(a);
    tmp.replaceWith(b);
};
user2820356
fonte
7
Essa deve ser a resposta aceita. A clonagem remove todos os gatilhos de eventos e não é necessária. Faço uso desse método exato no meu código.
Thekingoftruth
1
Esta é a melhor resposta! Por acaso, tive um ul como filho dos meus elementos trocados, que era um elemento classificável em jquery. Depois de trocar com a resposta de Paolo Bergantino, os itens da lista não podiam mais ser descartados. Com a resposta do usuário2820356, tudo ainda funcionou bem.
agoldev
20

Muitas dessas respostas estão simplesmente erradas para o caso geral, outras são desnecessariamente complicadas se, de fato, funcionam. O jQuery .beforee os .aftermétodos fazem a maior parte do que você deseja fazer, mas você precisa de um terceiro elemento da maneira como muitos algoritmos de swap funcionam. É bem simples - crie um elemento DOM temporário como um espaço reservado enquanto você move as coisas. Não há necessidade de olhar para pais ou irmãos, e certamente não há necessidade 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) coloque a div temporária tempantesthis

2) mover thisantesthat

3) mover thatdepoistemp

3b) remover temp

Então simplesmente

$(selectorA).swapWith(selectorB);

DEMO: http://codepen.io/anon/pen/akYajE

robisrob
fonte
2
Esta é a melhor resposta, todas as outras já que os elementos estão próximos um do outro. Isso funciona em todas as situações.
brohr 23/01
11

Você não deve precisar de dois clones, um servirá. Tomando Paolo Bergantino resposta, temos:

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

Deve ser mais rápido. Passar no menor dos dois elementos também deve acelerar as coisas.

Matthew Wilcoxson
fonte
4
O problema com isso, e com o de Paolo, é que eles não podem trocar elementos com um ID, pois os IDs devem ser exclusivos para que a clonagem não funcione. A solução de Bobince funciona nesse caso.
Ruud
Outro problema é que isso removerá os eventos de copy_to.
Jan Willem B
8

Eu usei uma técnica como essa antes. Eu o uso para a lista de conectores em 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
fonte
esta é a melhor solução, mas não é necessário end()se você não continuar a cadeia. que tal #$('element1').clone().before('element2').end().replaceWith('element2');
robisrob
1
só notei clone()não preserva nenhuma jquery data()menos que você especifique clone(true) - uma distinção importante se você está classificando para que você não perca todos os seus dados
robisrob
3

Eu criei uma função que permite mover várias opções selecionadas para cima ou para baixo

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

Dependências:

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

Primeiro, ele verifica se a primeira / última opção selecionada é capaz de mover para cima / para baixo. Em seguida, percorre todos os elementos e chama

swapWith (element.next () ou 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
fonte
Isso é ineficiente, tanto na forma como o código é escrito quanto no desempenho.
Blaise
Não era um recurso importante do nosso aplicativo e eu apenas o uso com pequenas quantidades de opções. Eu só queria algo rápido e fácil ... Eu adoraria se você pudesse melhorar isso! Isso seria bom!
Tom Maeckelberghe
3

outro sem clonagem:

Eu tenho um elemento real e um nominal para trocar:

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

então insert <div> beforee removedepois só são necessários, se você não puder garantir, que sempre exista um elemento antes (no meu caso, é)

halfbit
fonte
Ou você pode apenas verificar .prev()o comprimento de, e se 0, então .prepend()para .parent():) Dessa forma, você não precisa criar elementos extras.
Jave.web #
2

dê uma olhada no plugin jQuery "Swapable"

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

é baseado em "Classificável" e parece classificável (arrastar e soltar, espaço reservado etc.), mas apenas trocar dois elementos: arrastado e solto. Todos os outros elementos não são afetados e permanecem na posição atual.

vadimk
fonte
2

Esta é uma resposta baseada em @lotif lógica de resposta do , mas um pouco mais generalizada

Se você acrescentar / acrescentar antes / antes dos elementos serem realmente movidos
=> não é necessário clonar
=> eventos mantidos

Existem dois casos que podem acontecer

  1. Um alvo tem algo " .prev()ious" => podemos colocar o outro alvo .after()nisso.
  2. Um alvo é o primeiro filho dele .parent()=> podemos .prepend()o outro alvo para os pais.

O código

Esse código pode ser feito ainda mais curto, mas eu o mantive assim para facilitar a leitura. Observe que o pré-armazenamento dos pais (se necessário) e dos elementos anteriores é obrigatório.

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

});

... sinta-se à vontade para agrupar o código interno em uma função :)

O violino de exemplo possui eventos de clique extras anexados para demonstrar a preservação do evento ...
Violino de exemplo: https://jsfiddle.net/ewroodqa/

... funcionará em vários casos - até um 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
fonte
2

Esta é minha solução para mover vários elementos filhos para cima e para baixo dentro do elemento pai. Funciona bem para mover as opções selecionadas na caixa de listagem ( <select multiple></select>)

Subir:

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

Mover para baixo:

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

Eu queria uma solução que não use clone (), pois tem efeito colateral com eventos anexados, eis o que acabei fazendo

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

ele não deve ser usado com objetos jQuery que contêm mais de um elemento DOM

Mistic
fonte
1

Se você tiver várias cópias de cada elemento, precisará fazer algo em loop naturalmente. Eu tive essa situação recentemente. Os dois elementos repetidos que eu precisava alternar tinham classes e uma div de contêiner da seguinte maneira:

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

O código a seguir me permitiu percorrer tudo e reverter ...

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

Eu fiz isso com este trecho

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

Eu fiz uma tabela para alterar a ordem de obj no banco de dados usado .after () .before (), então isso é do que eu experimento.

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

É inserir obj1 antes de obj2 e

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

faça o vice-versa.

Portanto, se obj1 for após obj3 e obj2 após obj4, e se você quiser mudar de lugar obj1 e obj2, você fará isso como

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

Isso deve ser feito: BTW, você pode usar .prev () e .next () para encontrar obj3 e obj4 se você ainda não possui algum tipo de índice.

Tran Vu Dang Khoa
fonte
Não é apenas uma imitação do respondere aceito, ele contém um erro sintático ruim. Você copypasta isso?
Clockw0rk
ehh não ?? Este é um código de trabalho para o meu trabalho na minha empresa anterior. Ao lado, certifiquei-me de apontar como usar e em qual situação. Por que eu preciso arrancar alguém? Também forneço como obter o índice do objeto, e é por isso que a resposta aceita foi comentada como não concluída.
Tran Vu Dang Khoa
então por que é depois de $ () em vez de depois ($ ())
clockw0rk
oh sim, eu não vi, obrigado. Input I-lo da memória, não tem o direito de manter esse código depois que eu deixar a empresa
Tran Vu Dang Khoa
0

se nodeA e nodeB são irmãos, gostam de dois <tr>no mesmo <tbody>, você pode simplesmente usar $(trA).insertAfter($(trB))ou $(trA).insertBefore($(trB))trocá-los, funciona para mim. e você não precisa ligar $(trA).remove()antes, caso contrário, é necessário vincular novamente alguns eventos de clique em$(trA)

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

Crie uma função 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();
    });
};

Agradecimentos a Yannick Guinness em https://jsfiddle.net/ARTsinn/TVjnr/

Arejado
fonte
-2

A melhor opção é cloná-los com o método clone ().

user84771
fonte
1
isso remove quaisquer chamadas de retorno e ligações
thekingoftruth
-2

Eu acho que você pode fazer isso de maneira muito simples. Por exemplo, digamos que você tenha a próxima estrutura: ...

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

e o resultado deve ser

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

jquery:

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

Espero que ajude!

Jernej Gololicic
fonte
2
essa solução já foi apontada e não funciona, a menos que os dois elementos estejam imediatamente próximos um do outro.
BernaMariano