Czy istnieje natywna funkcja jQuery do przełączania elementów?

174

Czy mogę łatwo zamienić dwa elementy za pomocą jQuery?

Chcę to zrobić za pomocą jednej linii, jeśli to możliwe.

Mam element select i mam dwa przyciski do przesuwania opcji w górę lub w dół, a selektory zaznaczone i docelowe już mam na miejscu, robię to z if, ale zastanawiałem się, czy jest łatwiejszy sposób.

juan
źródło
Czy możesz wysłać swój kod i próbkę kodu?
Konstantin Tarkus,
To nie jest kwestia jQuery, ale JavaScript: nie możesz zamienić elementów DOM w jednej instrukcji. Jednak [odpowiedź Paolo] (# 698386) to świetna wtyczka;)
Seb
1
Dla osób przyjeżdżających tutaj z Google: sprawdź odpowiedź lotif, bardzo prosta i działała idealnie dla mnie.
Maurice
1
@Maurice, zmieniłem Zaakceptowanych odpowiedź
juan
1
To zamienia elementy tylko wtedy, gdy są obok siebie! Zmień zaakceptowaną odpowiedź.
Serhiy

Odpowiedzi:

233

Znalazłem ciekawy sposób rozwiązania tego problemu przy użyciu tylko jQuery:

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

lub

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

:)

lotif
źródło
2
Tak, to powinno zadziałać: Dokumentacja mówi: „Jeśli element wybrany w ten sposób zostanie wstawiony w innym miejscu, zostanie przesunięty przed cel (nie sklonowany)”.
Ridcully,
12
Ta opcja podoba mi się najbardziej! Prosty i ładnie kompatybilny. Mimo wszystko lubię używać el1.insertBefore (el2) i el1.insertAfter (el2) dla czytelności.
Maurice
6
Jedna uwaga ... (która, jak sądzę, odnosi się do wszystkich rozwiązań na tej stronie), ponieważ manipulujemy tutaj DOM. Usuwanie i dodawanie nie działa dobrze, jeśli w przenoszonym elemencie znajduje się ramka iframe. Element iframe zostanie ponownie załadowany. Ponadto załaduje się ponownie do oryginalnego adresu URL zamiast bieżącego. Bardzo irytujące, ale związane z bezpieczeństwem. Nie znalazłem na to rozwiązania
Maurice
202
nie powoduje to zamiany dwóch elementów, chyba że te dwa elementy są bezpośrednio obok siebie.
BonyT,
12
Nie powinno to już być akceptowaną odpowiedzią. Nie działa to w uogólnionym przypadku.
thekingoftruth
79

Paulo ma rację, ale nie jestem pewien, dlaczego klonuje dane elementy. Nie jest to naprawdę konieczne i spowoduje utratę wszelkich odniesień lub detektorów zdarzeń powiązanych z elementami i ich elementami podrzędnymi.

Oto wersja nieklonująca przy użyciu zwykłych metod DOM (ponieważ jQuery tak naprawdę nie ma żadnych specjalnych funkcji, które ułatwiłyby tę konkretną operację):

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
źródło
2
docs.jquery.com/Clone - przekazanie go „true” również klonuje zdarzenia. Próbowałem bez klonowania go najpierw, ale robił to, co obecnie jest twoje: zamienia pierwszy z drugim i pozostawia pierwszy taki, jaki jest.
Paolo Bergantino
jeśli mam <div id = "div1"> 1 </div> <div id = "div2"> 2 </div> i wywołam swapNodes (document.getElementById ('div1'), document.getElementById ('div2') ); dostaję <div id = "div1"> 1 </div> <div id = "div2"> 1 </div>
Paolo Bergantino
3
Ach, jest przypadek narożny, w którym następnym rodzeństwem a jest sam b. Naprawiono w powyższym fragmencie. jQuery robił dokładnie to samo.
bobince
Mam bardzo wrażliwą na porządek listę, która musi przechowywać zdarzenia i identyfikatory, a to działa jak urok! Dzięki!
Teekin
1
Wydaje mi się, że powinieneś dodać wersję jQuery, aby technicznie spełnić tytuł tego pytania. :)
thekingoftruth
70

Nie, nie ma, ale możesz go podnieść:

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

Stosowanie:

$(selector1).swapWith(selector2);

Zauważ, że działa to tylko wtedy, gdy selektory pasują tylko do 1 elementu, w przeciwnym razie może dać dziwne wyniki.

Paolo Bergantino
źródło
2
czy konieczne jest ich klonowanie?
czerwiec
Może mógłbyś napisać je bezpośrednio za pomocą html ()?
Ed James
2
@Ed Woodcock Jeśli to zrobisz, stracisz powiązane wydarzenia na tych elementach, jestem prawie pewien.
alex
2
Działa świetnie, gdy divy nie są obok siebie :)
Elmer
To powinna być akceptowana odpowiedź. Rozszerza jQuery o to, o co proszono.
Alice Wonder,
40

Istnieje wiele skrajnych przypadków tego problemu, które nie są obsługiwane przez zaakceptowaną odpowiedź lub odpowiedź Bobince'a. Inne rozwiązania obejmujące klonowanie są na dobrej drodze, ale klonowanie jest drogie i niepotrzebne. Kusi nas, aby klonować, ze względu na odwieczny problem dotyczący zamiany dwóch zmiennych, w którym jednym z kroków jest przypisanie jednej ze zmiennych do zmiennej tymczasowej. Przypisanie (klonowanie) w tym przypadku nie jest potrzebne. Oto rozwiązanie oparte na jQuery:

function swap(a, b) {
    a = $(a); b = $(b);
    var tmp = $('<span>').hide();
    a.before(tmp);
    b.before(a);
    tmp.replaceWith(b);
};
user2820356
źródło
7
To powinna być akceptowana odpowiedź. Klonowanie usuwa wszelkie wyzwalacze zdarzeń i nie jest konieczne. Używam tej dokładnej metody w moim kodzie.
thekingoftruth
1
To jest lepsza odpowiedź! Tak się złożyło, że miałem ul jako dziecko moich zamienionych elementów, który był elementem sortowalnym w jquery. Po zamianie z odpowiedzią Paolo Bergantino, elementy listy nie mogły już zostać usunięte. Z odpowiedzią user2820356 wszystko nadal działało dobrze.
agoldev
20

Wiele z tych odpowiedzi jest po prostu błędnych w ogólnym przypadku, inne są niepotrzebnie skomplikowane, jeśli w rzeczywistości działają. JQuery .beforei .aftermetody robią większość tego, co chcesz, ale potrzebujesz trzeciego elementu, tak jak działa wiele algorytmów wymiany. To całkiem proste - stwórz tymczasowy element DOM jako symbol zastępczy podczas przenoszenia elementów. Nie ma potrzeby patrzeć na rodziców czy rodzeństwo, a już na pewno nie ma potrzeby klonowania ...

$.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) wstaw tymczasowy div tempwcześniejthis

2) poruszać się thiswcześniejthat

3) przemieszczają się thatpotemp

3b) usunąć temp

Wtedy po prostu

$(selectorA).swapWith(selectorB);

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

robisrob
źródło
2
To najlepsza odpowiedź, wszyscy inni zakładają, że elementy są obok siebie. Działa to w każdej sytuacji.
brohr
11

Nie powinieneś potrzebować dwóch klonów, wystarczy jeden. Biorąc odpowiedź Paolo Bergantino, mamy:

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

Powinno być szybsze. Przekazanie mniejszego z dwóch elementów powinno również przyspieszyć działanie.

Matthew Wilcoxson
źródło
4
Problem z tym i Paolo polega na tym, że nie mogą zamieniać elementów z identyfikatorem, ponieważ identyfikatory muszą być unikalne, więc klonowanie nie działa. Rozwiązanie Bobince działa w tym przypadku.
Ruud
Innym problemem jest to, że spowoduje to usunięcie zdarzeń z copy_to.
Jan Willem B
8

Wcześniej użyłem takiej techniki. Używam go do listy łączników na 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
źródło
jest to najlepsze rozwiązanie imo, ale nie musisz tego robić, end()jeśli nie kontynuujesz łańcucha. co $('element1').clone().before('element2').end().replaceWith('element2');
powiesz na
1
właśnie zauważone clone()nie zachowuje żadnego jquery, data()chyba że określisz clone(true) - ważna różnica, jeśli sortujesz, aby nie stracić wszystkich danych
robisrob
3

Stworzyłem funkcję, która pozwala na przesuwanie wielu wybranych opcji w górę lub w dół

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

Zależności:

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

Najpierw sprawdza, czy pierwsza / ostatnia wybrana opcja może poruszać się w górę / w dół. Następnie przechodzi przez wszystkie elementy i wywołania

swapWith (element.next () lub 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
źródło
Jest to nieefektywne, zarówno pod względem sposobu pisania kodu, jak i jego działania.
Blaise,
Nie była to główna cecha naszej aplikacji i używam jej po prostu z niewielką liczbą opcji. Chciałem tylko czegoś szybkiego i łatwego ... Byłbym bardzo zadowolony, gdybyś mógł to poprawić! Byłoby świetnie!
Tom Maeckelberghe
3

inny bez klonowania:

Mam rzeczywisty i nominalny element do zamiany:

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

wtedy insert <div> beforei removepóźniej są potrzebne tylko wtedy, gdy nie możesz zapewnić, że zawsze istnieje element przed (w moim przypadku jest)

półbito
źródło
Albo może po prostu sprawdzić, czy .prev()„s długości, a jeśli 0, a następnie .prepend()do .parent():) W ten sposób nie trzeba tworzyć dodatkowe elementy.
jave.web
2

spójrz na wtyczkę jQuery „Swapable”

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

jest zbudowany na "Sortable" i wygląda jak sortable (drag-n-drop, placeholder itp.), ale zamienia tylko dwa elementy: przeciągnij i upuść. Żadne inne elementy nie ulegają zmianie i pozostają na swojej aktualnej pozycji.

vadimk
źródło
2

To jest odpowiedź oparta na logice odpowiedzi @lotif , ale nieco bardziej uogólniona

Jeśli dodasz / dodasz na początku po / przed rzeczywistym przeniesieniem elementów
=> klonowanie nie jest potrzebne
=> zdarzenia są zachowywane

Mogą się zdarzyć dwa przypadki

  1. Jeden cel ma coś „ .prev()ious” => możemy to ustawić dla drugiego celu .after().
  2. Jeden cel jest pierwszym dzieckiem to .parent()=> .prepend()drugi cel możemy skierować do rodzica.

Kod

Ten kod można by zrobić jeszcze krócej, ale zostawiłem go w ten sposób dla czytelności. Zwróć uwagę, że wstępne zapisywanie rodziców (w razie potrzeby) i poprzednich elementów jest obowiązkowe.

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

});

... nie krępuj się zawinąć kod wewnętrzny w funkcję :)

Przykładowe skrzypce mają dołączone dodatkowe zdarzenia kliknięcia, aby zademonstrować zachowanie zdarzeń ...
Przykładowe skrzypce: https://jsfiddle.net/ewroodqa/

... sprawdzi się w różnych przypadkach - nawet takich jak:

<div>
  <div id="one">ONE</div>
</div>
<div>Something in the middle</div>
<div>
  <div></div>
  <div id="two">TWO</div>
</div>
jave.web
źródło
2

To jest moje rozwiązanie do przenoszenia wielu elementów podrzędnych w górę iw dół wewnątrz elementu nadrzędnego. Działa dobrze w przypadku przenoszenia wybranych opcji w polu listy ( <select multiple></select>)

Podnieść:

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

Padnij:

$($(parent).find("childrenSelector").get().reverse()).each((idx, child) => {
    $(opt).insertAfter($(child).next().not("childrenSelector"));
});
pistipanko
źródło
1

Chciałem rozwiązania, które nie używa clone (), ponieważ ma efekt uboczny z dołączonymi zdarzeniami, oto co ostatecznie zrobiłem

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

nie może być używane z obiektami jQuery zawierającymi więcej niż jeden element DOM

Mistyczny
źródło
1

Jeśli masz wiele kopii każdego elementu, musisz naturalnie zrobić coś w pętli. Niedawno miałem taką sytuację. Dwa powtarzające się elementy, które musiałem zmienić, miały klasy i element div kontenera:

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

Poniższy kod pozwolił mi na iterację wszystkiego i odwrócenie ...

$( ".container " ).each(function() {
  $(this).children(".item2").after($(this).children(".item1"));
});
AdamJones
źródło
1

Zrobiłem to za pomocą tego fragmentu

// 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
źródło
1

Zrobiłem tabelę do zmiany kolejności obiektów w używanej bazie danych .after () .before (), więc to jest z tego, co mam eksperyment.

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

Wstaw obj1 przed obj2 i

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

zrób odwrotnie.

Więc jeśli obj1 jest po obj3 i obj2 po obj4, a jeśli chcesz zmienić miejsce obj1 i obj2, zrobisz to tak, jak

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

Powinno to zrobić BTW, możesz użyć .prev () i .next () do znalezienia obj3 i obj4, jeśli nie masz jeszcze dla nich jakiegoś indeksu.

Tran Vu Dang Khoa
źródło
Nie jest to tylko zdzierstwo zaakceptowanej odpowiedzi, zawiera zły błąd składniowy. Czy skopiowałeś to?
clockw0rk
ehh nie ?? To działa kod do mojej pracy w mojej poprzedniej firmie. Poza tym zwróciłem uwagę, jak używać iw jakiej sytuacji. Dlaczego muszę kogoś oszukać? Podaję również jak uzyskać indeks obiektu, dlatego zaakceptowana odpowiedź została skomentowana jako niekompletna.
Tran Vu Dang Khoa
to dlaczego jest po $ () zamiast po ($ ())
clockw0rk
o tak, nie widziałem tego, dzięki. Wprowadzam to z pamięci, nie mam prawa zachować tego kodu po odejściu z firmy
Tran Vu Dang Khoa
0

jeśli nodeA i nodeB są rodzeństwem, lubią dwoje <tr>w tym samym <tbody>, możesz po prostu ich użyć $(trA).insertAfter($(trB))lub $(trA).insertBefore($(trB))zamienić, to działa dla mnie. i nie musisz $(trA).remove()wcześniej dzwonić , w przeciwnym razie musisz ponownie powiązać niektóre zdarzenia kliknięcia$(trA)

Spark.Bao
źródło
0
$('.five').swap('.two');

Utwórz taką funkcję jQuery

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

Podziękowania dla Yannick Guinness na https://jsfiddle.net/ARTsinn/TVjnr/

Przewiewny
źródło
-2

Najlepszą opcją jest sklonowanie ich metodą clone ().

user84771
źródło
1
to usuwa wszelkie wywołania zwrotne i powiązania
thekingoftruth
-2

Myślę, że możesz to zrobić bardzo prosto. Na przykład, powiedzmy, że masz następną strukturę: ...

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

a wynik powinien być

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

jQuery:

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

Mam nadzieję, że to pomoże!

Jernej Gololicic
źródło
2
to rozwiązanie zostało już wskazane i nie działa, chyba że te dwa elementy znajdują się bezpośrednio obok siebie.
BernaMariano