มีฟังก์ชัน jQuery ในการสลับองค์ประกอบหรือไม่


174

ฉันสามารถสลับสององค์ประกอบด้วย jQuery ได้อย่างง่ายดายหรือไม่?

ฉันกำลังมองหาที่จะทำกับหนึ่งบรรทัดถ้าเป็นไปได้

ฉันมีองค์ประกอบที่เลือกและฉันมีสองปุ่มเพื่อเลื่อนขึ้นหรือลงตัวเลือกและฉันมีตัวเลือกและตัวเลือกปลายทางอยู่แล้วฉันทำได้ด้วยถ้า แต่ฉันสงสัยว่ามีวิธีที่ง่ายกว่าหรือไม่


คุณสามารถโพสต์มาร์กอัพและโค้ดตัวอย่างได้หรือไม่
Konstantin Tarkus

ไม่ใช่เรื่องของ jQuery แต่เป็น JavaScript: คุณไม่สามารถสลับองค์ประกอบ DOM ในคำสั่งเดียว อย่างไรก็ตาม [คำตอบของเปาโล] (# 698386) เป็นปลั๊กอินที่ดี;)
Seb

1
สำหรับคนที่มาที่นี่จาก google: ดูคำตอบของล็อตเตอรี่ง่ายมากและทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน
Maurice

1
@Maurice ฉันเปลี่ยนคำตอบที่ได้รับการยอมรับ
juan

1
นี่แค่สลับองค์ประกอบถ้าพวกมันอยู่ข้าง ๆ กัน! กรุณาเปลี่ยนคำตอบที่ยอมรับได้
Serhiy

คำตอบ:


233

ฉันพบวิธีที่น่าสนใจในการแก้ปัญหานี้โดยใช้ jQuery เท่านั้น:

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

หรือ

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

:)


2
ใช่สิ่งนี้ควรใช้งานได้: เอกสารบอกว่า: "ถ้าองค์ประกอบที่เลือกด้วยวิธีนี้ถูกแทรกไว้ที่อื่นมันจะถูกย้ายก่อนเป้าหมาย (ไม่ใช่โคลน)"
Ridcully

12
ฉันชอบตัวเลือกนี้ที่สุด! เรียบง่ายและเข้ากันได้ดี แม้ว่าฉันต้องการใช้ el1.insertBefore (el2) และ el1.insertAfter (el2) เพื่อให้สามารถอ่านได้
Maurice

6
แม้ว่า sidenote หนึ่ง .. (ซึ่งฉันเดาว่าใช้ได้กับโซลูชันทั้งหมดในหน้านี้) เนื่องจากเราจัดการ DOM ที่นี่ การลบและการเพิ่มไม่สามารถทำงานได้ดีเมื่อมี iframe อยู่ในองค์ประกอบที่คุณกำลังย้าย iframe จะโหลดซ้ำ นอกจากนี้มันจะโหลดไปที่ URL เดิมแทนที่จะเป็น URL ปัจจุบัน น่ารำคาญมาก แต่เกี่ยวข้องกับความปลอดภัย ฉันไม่พบวิธีแก้ปัญหาสำหรับเรื่องนี้
Maurice

202
สิ่งนี้จะไม่สลับสององค์ประกอบยกเว้นว่าทั้งสององค์ประกอบอยู่ติดกันทันที
BonyT

12
นี่ไม่ควรเป็นคำตอบที่ยอมรับได้อีกต่อไป มันไม่ทำงานในกรณีทั่วไป
thekingoftruth

79

ถูกต้องของเปาโล แต่ฉันไม่แน่ใจว่าทำไมเขาโคลนองค์ประกอบที่เกี่ยวข้อง สิ่งนี้ไม่จำเป็นจริงๆและจะสูญเสียการอ้างอิงหรือผู้ฟังเหตุการณ์ที่เกี่ยวข้องกับองค์ประกอบและลูกหลานของพวกเขา

นี่คือรุ่นที่ไม่ลอกแบบโดยใช้วิธี DOM ธรรมดา (เนื่องจาก jQuery ไม่มีฟังก์ชั่นพิเศษใด ๆ ที่จะทำให้การทำงานนี้ง่ายขึ้น):

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

2
docs.jquery.com/Clone - ส่งผ่าน "true" เพื่อโคลนเหตุการณ์เช่นกัน ฉันพยายามโดยไม่ลอกมันก่อน แต่มันก็ทำสิ่งที่คุณเป็นอยู่ในปัจจุบัน: มันสลับอันแรกกับอันที่สองและปล่อยอันแรกตามที่เป็นอยู่
เปาโล Bergantino

หากฉันมี <div id = "div1"> 1 </div> <div id = "div2"> 2 </div> และเรียก swapNodes (document.getElementById ('div1'), document.getElementById ('div2') ); ฉันได้รับ <div id = "div1"> 1 </div> <div id = "div2"> 1 </div>
Paolo Bergantino

3
อามีกรณีมุมที่พี่น้องคนต่อไปของตัวเองเป็น b แก้ไขในตัวอย่างข้างต้น jQuery กำลังทำสิ่งเดียวกัน
bobince

ฉันมีรายการที่มีความอ่อนไหวต่อการสั่งซื้อมากซึ่งจำเป็นต้องเก็บรักษากิจกรรมและรหัสและนี่เป็นงานที่มีเสน่ห์ ขอบคุณ!
Teekin

1
ฉันรู้สึกว่าคุณควรเพิ่ม jQuery version เพื่อให้เป็นไปตามเทคนิคของชื่อคำถามนี้ :)
thekingoftruth

70

ไม่ไม่มี แต่คุณสามารถชักหนึ่ง:

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

การใช้งาน:

$(selector1).swapWith(selector2);

โปรดทราบว่าจะใช้งานได้ก็ต่อเมื่อตัวเลือกตรงกับ 1 องค์ประกอบเท่านั้นมิฉะนั้นจะทำให้ได้ผลลัพธ์ที่แปลก


2
จำเป็นต้องทำการโคลนนิ่งพวกมันหรือไม่?
juan

บางทีคุณสามารถเขียนโดยตรงโดยใช้ html ()
Ed James

2
@Ed Woodcock ถ้าคุณทำคุณจะสูญเสียเหตุการณ์ที่ถูกผูกไว้กับองค์ประกอบเหล่านั้นฉันค่อนข้างแน่ใจ
alex

2
ใช้งานได้ดีเมื่อ divs จะไม่ติดกัน :)
เอลเมอ

นี่ควรเป็นคำตอบที่ยอมรับได้ มันขยาย jQuery เพื่อให้สิ่งที่ถูกถาม
Alice Wonder

40

มีปัญหาจำนวนมากเกี่ยวกับปัญหานี้ซึ่งไม่ได้รับการจัดการโดยคำตอบที่ยอมรับหรือคำตอบของ bobince โซลูชันอื่น ๆ ที่เกี่ยวข้องกับการโคลนนั้นมาถูกทาง แต่การโคลนนั้นมีราคาแพงและไม่จำเป็น เราถูกล่อลวงให้โคลนเพราะปัญหาอายุของการแลกเปลี่ยนสองตัวแปรซึ่งหนึ่งในขั้นตอนคือการกำหนดหนึ่งในตัวแปรให้เป็นตัวแปรชั่วคราว การมอบหมาย (การโคลน) ในกรณีนี้ไม่จำเป็น นี่คือโซลูชันที่ใช้ jQuery:

function swap(a, b) {
    a = $(a); b = $(b);
    var tmp = $('<span>').hide();
    a.before(tmp);
    b.before(a);
    tmp.replaceWith(b);
};

7
นี่ควรเป็นคำตอบที่ยอมรับได้ การโคลนจะลบทริกเกอร์เหตุการณ์ใด ๆ และไม่จำเป็น ฉันใช้วิธีการที่แน่นอนนี้ในรหัสของฉัน
thekingoftruth

1
นี่คือคำตอบที่ดีกว่า! ฉันมีความสุขที่ได้มี ul เป็นลูกขององค์ประกอบที่สับเปลี่ยนซึ่งเป็นองค์ประกอบที่จัดเรียง jquery หลังจากแลกเปลี่ยนกับคำตอบของเปาโลเบอร์กันติโนแล้วรายการก็จะไม่สามารถถูกทิ้งได้อีกต่อไป ด้วยคำตอบของผู้ใช้ 2820356 ทุกอย่างยังคงทำงานได้ดี
agoldev

20

คำตอบเหล่านี้จำนวนมากผิดปกติสำหรับกรณีทั่วไปคำตอบอื่น ๆ มีความซับซ้อนโดยไม่จำเป็นหากจริง ๆ แล้วพวกเขายังใช้งานได้ jQuery .beforeและ.afterเมธอดทำสิ่งที่คุณต้องการทำส่วนใหญ่ แต่คุณจำเป็นต้องมีองค์ประกอบที่ 3 ในการทำงานของอัลกอริทึมการแลกเปลี่ยนหลายอย่าง มันค่อนข้างง่าย - สร้างองค์ประกอบ DOM ชั่วคราวเป็นตัวยึดตำแหน่งขณะที่คุณย้ายสิ่งต่าง ๆ ไม่จำเป็นต้องดูพ่อแม่หรือพี่น้องและไม่จำเป็นต้องโคลน ...

$.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) วาง div ชั่วคราวไว้tempก่อนthis

2) ย้ายthisก่อนthat

3) ย้ายthatหลังจากtemp

3b) ลบ temp

จากนั้นก็ง่ายๆ

$(selectorA).swapWith(selectorB);

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


2
นี่คือคำตอบที่ดีที่สุดส่วนอื่น ๆ ทั้งหมดถือว่าองค์ประกอบอยู่ติดกัน ใช้ได้กับทุกสถานการณ์
brohr

11

คุณไม่จำเป็นต้องมีสองโคลนหนึ่งจะทำ รับ Paolo Bergantino ตอบเรามี:

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

ควรจะเร็วกว่า การผ่านองค์ประกอบที่น้อยลงของทั้งสองควรทำให้สิ่งต่าง ๆ เร็วขึ้น


4
ปัญหาเกี่ยวกับสิ่งนี้และของเปาโลคือพวกเขาไม่สามารถแลกเปลี่ยนองค์ประกอบที่มี ID ได้เพราะ ID จะต้องไม่ซ้ำกันดังนั้นการโคลนนิ่งจะไม่ทำงาน โซลูชันของ Bobince ทำงานได้ในกรณีนี้
Ruud

ปัญหาอีกข้อคือสิ่งนี้จะลบกิจกรรมออกจาก copy_to
Jan Willem B

8

ฉันใช้เทคนิคแบบนี้มาก่อน ฉันใช้สำหรับรายการตัวเชื่อมต่อบน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');

นี่คือทางออกที่ดีที่สุดของ IMO แต่ไม่จำเป็นend()ถ้าคุณยังไม่ดำเนินการต่อ แล้ว$('element1').clone().before('element2').end().replaceWith('element2');
robisrob

1
เพิ่งสังเกตเห็นclone()ไม่ได้รักษา jQuery ใด ๆdata()เว้นแต่คุณจะระบุclone(true) - ความแตกต่างที่สำคัญถ้าคุณกำลังเรียงลำดับดังนั้นคุณจะไม่สูญเสียข้อมูลทั้งหมดของคุณ
robisrob

3

ฉันได้ทำฟังก์ชั่นที่ให้คุณเลื่อนตัวเลือกที่เลือกไว้หลายตัวขึ้นหรือลง

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

อ้างอิง:

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

ก่อนอื่นจะตรวจสอบว่าตัวเลือกแรก / ตัวเลือกสุดท้ายสามารถเลื่อนขึ้น / ลงได้หรือไม่ จากนั้นจะวนซ้ำผ่านองค์ประกอบทั้งหมดและการโทร

swapWith (element.next () หรือ 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);
}

สิ่งนี้ไม่มีประสิทธิภาพทั้งในวิธีการเขียนโค้ดและวิธีการทำงาน
Blaise

มันไม่ใช่คุณสมบัติที่สำคัญของแอพของเราและฉันแค่ใช้มันพร้อมตัวเลือกจำนวนน้อย ฉันต้องการบางสิ่งที่รวดเร็วและง่ายดาย ... ฉันจะรักถ้าคุณสามารถปรับปรุงสิ่งนี้! มันจะดีมาก!
Tom Maeckelberghe

3

อีกอันหนึ่งที่ไม่มีการโคลน:

ฉันมีองค์ประกอบจริงและมีการแลกเปลี่ยน:

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

แล้วก็ insert <div> beforeและremoveหลังจากนั้นมีความจำเป็นเท่านั้นถ้าคุณไม่สามารถตรวจสอบให้แน่ใจว่ามีอยู่เสมอเป็นองค์ประกอบ befor (ในกรณีของฉันมันเป็น)


หรือคุณสามารถตรวจสอบ.prev()ความยาวของและถ้าเป็น 0, .prepend()ถึง.parent():) วิธีนี้คุณไม่จำเป็นต้องสร้างองค์ประกอบเพิ่มเติม
jave.web

2

ลองดูที่ jQuery plugin "สลับได้"

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

มันสร้างขึ้นบน "Sortable" และดูเหมือนว่าสามารถเรียงได้ (drag-n-drop, placeholder, ฯลฯ ) แต่เพียงสลับสององค์ประกอบ: ลากแล้วปล่อย องค์ประกอบอื่น ๆ ทั้งหมดจะไม่ได้รับผลกระทบและยังคงอยู่ในตำแหน่งปัจจุบัน


2

นี่คือคำตอบตาม@lotifตรรกะการตอบของแต่เป็นการวางนัยทั่วไปมากกว่า

หากคุณต่อท้าย / เสริมหลัง / ก่อนที่องค์ประกอบจะถูกย้าย จริง
=> ไม่จำเป็นต้องมีการโคลน
=> เหตุการณ์ที่เก็บไว้

มีสองกรณีที่สามารถเกิดขึ้นได้

  1. เป้าหมายหนึ่งมีบางอย่าง " .prev()ious" => เราสามารถใส่เป้าหมายอื่น.after()ได้
  2. เป้าหมายหนึ่งคือลูกคนแรกของมัน.parent()=> เราสามารถ.prepend()กำหนดเป้าหมายอื่นให้กับผู้ปกครองได้

รหัส

รหัสนี้สามารถทำได้สั้นกว่านี้ แต่ฉันเก็บไว้เพื่อความสะดวกในการอ่าน ทราบว่าผู้ปกครอง prestoring (ถ้าจำเป็น) และองค์ประกอบก่อนหน้านี้เป็นสิ่งจำเป็น

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

});

... รู้สึกอิสระในการห่อโค้ดด้านในฟังก์ชั่น :)

ตัวอย่างซอมีการคลิกเหตุการณ์พิเศษที่แนบมาเพื่อแสดงให้เห็นถึงการเก็บรักษาเหตุการณ์ ...
ตัวอย่างซอ: https://jsfiddle.net/ewroodqa/

... จะใช้งานได้ในหลายกรณี - แม้แต่กรณีเดียวเช่น:

<div>
  <div id="one">ONE</div>
</div>
<div>Something in the middle</div>
<div>
  <div></div>
  <div id="two">TWO</div>
</div>

2

นี่เป็นวิธีการแก้ปัญหาของฉันในการย้ายองค์ประกอบลูกหลายรายการขึ้นและลงภายในองค์ประกอบหลัก ทำงานได้ดีสำหรับการย้ายตัวเลือกที่เลือกใน listbox ( <select multiple></select>)

ขยับขึ้น:

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

ย้ายลง:

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


1

ฉันต้องการแม่มดแก้ปัญหาไม่ได้ใช้โคลน () เพราะมันมีผลข้างเคียงกับเหตุการณ์ที่แนบมานี่คือสิ่งที่ฉันทำ

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

ต้องไม่ใช้กับวัตถุ jQuery ที่มีองค์ประกอบ DOM มากกว่าหนึ่งรายการ


1

หากคุณมีหลายองค์ประกอบของแต่ละองค์ประกอบคุณต้องทำบางสิ่งบางอย่างในวงโดยธรรมชาติ ฉันมีสถานการณ์เช่นนี้เมื่อเร็ว ๆ นี้ องค์ประกอบการทำซ้ำสองที่ฉันต้องการสลับมีคลาสและ div คอนเทนเนอร์ดังนี้:

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

รหัสต่อไปนี้อนุญาตให้ฉันวนซ้ำทุกอย่างและย้อนกลับ ...

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

1

ฉันทำมันด้วยตัวอย่างนี้

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

1

ฉันทำตารางสำหรับเปลี่ยนลำดับของ obj ในฐานข้อมูลที่ใช้. after () .before () ดังนั้นนี่คือสิ่งที่ฉันได้ทำการทดลอง

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

คือการแทรก obj1 ก่อน obj2 และ

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

ทำในทางกลับกัน

ดังนั้นหาก obj1 อยู่หลัง obj3 และ obj2 หลังจาก obj4 และถ้าคุณต้องการเปลี่ยนสถานที่ obj1 และ obj2 คุณจะทำเช่นนั้น

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

สิ่งนี้ควรทำด้วย BTW คุณสามารถใช้. prev () และ. next () เพื่อค้นหา obj3 และ obj4 หากคุณไม่มีดัชนีสำหรับประเภทนี้อยู่แล้ว


ไม่ใช่นี่เป็นเพียงจุดเริ่มต้นของคำตอบที่ยอมรับ แต่ก็มีข้อผิดพลาดทางไวยากรณ์ที่ไม่ดี คุณคัดลอกสิ่งนี้หรือไม่
clockw0rk

ไม่เอ๊ะ นี่เป็นรหัสที่ใช้งานได้สำหรับ บริษัท ของฉันก่อนหน้านี้ ข้างฉันแน่ใจว่าจะชี้ให้เห็นถึงวิธีการใช้และในสถานการณ์ที่ ทำไมฉันต้องฉ้อใครบางคน? ฉันยังให้วิธีการรับดัชนีของวัตถุซึ่งเป็นสาเหตุที่คำตอบที่ได้รับการยอมรับแสดงความคิดเห็นว่ายังไม่เสร็จ
Tran Vu Dang Khoa

แล้วทำไมมันเป็น $ () แทนที่จะเป็นหลัง ($ ())
clockw0rk

โอ้ใช่ฉันไม่เห็นขอบคุณ ฉันป้อนจากหน่วยความจำไม่มีสิทธิ์เก็บรหัสนั้นหลังจากฉันออกจาก บริษัท
Tran Vu Dang Khoa

0

ถ้า nodeA และ nodeB เป็นพี่น้องชอบสอง<tr>สิ่งเดียวกัน<tbody>คุณสามารถใช้$(trA).insertAfter($(trB))หรือ$(trA).insertBefore($(trB))สลับมันได้ผลสำหรับฉัน และคุณไม่จำเป็นต้องโทรหา$(trA).remove()ก่อนอื่นคุณต้องผูกกิจกรรมการคลิกบางส่วนอีกครั้ง$(trA)



-2

ตัวเลือกที่ดีที่สุดคือการโคลนด้วยวิธี clone ()


1
สิ่งนี้จะลบการเรียกกลับและการโยงใด ๆ
thekingoftruth

-2

ฉันคิดว่าคุณสามารถทำได้ง่ายมาก ตัวอย่างเช่นสมมติว่าคุณมีโครงสร้างถัดไป: ...

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

และผลลัพธ์ควรเป็น

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

jQuery:

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

ฉันหวังว่ามันจะช่วย!


2
วิธีการแก้ปัญหานี้ชี้ให้เห็นแล้วและใช้งานไม่ได้เว้นแต่ว่าทั้งสององค์ประกอบจะอยู่ติดกันทันที
BernaMariano
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.