Existe-t-il une fonction jQuery native pour changer d'éléments?

174

Puis-je facilement échanger deux éléments avec jQuery?

Je cherche à faire cela avec une seule ligne si possible.

J'ai un élément de sélection et j'ai deux boutons pour déplacer les options vers le haut ou vers le bas, et j'ai déjà les sélecteurs sélectionnés et les sélecteurs de destination en place, je le fais avec un si, mais je me demandais s'il existe un moyen plus simple.

Juan
la source
Pouvez-vous publier un exemple de balisage et de code?
Konstantin Tarkus
Ce n'est pas une question de jQuery mais de JavaScript: vous ne pouvez pas permuter les éléments DOM en une seule instruction. Cependant, [réponse de Paolo] (# 698386) est un excellent plugin;)
Seb
1
Pour les personnes venant ici de google: consultez la réponse de lotif, très simple et a parfaitement fonctionné pour moi.
Maurice
1
@Maurice, j'ai changé la réponse acceptée
juan
1
Cela n'échange les éléments que s'ils sont côte à côte! Veuillez modifier la réponse acceptée.
Serhiy

Réponses:

233

J'ai trouvé un moyen intéressant de résoudre ce problème en utilisant uniquement jQuery:

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

ou

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

:)

lotif
la source
2
Oui, cela devrait fonctionner: La documentation dit: "Si un élément sélectionné de cette façon est inséré ailleurs, il sera déplacé avant la cible (non cloné)".
Ridcully
12
J'aime cette option le plus! Simple et bien compatible. Bien que j'aime utiliser el1.insertBefore (el2) et el1.insertAfter (el2) pour la lisibilité.
Maurice
6
Une remarque cependant ... (qui, je suppose, s'applique à toutes les solutions de cette page) puisque nous manipulons le DOM ici. La suppression et l'ajout ne fonctionnent pas correctement lorsqu'il y a une iframe présente dans l'élément que vous déplacez. L'iframe se rechargera. De plus, il se rechargera vers son URL d'origine au lieu de celle actuelle. Très ennuyeux mais lié à la sécurité. Je n'ai pas trouvé de solution pour cela
Maurice
202
cela n'échange pas deux éléments à moins que les deux éléments soient immédiatement côte à côte.
BonyT
12
Cela ne devrait plus être la réponse acceptée. Cela ne fonctionne pas dans le cas généralisé.
thekingoftruth
79

Paulo a raison, mais je ne sais pas pourquoi il clone les éléments concernés. Ce n'est pas vraiment nécessaire et perdra toutes les références ou écouteurs d'événements associés aux éléments et à leurs descendants.

Voici une version sans clonage utilisant des méthodes DOM simples (puisque jQuery n'a pas vraiment de fonctions spéciales pour faciliter cette opération particulière):

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
la source
2
docs.jquery.com/Clone - le passer "true" clone également les événements. J'ai essayé sans le cloner au préalable, mais il faisait ce que vous faites actuellement: il échange le premier avec le deuxième et laisse le premier tel quel.
Paolo Bergantino
si j'ai <div id = "div1"> 1 </div> <div id = "div2"> 2 </div> et appelle swapNodes (document.getElementById ('div1'), document.getElementById ('div2') ); j'obtiens <div id = "div1"> 1 </div> <div id = "div2"> 1 </div>
Paolo Bergantino
3
Ah, il y a un cas critique où le prochain frère de a est b lui-même. Corrigé dans l'extrait de code ci-dessus. jQuery faisait exactement la même chose.
bobince
J'ai une liste très sensible à l'ordre qui doit conserver les événements et les identifiants et cela fonctionne comme un charme! Merci!
Teekin
1
J'ai l'impression que vous devriez ajouter une version jQuery pour satisfaire techniquement le titre de cette question. :)
thekingoftruth
70

Non, il n'y en a pas, mais vous pouvez en préparer un:

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

Notez que cela ne fonctionne que si les sélecteurs ne correspondent qu'à 1 élément chacun, sinon cela pourrait donner des résultats étranges.

Paolo Bergantino
la source
2
est-il nécessaire de les cloner?
juan
Peut-être pourriez-vous les écrire directement en utilisant html ()?
Ed James
2
@Ed Woodcock Si vous le faites, vous perdrez des événements liés sur ces éléments, j'en suis presque sûr.
alex
2
Fonctionne très bien lorsque les divs ne sont pas côte à côte :)
Elmer
Cela devrait être la réponse acceptée. Il étend jQuery pour fournir ce qui a été demandé.
Alice Wonder le
40

Il y a beaucoup de cas extrêmes à ce problème, qui ne sont pas traités par la réponse acceptée ou la réponse de bobince. D'autres solutions qui impliquent le clonage sont sur la bonne voie, mais le clonage est coûteux et inutile. Nous sommes tentés de cloner, à cause du problème séculaire de la permutation de deux variables, dans lequel l'une des étapes consiste à affecter l'une des variables à une variable temporaire. L'affectation, (clonage), dans ce cas n'est pas nécessaire. Voici une solution basée sur jQuery:

function swap(a, b) {
    a = $(a); b = $(b);
    var tmp = $('<span>').hide();
    a.before(tmp);
    b.before(a);
    tmp.replaceWith(b);
};
user2820356
la source
7
Cela devrait être la réponse acceptée. Le clonage supprime tous les déclencheurs d'événements et n'est pas nécessaire. J'utilise cette méthode exacte dans mon code.
thekingoftruth
1
C'est la meilleure réponse! J'ai eu le plaisir d'avoir un ul en tant qu'enfant de mes éléments échangés, qui était un élément triable jquery. Après avoir échangé avec la réponse de Paolo Bergantino, les éléments de la liste ne pouvaient plus être supprimés. Avec la réponse de user2820356, tout fonctionnait toujours bien.
agoldev
21

Beaucoup de ces réponses sont tout simplement fausses pour le cas général, d'autres sont inutilement compliquées si elles fonctionnent même. Le jQuery .beforeet les .afterméthodes font la plupart de ce que vous voulez faire, mais vous avez besoin d'un 3ème élément comme de nombreux algorithmes d'échange. C'est assez simple: créez un élément DOM temporaire comme espace réservé pendant que vous déplacez les choses. Il n'est pas nécessaire de regarder les parents ou les frères et sœurs, et certainement pas besoin de cloner ...

$.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) mettre le div temporaire temp avantthis

2) se déplacer thisavantthat

3) se déplacer thataprèstemp

3b) supprimer temp

Puis simplement

$(selectorA).swapWith(selectorB);

DÉMO: http://codepen.io/anon/pen/akYajE

robisrob
la source
2
C'est la meilleure réponse, toutes les autres supposent que les éléments sont les uns à côté des autres. Cela fonctionne dans toutes les situations.
brohr
11

Vous ne devriez pas avoir besoin de deux clones, un seul suffira. En prenant la réponse de Paolo Bergantino, nous avons:

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

Ça devrait être plus rapide. Passer le plus petit des deux éléments devrait également accélérer les choses.

Matthew Wilcoxson
la source
4
Le problème avec ceci, et avec celui de Paolo, est qu'ils ne peuvent pas échanger des éléments avec un ID car les ID doivent être uniques pour que le clonage ne fonctionne pas. La solution de Bobince fonctionne dans ce cas.
Ruud
Un autre problème est que cela supprimera les événements de copy_to.
Jan Willem B
8

J'ai utilisé une technique comme celle-ci avant. Je l'utilise pour la liste des connecteurs sur 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
la source
c'est la meilleure solution imo, mais inutile end()si vous ne continuez pas la chaîne. que diriez-vous$('element1').clone().before('element2').end().replaceWith('element2');
robisrob
1
juste remarqué clone()ne conserve aucune jquery data()sauf si vous spécifiez clone(true) - une distinction importante si vous triez pour ne pas perdre toutes vos données
robisrob
3

J'ai créé une fonction qui vous permet de déplacer plusieurs options sélectionnées vers le haut ou vers le bas

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

Dépendances:

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

Tout d'abord, il vérifie si la première / dernière option sélectionnée est capable de monter / descendre. Ensuite, il parcourt tous les éléments et appelle

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
la source
Ceci est inefficace, à la fois dans la manière dont le code est écrit et dans ses performances.
Blaise
Ce n'était pas une fonctionnalité majeure de notre application et je l'utilise simplement avec de petites options. Je voulais juste quelque chose de rapide et facile ... J'adorerais si vous pouviez améliorer cela! Ce serait génial!
Tom Maeckelberghe
3

un autre sans clonage:

J'ai un élément réel et un élément nominal à échanger:

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

puis insert <div> beforeet removeaprès ne sont nécessaires que si vous ne pouvez pas vous assurer qu'il y a toujours un élément avant (dans mon cas, c'est le cas)

demi-bit
la source
Ou vous pouvez simplement vérifier .prev()la longueur de s, et si 0, alors .prepend()à .parent():) De cette façon, vous n'avez pas besoin de créer des éléments supplémentaires.
jave.web
2

jetez un œil au plugin jQuery "Swapable"

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

il est construit sur "Sortable" et semble triable (glisser-déposer, espace réservé, etc.) mais n'échange que deux éléments: glisser-déposer. Tous les autres éléments ne sont pas affectés et restent sur leur position actuelle.

vadimk
la source
2

Ceci est une réponse basée sur @lotif la logique de réponse de , mais un peu plus généralisée

Si vous ajoutez / préférez après / avant que les éléments ne soient réellement déplacés
=> aucun clonage nécessaire
=> événements conservés

Il y a deux cas qui peuvent arriver

  1. Une cible a quelque chose de " .prev()ious" => nous pouvons mettre l'autre cible.after() qui.
  2. Une cible est le premier enfant de it's .parent()=> nous pouvons .prepend()l'autre cible parent.

Le code

Ce code pourrait être fait encore plus court, mais je l'ai gardé de cette façon pour plus de lisibilité. Notez que la pré-sauvegarde des parents (si nécessaire) et des éléments précédents est obligatoire.

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

});

... n'hésitez pas à envelopper le code interne dans une fonction :)

L'exemple de violon a des événements de clic supplémentaires associés pour démontrer la préservation de l'événement ...
Exemple de violon: https://jsfiddle.net/ewroodqa/

... fonctionnera pour divers cas - même un tel que:

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

C'est ma solution pour déplacer plusieurs éléments enfants de haut en bas à l'intérieur de l'élément parent. Fonctionne bien pour déplacer les options sélectionnées dans la zone de liste ( <select multiple></select>)

Déplacer vers le haut:

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

Descendre:

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

Je voulais une solution qui n'utilise pas clone () car elle a un effet secondaire avec les événements attachés, voici ce que j'ai fini par faire

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

il ne doit pas être utilisé avec des objets jQuery contenant plus d'un élément DOM

Mistic
la source
1

Si vous avez plusieurs copies de chaque élément, vous devez faire quelque chose en boucle naturellement. J'ai eu cette situation récemment. Les deux éléments répétitifs dont j'avais besoin pour changer avaient des classes et un conteneur div comme suit:

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

Le code suivant m'a permis de parcourir tout et d'inverser ...

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

Je l'ai fait avec cet extrait

// 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
la source
1

J'ai fait une table pour changer l'ordre d'obj dans la base de données utilisée .after () .before (), donc c'est de ce que j'ai expérimenté.

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

Insère obj1 avant obj2 et

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

faites l'inverse.

Donc, si obj1 est après obj3 et obj2 après obj4, et si vous voulez changer de place obj1 et obj2, vous le ferez comme

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

Cela devrait le faire. BTW vous pouvez utiliser .prev () et .next () pour trouver obj3 et obj4 si vous n'avez pas déjà une sorte d'index pour cela.

Tran Vu Dang Khoa
la source
Ce n'est pas juste une arnaque de la réponse acceptée, il contient une mauvaise erreur syntaxique. Avez-vous copypasta ceci?
clockw0rk
hein non ?? Il s'agit d'un code fonctionnel pour mon travail dans mon entreprise précédente. A côté, je me suis assuré de préciser comment utiliser et dans quelle situation. Pourquoi dois-je arnaquer quelqu'un? Je donne également comment obtenir l'index de l'objet, c'est pourquoi la réponse acceptée a été commentée comme non terminée.
Tran Vu Dang Khoa
alors pourquoi est-ce après $ () au lieu après ($ ())
clockw0rk
oh ouais je ne l'ai pas vu, merci. Je le saisis de mémoire, je n'ai pas le droit de conserver ce code après avoir quitté l'entreprise
Tran Vu Dang Khoa
0

si nodeA et nodeB sont des frères et sœurs, en aime deux <tr>dans le même <tbody>, vous pouvez simplement les utiliser $(trA).insertAfter($(trB))ou $(trA).insertBefore($(trB))les échanger, cela fonctionne pour moi. et vous n'avez pas besoin d'appeler $(trA).remove()avant, sinon vous devez relier certains événements de clic sur$(trA)

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

Créez une fonction jQuery comme celle-ci

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

Merci à Yannick Guinness sur https://jsfiddle.net/ARTsinn/TVjnr/

Airy
la source
-2

La meilleure option est de les cloner avec la méthode clone ().

user84771
la source
1
cela supprime tous les rappels et liaisons
thekingoftruth
-2

Je pense que vous pouvez le faire très simplement. Par exemple, disons que vous avez la structure suivante: ...

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

et le résultat devrait être

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

jquery:

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

J'espère que ça aide!

Jernej Gololicic
la source
2
cette solution a déjà été signalée et ne fonctionne pas, sauf si les deux éléments sont immédiatement côte à côte.
BernaMariano