jQuery: evento de activación cuando un elemento se elimina del DOM

211

Estoy tratando de descubrir cómo ejecutar un código js cuando un elemento se elimina de la página:

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

¿Hay un evento diseñado para eso, algo como:

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

Gracias.

sa125
fuente
1
por curiosidad, ¿qué querría hacer con el elemento que se ha eliminado?
Natrium
8
Tengo un elemento que se adhiere independientemente a la pieza que elimino, por lo que quiero detectar cuándo esa pieza se ha ido para eliminar ese elemento también. Podría rediseñar todo, pero lograr lo anterior me ahorrará mucho tiempo (y código).
sa125

Respuestas:

118

Recién marcado, ya está incorporado en la versión actual de JQuery:

jQuery - v1.9.1

jQuery UI - v1.10.2

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

Importante : Esta es la funcionalidad del script Jquery UI (no JQuery), por lo que debe cargar ambos scripts (jquery y jquery-ui) para que funcione. Aquí hay un ejemplo: http://jsfiddle.net/72RTz/

Philipp Munin
fuente
11
Esto fue muy útil. Aprendí que esta funcionalidad está dentro del componente "Widget" de jQuery UI si no está buscando descargar toda la biblioteca de UI.
Neil
55
¿Está documentado en alguna parte? Acabo de mirar a través de la documentación del widget jQuery UI y no pude encontrar una mención de esto. Me gustaría ver si este es oficialmente compatible con cualquier / advertencias acerca de su uso ...
Josh
Este no funciona: intentado en jQuery 1.10.2, sin embargo, la respuesta a continuación por @mtkopone funciona perfectamente, por lo tanto, votaría por actualizar la respuesta en esa pregunta
Marcin
20
Este solo funciona en parte. Si lo hace removeen el elemento, se dispara, pero si el elemento se destruye de otra manera, al sobrescribirlo, no funciona.
Carl Smith
Puede que tengas razón, probablemente hay otro nombre de evento para este caso. Sería genial si puede proporcionar una muestra jsfiddle para su caso.
Philipp Munin
195

Puedes usar eventos especiales de jQuery para esto.

Con toda simplicidad,

Preparar:

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

Uso:

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

Anexo para responder a los comentarios de Pierre y DesignerGuy:

Para que no se active la devolución de llamada al llamar $('.thing').off('destroyed'), cambie la condición if a:if (o.handler && o.type !== 'destroyed') { ... }

mtkopone
fuente
15
Muy buena solución. Ben Alman tiene una buena reseña sobre eventos especiales para obtener más información: benalman.com/news/2010/03/jquery-special-events
antti_s
11
¡+1 había logrado perderse por completo estos eventos especiales hasta ahora, malditamente útil! Una cosa para afirmar que acaba de implementar lo anterior, sería mejor cambiar o.handler()para que, de lo o.handler.apply(this,arguments)contrario, el evento y los objetos de datos no pasen a través del detector de eventos.
Pebbl
66
Sin embargo, esto no funciona cuando elimina elementos sin jQuery.
djjeck
55
esto no funciona a) cuando los elementos se separan en lugar de eliminar ob) cuando algunas bibliotecas antiguas que no son jquery usan innerHTML para destruir sus elementos (similar a lo que dijo djjeck)
Charon ME
55
Tenga en cuenta que el controlador se llama cuando usted, lo $('.thing').unbind('destroyed')que realmente podría ser molesto (ya que desvincular significa que no queremos que se llame al controlador ...)
Pierre
54

Puede vincularse al evento DOMNodeRemoved (parte de la especificación DOM Level 3 WC3).

Funciona en IE9, últimas versiones de Firefox y Chrome.

Ejemplo:

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

También puede recibir una notificación cuando se insertan elementos mediante el enlace a DOMNodeInserted

Adán
fuente
13
Nota: "Agregar oyentes de mutación DOM a un documento degrada profundamente el rendimiento de otras modificaciones DOM a ese documento (¡haciéndolos 1.5 - 7 veces más lentos!"). de: developer.mozilla.org/en/DOM/Mutation_events
Matt Crinklaw-Vogt
1
Me encanta esto, aunque nodeName rara vez es útil para mí. Solo uso e.target.classNameo if ($(e.target).hasClass('my-class')) { ....
Micah Alcorn
Esto no funciona en el elemento en sí, solo en sus hijos.
Xdg
Increíble respuesta! Realmente me ayudó!
Kir Mazur
Puede ser lento y una mala idea para el código en producción, pero este parece ser el único método que funciona sincrónicamente (a diferencia de MutationObserver) y para cualquier nodo (no solo los nodos eliminados mediante jQuery). Esta es una gran opción para la depuración.
CertainPerformance
38

No hay un evento incorporado para eliminar elementos, pero puede crear uno mediante la falsificación del método de eliminación predeterminado de jQuery. Tenga en cuenta que se debe llamar a la devolución de llamada antes de eliminarla realmente para mantener la referencia.

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

Nota: algunos problemas con esta respuesta han sido descritos por otros carteles.

  1. Esto no funcionará cuando el nodo se elimine mediante html() replace()u otros métodos jQuery
  2. Este evento brota
  3. La interfaz de usuario de jQuery anula la eliminación también

La solución más elegante para este problema parece ser: https://stackoverflow.com/a/10172676/216941

David Hellsing
fuente
2
¡Gracias por eso! Una pequeña adición: debido a que el evento remove surge, también lo recibirías cuando se retira a un niño, así que mejor escribe el controlador de esa manera:$('#some-element').bind('remove', function(ev) { if (ev.target === this) { console.log('removed!'); } });
Meyertee
3
Esto no funcionó de la caja para mí: tuve que devolver el resultado de orig.apply.
fturtle
1
En realidad, @ Adam, lo es, pero es compatible con varios navegadores. Con las adiciones de meyertee / fturtle es una solución perfectamente confiable, siempre que solo elimine elementos con este método, en lugar de modificar / vaciar HTML, etc. Para algo más flexible, sí, los eventos de mutación DOM están bien, pero he sido escéptico sobre ellos como Debería estar escuchando los eventos de negocios en una aplicación, no los DOM cuya estructura es probable que cambie con un mayor desarrollo. Además, suscribirse a eventos de mutación DOM significa que su programa es potencialmente susceptible de retrasarse en jerarquías DOM complejas.
Will Morgan
1
Mi único acuerdo sobre la precisión es la declaración: 'no hay un evento incorporado para eliminar elementos' --- hay un evento incorporado para los navegadores que implementan eventos DOM de Nivel 3 (como se detalla en mi respuesta).
Adam
44
Si bien esto detectará los elementos eliminados mediante la función 'eliminar', no detectará los elementos eliminados por otros medios (por ejemplo, utilizando el html de jQuery, reemplazar, etc.). Por favor, vea mi respuesta para una solución más completa.
zah
32

De enganche .remove()no es la mejor manera de manejar esto, ya que hay muchas maneras de eliminar elementos de la página (por ejemplo, mediante el uso .html(), .replace()etc).

Para evitar varios riesgos de pérdida de memoria, internamente jQuery intentará llamar a la función jQuery.cleanData()para cada elemento eliminado, independientemente del método utilizado para eliminarlo.

Vea esta respuesta para más detalles: fugas de memoria javascript

Por lo tanto, para obtener mejores resultados, debe conectar la cleanDatafunción, que es exactamente lo que hace el complemento jquery.event.destroyed :

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

zah
fuente
¡Esta idea cleanDatafue muy útil para mí! Muchas gracias, Joe :)
Petr Vostrel 05 de
1
Buena respuesta, pero aún así eso solo funciona para los métodos jQuery. Si se está integrando con otra plataforma que puede "sacar la alfombra" debajo de usted, la respuesta de Adam tiene más sentido.
Bron Davies
7

Para aquellos que usan jQuery UI:

jQuery UI ha anulado algunos de los métodos de jQuery para implementar un removeevento que consigue manejado no sólo cuando se quita explícitamente el elemento dado, pero también si el elemento consigue quitado del DOM mediante cualquier método jQuery auto-limpieza (por ejemplo replace, html, etc.) . Básicamente, esto le permite poner un gancho en los mismos eventos que se activan cuando jQuery está "limpiando" los eventos y datos asociados con un elemento DOM.

John Resig ha indicado que está abierto a la idea de implementar este evento en una versión futura de jQuery core, pero no estoy seguro de dónde se encuentra actualmente.

StriplingWarrior
fuente
6

Solo se requiere jQuery (no se necesita jQuery UI)

( He extraído esta extensión del marco de la interfaz de usuario jQuery )

Funciona con: empty()y html()yremove()

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

Con esta solución también puede desvincular el controlador de eventos.

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

¡Intentalo! - Ejemplo

$.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>

Demo adicional - jsBin

Leyendas
fuente
4

No pude obtener esta respuesta para trabajar con la desvinculación (a pesar de la actualización, ver aquí ), pero pude encontrar una forma de evitarlo. La respuesta fue crear un evento especial 'destroy_proxy' que activó un evento 'destruido'. Pones el detector de eventos tanto en 'destruido_proxy' como en 'destruido', luego, cuando quieres desvincular, simplemente desvincula el evento 'destruido':

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

Aquí hay un violín

Bill Johnston
fuente
¿Qué pasa con la compatibilidad del navegador aquí? ¿Todavía funciona?
Vladislav Rastrusny
3

Me gusta la respuesta de mtkopone usando eventos especiales jQuery, pero tenga en cuenta que no funciona a) cuando los elementos se separan en lugar de eliminarse ob) cuando algunas bibliotecas antiguas que no son jquery usan innerHTML para destruir sus elementos

Charon ME
fuente
3
Voté en contra porque la pregunta pedía explícitamente el uso de .remove (), no la separación. detachse usa especialmente para no desencadenar la limpieza, ya que el elemento probablemente esté planeado para volverse a unir más tarde. b) sigue siendo cierto y aún no tiene un manejo confiable, al menos desde que los eventos de mutación DOM se implementen ampliamente.
Pierre
44
Apuesto a que hiciste esto solo para obtener la insignia de "crítico";)
Charon ME
1

No estoy seguro de que haya un identificador de evento para esto, por lo que tendría que mantener una copia del DOM y compararlo con el DOM existente en algún tipo de bucle de sondeo, lo que podría ser bastante desagradable. Sin embargo, Firebug hace esto: si inspecciona el HTML y ejecuta algunos cambios DOM, resalta los cambios en amarillo en la consola Firebug por un corto tiempo.

Alternativamente, podría crear una función de eliminación ...

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
fuente
1
+1 para esta idea. Aunque también podría extender jQuery (estilo de complemento) para obtener una llamada jQuery más estándar como: $ ('. ItemToRemove'). CustomRemove () ;. También puede hacerlo para que acepte una devolución de llamada como parámetro.
user113716
1

Así es como se crea un oyente jQuery live remove :

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

O:

$(document).on('DOMNodeRemoved', function(e)
{
  $(e.target).find('.element').each(function()
  {
    // do anything with $(this)
  }
});
Buzogany Laszlo
fuente
1
Sería genial ser libre de hacer eso. Desafortunadamente no es confiable, ya que los eventos de mutación están en desuso: developer.mozilla.org/en-US/docs/Web/Guide/Events/…
Kamafeather
1

El evento "eliminar" de jQuery funciona bien, sin adición. Puede ser más confiable a tiempo usar un truco simple, en lugar de parchear jQuery.

Simplemente modifique o agregue un atributo en el elemento que está a punto de eliminar del DOM. Por lo tanto, puede activar cualquier función de actualización, que simplemente ignorará los elementos en camino a ser destruidos, con el atributo "do_not_count_it".

Supongamos que tenemos una tabla con celdas correspondientes a los precios, y que necesita mostrar solo el último precio: este es el selector que se activará cuando se elimine una celda de precio (tenemos un botón en cada línea de la tabla que hace eso, no se muestra aquí)

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

Y aquí hay una función que encuentra el último precio en la tabla, sin tener en cuenta el último, si fue el que se eliminó. De hecho, cuando se activa el evento "eliminar", y cuando se llama a esta función, el elemento aún no se elimina.

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

Al final, la función update_prices () funciona bien, y después de eso, se elimina el elemento DOM.

philippe
fuente
0

haciendo referencia a la respuesta de @David:

Cuando desee hacerlo con otra función, p. Ej. html () como en mi caso, no olvides agregar return en una nueva función:

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

Esta.

$.each(
  $('#some-element'), 
        function(i, item){
            item.addEventListener('DOMNodeRemovedFromDocument',
                function(e){ console.log('I has been removed'); console.log(e);
                })
         })
Matas Vaitkevicius
fuente
1
DOMNodeRemovedFromDocument parece no ser compatible con Firefox. Tal vez intente MutationObserver en su lugar?
EricP
0

una extensión de la respuesta de Adam en caso de que necesite evitar el incumplimiento, aquí hay una solución alternativa:

$(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
fuente
0

También podemos usar DOMNodeRemoved:

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