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