Element Destroyed (a jQuery Special Event)

When building jQuery plugins, it's a best practice to provide some way for the user to teardown the plugin, remove any event handlers, and set things back to a good state. Most plugins can get away with waiting for the element to be removed, but more complex plugins listen for events on elements outside the plugin's element (such as the document or window). For these, it's important to know if an element has been removed and to clean up the plugins leftovers.

posted in jquery, in javascriptmvc, in open-source, in Development on May 25, 2010 by Justin Meyer

When building jQuery plugins, it’s a best practice to provide some way for the user to teardown the plugin, remove any event handlers, and set things back to a good state. Most plugins can get away with waiting for the element to be removed, but more complex plugins listen for events on elements outside the plugin’s element (such as the document or window). For these, it’s important to know if an element has been removed and to clean up the plugins leftovers.

To solve this problem, JavaScriptMVC adds a special jQuery event. You can bind to an element’s destroyed event like:

$('.foo').bind("destroyed", function(){
  //teardown here
})

Download jquery.event.destroyed.js. Read its documentation. View its jQuery plugin page.

Use Cases

Destroyed events are very useful for:

  • Plugins that listen for events on window or document events such as a contextmenu.
  • Primary plugins that create secondary plugins or elements that need to be removed if the primary plugin was removed. EX: a plugin that opens a modal.

Example

We’ll use a reusable menu as a demonstration of the power of destroyed events. View the demo here.

The reusable menu plugin creates a single menu for all elements that share the menu. For performance, the menu is cached in the dom and shown when appropriate. When all elements that share the menu are removed, we remove the menu.

The plugin also listens for a document click to hide the menu.When the menu is removed, it removes this event handler by using use a destroyed event on the menu.

Lets walk through the code step by step.

// create the reuse menu plugin
jQuery.fn.reusemenu = function( options ) {

  // create the menu element and put it in the dom
  var ul = $("<ul/>")
             .addClass("menu")
             .html(options.length ? 
                   "<li>"+options.join("</li><li>")+"</li>" 
                   : ""  )
             .appendTo(document.body),
      
  // save a reference to our handler so we can remove it
      hideHandler = function(){ 
        ul.find("li").animate({fontSize: 1, padding: 1});
      },
      
  // save the number of elements that remain
      count = this.length; 
  
  //hide the menu when the document is clicked
  $(document).click(hideHandler)     
  
  //remove the hide handler when the 
  //menu is removed
  ul.bind("destroyed", function(){
    $(document).unbind("click",hideHandler )     
  })

  
  // for each "Show Menu" button
  this.each(function(){
    
    var me = $(this);
    
    // position menu on click
    me.click( function(ev) {
      ul.offset({
        top: ev.pageY+20,
        left: ev.pageX+20
      }).find("li").animate({fontSize: 12, padding: 10});
      ev.stopPropagation();
    })
    
    // if last element, remove menu
    .bind("destroyed", function() {
      count--;
      if(!count){
        ul.remove();
        ul = null;
      }
    })
  })
};

// create a menu
$(".context").reusemenu(["reuse","able","menu"]);

// remove first context menu when clicked
$("#remove").click(function(){
  $(".context:first").remove()
})

Analysis

This demo does more to show how to use destroyed events than to highlight why to use them. To demonstrate that, a much more complex and involved demo is necessary. However, with a little imagination, I can hopefully use this demo to illustrate how destroyed events can isolate concerns and produce more error free plugins.

Consider how this widget unbound the document click handler. It would have been easy for the Show Menu button’s destroyed event to unbind the document click handler. It would have looked like this:

    .bind("destroyed", function() {
      count--;
      if(!count){
        ul.remove();
        $(document).unbind("click",hideHandler ) //added!
        ul = null;
      }
    })

However, what if the created menu was complex and interacted with other code or plugins? What if those plugins could remove the menu as well? We’d want to make damn sure that the document click handler was removed!

The destroyed event, and by extension Event Oriented Architecture, provides this encapsulation.By listening to the menu destroyed event we’ve made the menu’s clean up code independent of the “Show Me” button removal code. Thus, any other plugins could remove the menu we created and we’d be confident that the click handler would be cleaned up.

Hopefully, I’ve shown how, in very small scale, the destroyed event can be used to make your plugins clean themselves up nicely and cleanly separate concerns.

Destroyed in JavaScriptMVC

In JavaScriptMVC, controllers organize events, and the destroyed event is no different. By overwriting a controller’s destroy method, you can provide any additional cleanup you need.

  destroy: function(){
     this.someChildElement.remove();
     this._super(); 
  }
})

Note that in JavaScriptMVC, destroy is called when the Controller is removed from an element. This happens if the element is removed from the page or if controller.destroy() is called. This is a superior pattern to just using destroyed events as controller automatically provides a way to remove a plugin from an element without removing the element.

comments powered by Disqus
Contact Us
(312) 620-0386 | contact@bitovi.com
 or cancel