jQuery - Déclenche un événement lorsqu'un élément est supprimé du DOM

211

J'essaie de comprendre comment exécuter du code js lorsqu'un élément est supprimé de la page:

jQuery('#some-element').remove(); // remove some element from the page
/* need to figure out how to independently detect the above happened */

y a-t-il un événement adapté à cela, quelque chose comme:

jQuery('#some-element').onremoval( function() {
    // do post-mortem stuff here
});

Merci.

sa125
la source
1
par curiosité, que voudrait faire de l'élément qui a été supprimé?
Natrium
8
J'ai un élément qui s'attache indépendamment à la pièce que je supprime, donc je veux détecter quand cette pièce est partie pour éliminer cet élément également. Je pourrais reconcevoir le tout, mais accomplir ce qui précède me fera gagner beaucoup de temps (et de code).
sa125

Réponses:

118

Juste vérifié, il est déjà intégré dans la version actuelle de JQuery:

jQuery - v1.9.1

jQuery UI - v1.10.2

$("#myDiv").on("remove", function () {
    alert("Element was removed");
})

Important : il s'agit de la fonctionnalité du script d' interface utilisateur Jquery (pas JQuery), vous devez donc charger les deux scripts (jquery et jquery-ui) pour le faire fonctionner. Voici un exemple: http://jsfiddle.net/72RTz/

Philipp Munin
la source
11
Ce fut très utile. J'ai appris que cette fonctionnalité se trouve dans le composant "Widget" de jQuery UI si vous ne cherchez pas à télécharger l'intégralité de la bibliothèque d'interface utilisateur.
Neil
5
Est-ce documenté quelque part? Je viens de parcourir la documentation du widget jQuery UI et je n'ai pas trouvé de mention à ce sujet. Je voudrais voir si cela est officiellement pris en charge / des mises en garde sur son utilisation ...
josh
Celui-ci ne fonctionne pas - essayé dans jQuery 1.10.2, mais la réponse ci-dessous par @mtkopone fonctionne parfaitement, donc je voterais pour la mise à jour de la réponse dans cette question
Marcin
20
Celui-ci ne fonctionne que partiellement. Si vous faites removesur l'élément, il se déclenche, mais si l'élément est détruit d'une autre manière, en étant écrasé par exemple, cela ne fonctionne pas.
Carl Smith
Vous avez peut-être raison, il existe probablement un autre nom d'événement pour ce cas. Ce serait génial si vous pouviez fournir un échantillon jsfiddle pour votre cas.
Philipp Munin
195

Vous pouvez utiliser des événements spéciaux jQuery pour cela.

En toute simplicité,

Installer:

(function($){
  $.event.special.destroyed = {
    remove: function(o) {
      if (o.handler) {
        o.handler()
      }
    }
  }
})(jQuery)

Usage:

$('.thing').bind('destroyed', function() {
  // do stuff
})

Addendum pour répondre aux commentaires de Pierre et DesignerGuy:

Pour ne pas déclencher le rappel lors de l'appel $('.thing').off('destroyed'), remplacez la condition if par:if (o.handler && o.type !== 'destroyed') { ... }

mtkopone
la source
15
Solution vraiment sympa. Ben Alman a un bel article sur les événements spéciaux pour plus d'informations: benalman.com/news/2010/03/jquery-special-events
antti_s
11
+1 avait réussi à manquer complètement ces événements spéciaux jusqu'à présent, sacrément utile! Une chose à déclarer après avoir implémenté ce qui précède, il serait préférable de passer o.handler()à o.handler.apply(this,arguments)sinon l'événement et les objets de données ne sont pas passés par l'écouteur d'événement.
Pebbl
6
Cependant, cela ne fonctionne pas lorsque vous supprimez des éléments sans jQuery.
djjeck
5
cela ne fonctionne pas a) lorsque les éléments sont détachés au lieu d'être supprimés ou b) lorsque certaines anciennes bibliothèques non jquery utilisent innerHTML pour détruire vos éléments (similaire à ce que djjeck a dit)
Charon ME
5
Attention ce gestionnaire est appelé quand vous $('.thing').unbind('destroyed')ce qui pourrait vraiment être gênant (puisque délier signifie que nous ne voulons pas que le gestionnaire soit appelé ...)
Pierre
54

Vous pouvez vous lier à l'événement DOMNodeRemoved (partie de la spécification DOM niveau 3 WC3).

Fonctionne dans IE9, les dernières versions de Firefox et Chrome.

Exemple:

$(document).bind("DOMNodeRemoved", function(e)
{
    alert("Removed: " + e.target.nodeName);
});

Vous pouvez également recevoir une notification lorsque des éléments sont insérés en se liant à DOMNodeInserted

Adam
la source
13
Remarque: "L'ajout d'écouteurs de mutation DOM à un document dégrade profondément les performances des modifications DOM ultérieures à ce document (ce qui les rend 1,5 à 7 fois plus lents!)." de: developer.mozilla.org/en/DOM/Mutation_events
Matt Crinklaw-Vogt
1
J'adore ça, bien que nodeName soit rarement utile pour moi. J'utilise juste e.target.classNameou if ($(e.target).hasClass('my-class')) { ....
Micah Alcorn
Cela ne fonctionne pas sur l'élément lui-même, seulement sur ses enfants.
Xdg
Réponse incroyable! M'a vraiment aidé!
Kir Mazur
Cela peut être lent et une mauvaise idée pour le code en production, mais cela ressemble à la seule méthode qui fonctionne de manière synchrone (contrairement à MutationObserver) et pour n'importe quel nœud (pas seulement les nœuds supprimés via jQuery). C'est une excellente option pour le débogage.
CertainPerformance
38

Il n'y a pas d'événement intégré pour supprimer des éléments, mais vous pouvez en créer un en étirant la méthode de suppression par défaut de jQuery. Notez que le rappel doit être appelé avant de le supprimer pour conserver la référence.

(function() {
    var ev = new $.Event('remove'),
        orig = $.fn.remove;
    $.fn.remove = function() {
        $(this).trigger(ev);
        return orig.apply(this, arguments);
    }
})();

$('#some-element').bind('remove', function() {
    console.log('removed!');
    // do pre-mortem stuff here
    // 'this' is still a reference to the element, before removing it
});

// some other js code here [...]

$('#some-element').remove();

Remarque: certains problèmes avec cette réponse ont été soulignés par d'autres affiches.

  1. Cela ne fonctionnera pas lorsque le nœud est supprimé via html() replace() ou d'autres méthodes jQuery
  2. Cet événement bouillonne
  3. jQuery UI remplace également la suppression

La solution la plus élégante à ce problème semble être: https://stackoverflow.com/a/10172676/216941

David Hellsing
la source
2
Merci pour ça! Un petit ajout: parce que l'événement remove bouillonne, vous le recevriez également lorsqu'un enfant est supprimé, alors mieux vaut écrire le gestionnaire de cette façon:$('#some-element').bind('remove', function(ev) { if (ev.target === this) { console.log('removed!'); } });
meyertee
3
Cela n'a pas fonctionné pour moi - j'ai dû renvoyer le résultat d'orig.apply.
fturtle
1
En fait, @Adam, c'est vrai, mais il est compatible avec tous les navigateurs. Avec les ajouts de meyertee / fturtle, c'est une solution parfaitement fiable, tant que vous ne supprimez que les éléments avec cette méthode, plutôt que de modifier / vider le HTML, etc. vous devez écouter les événements commerciaux dans une application, et non ceux dont la structure est susceptible de changer avec le développement. De plus, la souscription aux événements de mutation DOM signifie que votre programme est potentiellement susceptible de prendre du retard dans les hiérarchies DOM complexes.
Will Morgan
1
Mon seul désaccord sur l'exactitude est la déclaration - 'il n'y a pas d'événement intégré pour supprimer des éléments' --- il y a un événement intégré pour les navigateurs qui implémentent les événements DOM de niveau 3 (comme détaillé dans ma réponse).
Adam
4
Bien que cela détecte les éléments supprimés à l'aide de la fonction «supprimer», il ne parvient pas à détecter les éléments supprimés par d'autres moyens (par exemple, en utilisant le HTML de jQuery, replace, etc.). Veuillez voir ma réponse pour une solution plus complète.
zah
32

Le crochet .remove()n'est pas le meilleur moyen de gérer cela, car il existe de nombreuses façons de supprimer des éléments de la page (par exemple, en utilisant .html(),.replace() etc.).

Afin d'éviter divers risques de fuite de mémoire, jQuery tentera en interne d'appeler la fonction jQuery.cleanData()pour chaque élément supprimé, quelle que soit la méthode utilisée pour le supprimer.

Voir cette réponse pour plus de détails: fuites de mémoire javascript

Donc, pour de meilleurs résultats, vous devez accrocher la cleanDatafonction, ce qui est exactement ce que fait le plugin jquery.event.destroyed :

http://v3.javascriptmvc.com/jquery/dist/jquery.event.destroyed.js

zah
la source
Cette idée cleanDatam'a été très utile! Merci beaucoup, Joe :)
Petr Vostrel
1
Belle réponse, mais cela ne fonctionne que pour les méthodes jQuery. Si vous intégrez avec une autre plate-forme qui peut "retirer le tapis" de dessous vous - la réponse d'Adam est la plus logique.
Bron Davies
7

Pour ceux qui utilisent jQuery UI:

jQuery UI a substituée quelques - unes des méthodes de jQuery pour mettre en œuvre un removeévénement qui est piloté non seulement lorsque vous supprimez explicitement l'élément donné, mais aussi si l'élément est alors retiré du DOM par toutes les méthodes jQuery auto-nettoyage (par exemple replace, htmletc.) . Cela vous permet essentiellement de mettre un crochet dans les mêmes événements qui se déclenchent lorsque jQuery "nettoie" les événements et les données associés à un élément DOM.

John Resig a indiqué qu'il était ouvert à l'idée d'implémenter cet événement dans une future version de jQuery core, mais je ne sais pas où il en est actuellement.

StriplingWarrior
la source
6

Seul jQuery est requis (aucune interface utilisateur jQuery requise)

( J'ai extrait cette extension du framework jQuery UI )

Fonctionne avec: empty()et html()etremove()

$.cleanData = ( function( orig ) {
    return function( elems ) {
        var events, elem, i;
        for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {
            try {

                // Only trigger remove when necessary to save time
                events = $._data( elem, "events" );
                if ( events && events.remove ) {
                    $( elem ).triggerHandler( "remove" );
                }

            // Http://bugs.jquery.com/ticket/8235
            } catch ( e ) {}
        }
        orig( elems );
    };
} )( $.cleanData );

Avec cette solution, vous pouvez également dissocier le gestionnaire d'événements.

$("YourElemSelector").off("remove");

Essayez! - Exemple

$.cleanData = (function(orig) {
  return function(elems) {
    var events, elem, i;
    for (i = 0;
      (elem = elems[i]) != null; i++) {
      try {

        // Only trigger remove when necessary to save time
        events = $._data(elem, "events");
        if (events && events.remove) {
          $(elem).triggerHandler("remove");
        }

        // Http://bugs.jquery.com/ticket/8235
      } catch (e) {}
    }
    orig(elems);
  };
})($.cleanData);


$("#DivToBeRemoved").on("remove", function() {
  console.log("div was removed event fired");
});

$("p").on("remove", function() {
  console.log("p was removed event fired");
});

$("span").on("remove", function() {
  console.log("span was removed event fired");
});

// $("span").off("remove");

$("#DivToBeRemoved").on("click", function() {
  console.log("Div was clicked");
});

function RemoveDiv() {
  //       $("#DivToBeRemoved").parent().html("");    
  $("#DivToBeRemoved").remove();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3>OnRemove event handler attached to elements `div`, `p` and `span`.</h3>
<div class="container">
  <br>
  <button onclick="RemoveDiv();">Click here to remove div below</button>
  <div id="DivToBeRemoved">
    DIV TO BE REMOVED 
    contains 1 p element 
    which in turn contains a span element
    <p>i am p (within div)
      <br><br><span>i am span (within div)</span></p>
  </div>
</div>

Démo supplémentaire - jsBin

Légendes
la source
4

Je n'ai pas réussi à faire fonctionner cette réponse avec la dissociation (malgré la mise à jour, voir ici ), mais j'ai réussi à trouver un moyen de la contourner. La réponse a été de créer un événement spécial «destroy_proxy» qui a déclenché un événement «détruit». Vous mettez l'écouteur d'événement à la fois sur 'destroy_proxy' et sur 'destroy', puis lorsque vous souhaitez vous dissocier, vous venez de dissocier l'événement 'destroy':

var count = 1;
(function ($) {
    $.event.special.destroyed_proxy = {
        remove: function (o) {
            $(this).trigger('destroyed');
        }
    }
})(jQuery)

$('.remove').on('click', function () {
    $(this).parent().remove();
});

$('li').on('destroyed_proxy destroyed', function () {
    console.log('Element removed');
    if (count > 2) {
        $('li').off('destroyed');
        console.log('unbinded');
    }
    count++;
});

Voici un violon

Bill Johnston
la source
Qu'en est-il de la compatibilité du navigateur ici? Ça marche toujours?
Vladislav Rastrusny
3

J'aime la réponse de mtkopone en utilisant les événements spéciaux jQuery, mais notez que cela ne fonctionne pas a) lorsque les éléments sont détachés au lieu d'être supprimés ou b) lorsque certaines anciennes bibliothèques non jquery utilisent innerHTML pour détruire vos éléments

Charon ME
la source
3
J'ai rétrogradé parce que la question demandait explicitement l'utilisation de .remove (), pas le détachement. detachest surtout utilisé pour ne pas déclencher le nettoyage car l'élément est probablement prévu pour être réattaché plus tard. b) est toujours vrai et n'a pas encore de manipulation fiable, du moins depuis que les événements de mutation DOM doivent être largement mis en œuvre.
Pierre
4
Je parie que vous l'avez fait juste pour obtenir le badge "critique";)
Charon ME
1

Je ne suis pas sûr qu'il existe un descripteur d'événement pour cela, vous devez donc conserver une copie du DOM et comparer au DOM existant dans une sorte de boucle d'interrogation - ce qui pourrait être assez désagréable. Firebug le fait cependant - si vous inspectez le HTML et exécutez quelques modifications DOM, il met en évidence les changements en jaune dans la console Firebug pendant une courte période.

Alternativement, vous pouvez créer une fonction de suppression ...

var removeElements = function(selector) {
    var elems = jQuery(selector);

    // Your code to notify the removal of the element here...
    alert(elems.length + " elements removed");

    jQuery(selector).remove();
};

// Sample usage
removeElements("#some-element");
removeElements("p");
removeElements(".myclass");
Fenton
la source
1
+1 pour cette idée. Bien que vous puissiez également étendre jQuery (style de plugin) afin d'obtenir un appel jQuery plus standard comme: $ ('. ItemToRemove'). CustomRemove () ;. Vous pouvez également faire en sorte qu'il accepte un rappel comme paramètre.
user113716
1

Voici comment créer un écouteur de suppression en direct jQuery :

$(document).on('DOMNodeRemoved', function(e)
{
  var $element = $(e.target).find('.element');
  if ($element.length)
  {
    // do anything with $element
  }
});

Ou:

$(document).on('DOMNodeRemoved', function(e)
{
  $(e.target).find('.element').each(function()
  {
    // do anything with $(this)
  }
});
Buzogany Laszlo
la source
1
Ce serait formidable d'être libre de le faire. Malheureusement, ce n'est pas fiable, car les événements de mutation sont dépréciés: developer.mozilla.org/en-US/docs/Web/Guide/Events/…
Kamafeather
1

L'événement "remove" de jQuery fonctionne correctement, sans ajout. Il pourrait être plus fiable à temps d'utiliser une astuce simple, au lieu de patcher jQuery.

Modifiez ou ajoutez simplement un attribut dans l'élément que vous êtes sur le point de supprimer du DOM. Ainsi, vous pouvez déclencher n'importe quelle fonction de mise à jour, qui ignorera simplement les éléments en voie d'être détruits, avec l'attribut "do_not_count_it".

Supposons que nous ayons un tableau avec des cellules correspondant aux prix et que vous ne deviez afficher que le dernier prix: il s'agit du sélecteur à déclencher lorsqu'une cellule de prix est supprimée (nous avons un bouton dans chaque ligne du tableau pour le faire, non affiché ici)

$('td[validity="count_it"]').on("remove", function () {
    $(this).attr("validity","do_not_count_it");
    update_prices();
});

Et voici une fonction qui trouve le dernier prix du tableau, sans tenir compte du dernier, si c'est celui qui a été supprimé. En effet, lorsque l'événement "remove" est déclenché, et lorsque cette fonction est appelée, l'élément n'est pas encore supprimé.

function update_prices(){
      var mytable=$("#pricestable");
      var lastpricecell = mytable.find('td[validity="count_it"]').last();
}

Au final, la fonction update_prices () fonctionne très bien, et après cela, l'élément DOM est supprimé.

philippe
la source
0

se référant à la réponse @David:

Lorsque vous voulez le faire avec une autre fonction, par exemple. html () comme dans mon cas, n'oubliez pas d'ajouter return dans une nouvelle fonction:

(function() {
    var ev = new $.Event('html'),
        orig = $.fn.html;
    $.fn.html = function() {
        $(this).trigger(ev);
        return orig.apply(this, arguments);
    }
})();
Patryk M
la source
0

Ce.

$.each(
  $('#some-element'), 
        function(i, item){
            item.addEventListener('DOMNodeRemovedFromDocument',
                function(e){ console.log('I has been removed'); console.log(e);
                })
         })
Matas Vaitkevicius
la source
1
DOMNodeRemovedFromDocument ne semble pas être pris en charge dans Firefox. Essayez peut-être plutôt MutationObserver ?
EricP
0

une extension de la réponse d' Adam au cas où vous auriez besoin de prévaloir par défaut, voici un contournement:

$(document).on('DOMNodeRemoved', function(e){
        if($(e.target).hasClass('my-elm') && !e.target.hasAttribute('is-clone')){
            let clone = $(e.target).clone();
            $(clone).attr('is-clone', ''); //allows the clone to be removed without triggering the function again

            //you can do stuff to clone here (ex: add a fade animation)

            $(clone).insertAfter(e.target);
            setTimeout(() => {
                //optional remove clone after 1 second
                $(clone).remove();
            }, 1000);
        }
    });
SwiftNinjaPro
la source
0

Nous pouvons également utiliser DOMNodeRemoved:

$("#youridwhichremoved").on("DOMNodeRemoved", function () {
// do stuff
})
Den Pat
la source