224

I'm trying to figure out how to execute some js code when an element is removed from the page:

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

is there an event tailored for that, something like:

jQuery('#some-element').onremoval( function() {
    // do post-mortem stuff here
});
Nakilon
  • 34,866
  • 14
  • 107
  • 142
sa125
  • 28,121
  • 38
  • 111
  • 153
  • 1
    out of curiousity, what would want to do with the element that has been removed? – Natrium Feb 04 '10 at 14:52
  • 8
    I have an element that independently attaches itself to the piece I remove, so I want to detect when that piece is gone to eliminate that element as well. I could re-design the whole thing, but accomplishing the above will save me a lot of time (and code). – sa125 Feb 07 '10 at 07:06

16 Answers16

198

You can use jQuery special events for this.

In all simplicity,

Setup:

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

Usage:

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

Addendum to answer Pierre and DesignerGuy's comments:

To not have the callback fire when calling $('.thing').off('destroyed'), change the if condition to: if (o.handler && o.type !== 'destroyed') { ... }

mtkopone
  • 5,955
  • 2
  • 27
  • 30
  • 15
    Really nice solution. Ben Alman has a nice writeup about special events for more info: http://benalman.com/news/2010/03/jquery-special-events/ – antti_s Apr 16 '12 at 10:52
  • 11
    +1 had managed to completely miss these special events till now, darn useful! One thing to state having just implemented the above, it would be best to change `o.handler()` to `o.handler.apply(this,arguments)` otherwise the event and data objects don't get passed through the event listener. – Pebbl Nov 07 '12 at 22:03
  • 1
    agreed, this is the best solution to the question. – Eat at Joes Dec 13 '12 at 16:30
  • 6
    This doesn't work when you remove elements without jQuery, though. – djjeck Dec 29 '12 at 00:06
  • 5
    this doesn't work a) when elements are detached instead of removed or b) when some old non-jquery libraries use innerHTML to destroy your elements (similar to what djjeck said) – Charon ME Jan 16 '13 at 14:32
  • 5
    Beware that handler is called when you `$('.thing').unbind('destroyed')` which could really be annoying (since unbind means we do not want the handler to be called...) – Pierre Jan 17 '13 at 17:19
  • @Pierre - Is there a fix for that? – Andy Fleming Feb 06 '13 at 01:37
  • Pierre and @DesignerGuy: added an addendum to the original answer for handling the case where you don't want a callback when unbinding the handler – mtkopone Feb 06 '13 at 09:39
  • 2
    ```o.type``` seems to be returning ```'destroyed'``` every time so ```if (o.handler && o.type !== 'destroyed')``` isn't working. Any other ways to stop the callback when calling ```.off```? The ```o``` object seems to be exactly the same for a removal from the DOM and ```.off``` http://jsfiddle.net/billjohnston4/wg4ks/3/ – Bill Johnston May 28 '14 at 19:41
  • @mtkopone yeah, I answered with a workaround [here](http://stackoverflow.com/a/23937738/1692715) – Bill Johnston Jun 16 '14 at 23:15
  • 1
    Great special event. Any way to make it usable with `$('body').on('destroy', '.target', func)` rather than `$('.target'.).on('destroy', func)` ?? – Eric Holmes Apr 24 '15 at 16:25
  • I don't understand why the Addendum would work. The `o.type==='destroyed'` should always be true, no matter through `off()`, `unbind()`, or `remove()`, isn't it? I mean if you modify the if statement according to the addendum, when the element is `remove()`d, the `destroyed` callback will not be fired since the whole thing is utilizing the side effect that when an element got `remove()`d, all its events get unbound. –  Mar 19 '18 at 10:03
  • Not working for me. I was trying to detect which step of an external library (js chosen) removed one item, but item dissapears going silent. – Mbotet Aug 30 '22 at 12:46
124

Just checked, it is already built-in in current version of JQuery:

jQuery - v1.9.1

jQuery UI - v1.10.2

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

Important: This is functionality of Jquery UI script (not JQuery), so you have to load both scripts (jquery and jquery-ui) to make it work. Here is example: http://jsfiddle.net/72RTz/

Philipp Munin
  • 5,610
  • 7
  • 37
  • 60
  • 11
    This was very helpful. I learned that this functionality is within the "Widget" component of jQuery UI if you aren't looking to download the whole UI library. – Neil May 10 '13 at 16:47
  • 5
    Is this documented anywhere? I just looked through the jQuery UI widget documentation and couldn't find a mention of this. Would like to see whether this is officially supported / any caveats about using it... – josh Aug 01 '13 at 17:00
  • This one doesn't work - tried in jQuery 1.10.2, however the answer below by @mtkopone works perfectly therefore I would vote for updating the answer in that question – Marcin Oct 19 '13 at 07:57
  • 23
    This one only partly works. If you do `remove` on the element it fires, but if the element is destroyed some other way, by being overwritten say, it doesn't work. – Carl Smith Oct 22 '13 at 16:38
  • You might be right, probably there is other event name for this case. It would be great if you can provide jsfiddle sample for your case. – Philipp Munin Oct 22 '13 at 16:41
  • **mtkopone**'s solution below works well and does **not** require jquery UI. – jdhildeb Sep 30 '15 at 18:39
  • Just a precision, the alert will be triggered right before the div is removed. This listener is called right before the action, so keep in mind that the div is still referenced in the DOM at this time. I had a case when I removed a line from a table and needed to update the display (line number), but it is not working because of that. – user2700551 Dec 16 '15 at 10:38
  • Is it possible to do it as live event? I need this, but not working: $('body').on('remove', '.selector', function() { ... }); – Buzogany Laszlo Sep 30 '16 at 06:18
  • This even works with `.html("")`. @BuzoganyLaszlo try `document` instead of `body` – Legends Feb 14 '17 at 21:55
  • @CarlSmith and @PhilippMunin, what is the event name e.g. when using jQuery `replaceWith`? – Backo Mar 27 '19 at 11:22
58

You can bind to the DOMNodeRemoved event (part of DOM Level 3 WC3 spec).

Works in IE9, latest releases of Firefox and Chrome.

Example:

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

You can also get notification when elements are inserting by binding to DOMNodeInserted

Adam
  • 28,537
  • 15
  • 60
  • 73
  • 13
    Note: "Adding DOM mutation listeners to a document profoundly degrades the performance of further DOM modifications to that document (making them 1.5 - 7 times slower!)." from: https://developer.mozilla.org/en/DOM/Mutation_events – Matt Wonlaw May 23 '12 at 14:01
  • 1
    Love this, although nodeName is rarely useful for me. I just use `e.target.className` or `if ($(e.target).hasClass('my-class')) { ...`. – Micah Alcorn Nov 02 '12 at 03:24
  • This is not working on element itself, only its children. – Xdg Apr 03 '16 at 08:27
  • Amazing answer! Really helped me! – Kir Mazur Mar 29 '17 at 06:34
  • It may be slow and a bad idea for code in production, but this is looks like the only method that works *synchronously* (unlike MutationObserver) and for *any node* (not just nodes removed via jQuery). This is a great option for debugging. – CertainPerformance Mar 20 '19 at 03:39
  • I like this method, the only problem is that I want to trigger removed event using `$(e.target).trigger('remove')`, but the event never trigger :( – Sam Jun 29 '20 at 02:33
39

There is no built-in event for removing elements, but you can create one by fake-extending jQuery's default remove method. Note that the callback must be called before actually removing it to keep reference.

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

Note: some problems with this answer have been outlined by other posters.

  1. This won't work when the node is removed via html() replace() or other jQuery methods
  2. This event bubbles up
  3. jQuery UI overrides remove as well

The most elegant solution to this problem seems to be: https://stackoverflow.com/a/10172676/216941

Community
  • 1
  • 1
David Hellsing
  • 106,495
  • 44
  • 176
  • 212
  • 2
    Thanks for that! One little addition: because the remove-event bubbles up, you'd also receive it when a child is removed, so better write the handler that way: `$('#some-element').bind('remove', function(ev) { if (ev.target === this) { console.log('removed!'); } });` – meyertee Mar 24 '11 at 16:53
  • 3
    This didn't work out of the box for me - I had to return the result from orig.apply. – fturtle Jul 18 '11 at 12:13
  • This is not an accurate answer. See my answer – Adam Aug 08 '11 at 19:25
  • 1
    Actually, @Adam, it is, but it's cross browser compatible. With meyertee/fturtle's additions it's a perfectly reliable solution, as long as you only remove elements with this method, rather than modifying/emptying HTML etc. For something more flexible, yes DOM mutation events are fine but I've been sceptic of them as you should be listening for business events in an app, not DOM ones whose structure is likely to change with further development. Also, subscribing to DOM mutation events means your program is potentially susceptible to lagging in complex DOM hierarchies. – Will Morgan Sep 23 '11 at 14:34
  • 1
    My only diagreement on the accuracy is the statement - 'there is no built in event for removing elements' --- there IS a built in event for browsers that implement Level 3 DOM events (as detailed in my answer). – Adam Oct 17 '11 at 17:40
  • 4
    While this will detect elements removed using the 'remove' function, it will fail to detect elements removed by other means (e.g. using jQuery's html, replace, etc). Please, see my answer for more complete solution. – zah Nov 03 '11 at 00:54
  • There are numerous problems with this answer. I'd suggest using the approach outlined by @mtkopone below. http://stackoverflow.com/a/10172676/216941 – Matt Wonlaw May 23 '12 at 14:22
  • The remove event is also triggered when content is removed using `html()`, at least with jQuery 3.1 – Legends Feb 06 '17 at 02:32
33

Hooking .remove() is not the best way to handle this as there are many ways to remove elements from the page (e.g. by using .html(), .replace(), etc).

In order to prevent various memory leak hazards, internally jQuery will try to call the function jQuery.cleanData() for each removed element regardless of the method used to remove it.

See this answer for more details: javascript memory leaks

So, for best results, you should hook the cleanData function, which is exactly what the jquery.event.destroyed plugin does:

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

Community
  • 1
  • 1
zah
  • 5,314
  • 1
  • 34
  • 31
  • This insight about `cleanData` was very helpful to me! Thank you very much, Joe :) – Petr Vostrel Apr 05 '12 at 16:57
  • 1
    Nice answer, but still that only works for jQuery methods. If you are integrating with another platform that can "pull the rug out" from under you - Adam's answer makes the most sense. – Bron Davies Nov 16 '12 at 19:37
7

Only jQuery is required (No jQuery UI needed)

(I have extracted this extension from the jQuery UI framework)

Works with: empty() and html() and remove()

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

With this solution you can also unbind the event handler.

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

Try it! - Example

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

Additional Demo - jsBin

Legends
  • 21,202
  • 16
  • 97
  • 123
7

For those who use jQuery UI:

jQuery UI has overridden some of the jQuery methods to implement a remove event that gets handled not only when you explicitly remove the given element, but also if the element gets removed from the DOM by any self-cleaning jQuery methods (e.g. replace, html, etc.). This basically allows you to put a hook into the same events that get fired when jQuery is "cleaning up" the events and data associated with a DOM element.

John Resig has indicated that he's open to the idea of implementing this event in a future version of jQuery core, but I'm not sure where it stands currently.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
4

I couldn't get this answer to work with unbinding (despite the update see here), but was able to figure out a way around it. The answer was to create a 'destroy_proxy' special event that triggered a 'destroyed' event. You put the event listener on both 'destroyed_proxy' and 'destroyed', then when you want to unbind, you just unbind the 'destroyed' event:

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

Here is a fiddle

Community
  • 1
  • 1
Bill Johnston
  • 1,160
  • 1
  • 14
  • 31
3

I like mtkopone's answer using jQuery special events, but note that it doesn't work a) when elements are detached instead of removed or b) when some old non-jquery libraries use innerHTML to destroy your elements

Charon ME
  • 698
  • 10
  • 22
  • 3
    I downvoted because the question asked explicitly for .remove() use, not detach. `detach` is used especially to not trigger cleanup since the element is probably planned to be reattached later. b) is still true and has yet no reliable handling, at least since DOM mutation events be implemented widely. – Pierre Jun 15 '13 at 13:26
  • 4
    I bet you did this just to get the "critic" badge ;) – Charon ME Jun 17 '13 at 14:33
1

I'm not sure there is an event handle for this, so you would have to keep a copy of the DOM and compare to the existing DOM in some kind of polling loop - which could be quite nasty. Firebug does this though - if you inspect the HTML and run some DOM-changes, it highlights the changes in yellow in the Firebug console for a short time.

Alternatively, you could create a remove function...

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
  • 241,084
  • 71
  • 387
  • 401
  • 1
    +1 for this idea. Though you could also extend jQuery (plugin style) in order to get a more standard jQuery call like: $('.itemToRemove').customRemove();. You could also make it so that it accepts a callback as a parameter. – user113716 Feb 04 '10 at 15:06
1

This.

$.each(
  $('#some-element'), 
        function(i, item){
            item.addEventListener('DOMNodeRemovedFromDocument',
                function(e){ console.log('I has been removed'); console.log(e);
                })
         })
Matas Vaitkevicius
  • 58,075
  • 31
  • 238
  • 265
  • 1
    DOMNodeRemovedFromDocument seems to not be supported in Firefox. Perhaps try [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) instead? – EricP Oct 21 '14 at 19:25
1

This is how to create a jQuery live remove listener:

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

Or:

$(document).on('DOMNodeRemoved', function(e)
{
  $(e.target).find('.element').each(function()
  {
    // do anything with $(this)
  }
});
Buzogany Laszlo
  • 921
  • 11
  • 19
  • 1
    It would be great to be free to do that. Unfortunately it is not reliable, as the mutation-events are being deprecated: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Mutation_events#Cross-browser_support – Kamafeather Jul 26 '17 at 10:37
1

The "remove" event from jQuery works fine, without addition. It might be more reliable in time to use a simple trick, instead of patching jQuery.

Just modify or add an attribute in the element you are about to remove from the DOM. Thus, you can trigger any update function, that will just ignore elements on way to be destroyed, with the attribute "do_not_count_it".

Suppose we have a table with cells corresponding to prices, and that you need to show only the last price: This is the selector to trigger when a price cell is deleted (we have a button in each line of the table doing that, not shown here)

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

And here is a function that finds the last price in the table, not taking account of the last one, if it was the one that was removed. Indeed, when the "remove" event is triggered, and when this function is called, the element is not removed yet.

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

In the end, the update_prices() function works fine, and after that, the DOM element is removed.

philippe
  • 1,877
  • 2
  • 20
  • 25
1

We can also use DOMNodeRemoved:

$("#youridwhichremoved").on("DOMNodeRemoved", function () {
// do stuff
})
Den Pat
  • 1,118
  • 10
  • 17
  • As Kamafeather comment it [here below](https://stackoverflow.com/a/42243620/1863761), MutationEvents should not be used : https://developer.mozilla.org/en-US/docs/Web/API/MutationEvent#cross-browser_support – ZalemCitizen Nov 05 '21 at 18:13
  • 1
    @ZalemCitizen yes, it should not be used but please show other alternatives. also even it says deprecated, it still works for most browsers. – Den Pat Nov 06 '21 at 19:34
  • What about https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver? – Aaron Digulla Dec 17 '21 at 08:45
0

an extension to Adam's answer incase you need to prevend default, here is a work around:

$(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
  • 787
  • 8
  • 17
0

referencing to @David answer:

When You want to do soo with another function, eg. html() like in my case, don't forget to add return in new function:

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