jQuery Default Events

Event Oriented Architectures (EOAs) are an emerging best practice when designing reusable JavaScript widgets. jQuery's trigger, triggerHandler, and jQuery.event.special[EVENT]._default methods have enabled widget authors to easily expose custom events to developers. However, these methods lack the scalability or expressiveness to enable powerful EOAs.

posted in open-source, javascriptmvc, jquery, Development on June 02, 2010 by Justin Meyer

Event Oriented Architectures (EOAs) are an emerging best practice when designing reusable JavaScript widgets. jQuery’s trigger, triggerHandler, and jQuery.event.special[EVENT]._default methods have enabled widget authors to easily expose custom events to developers. However, these methods lack the scalability or expressiveness to enable powerful EOAs.

JavaScriptMVC’s jquery.event.default plugin provides an easier and more scalable way of providing default events.

Download

jquery.event.default.js (minified 1.3kb) [works with latest jQuery on github]

Demo

Default tabs - a tabs widget that uses default events.

Event Oriented Architectures

EOAs are simply widgets that produce synthetic events that you can listen to similar to a native event. For example, jQuery UI’s tabs widget produces events like tabselect that you can register event handlers on like:

$('#mytabs').bind('tabselect', function(){
  //called before a tab is selected
})

Why EOAs?

There are many other methods of exposing a widget’s API. The most common is taking a callback function when the widget is created. jQuery.UI’s tab widget also allows you to do this:

$("#mytabs").tabs({
  select : function(){
    //called when a tab is selected
  }
})

But, this only allows one responder! It’s possible that many objects want to know when something happens in your widget.

If you use Dojo, you might be lucky enough to use dojo.connect. Connect allows you to bind to any other function. By just exposing a function, any other object can be notified when that function is called.

//in the tabs widget
myTabsWidget = {
  select : function(){
     //tabs select functionality
  }
};
// somewhere in your code
dojo.connect(myTabs,"select",null, function(){
  //called when select is called on myTabs
})

Another multiple-callback solution is using a library like OpenAjax.hub. Hub allows you to publish and subscribe messages like:

//in your tabs widget
OpenAjax.hub.publish("tab.select",tab)

//in your code
OpenAjax.hub.subscribe("tab.select", function(called, tab){
 //do stuff!
})

But these options suck compared to synthetic events for UI widgets because:

  • Developers already understand DOM events and how to listen to them.
  • Bubbling events naturally group related widgets. Ex: respond to all ‘activate’ events by listening on a parent element.

jQuery and EOAs

jQuery makes creating EOAs simple, if underpowered. Bind, delegate, and live can listen to arbitrary events on elements. Trigger creates synthetic events that bubble like a DOM events. The following shows the first hidden element in the page:

//listen for show events on the document
$(document).bind('show', function(ev){
  //show the target
  $(ev.target).show()
});

//trigger show on the first hidden element
$(':hidden:first').trigger("show")

Trigger makes it easy to create synthetic events and expose a callback API. But to make these callback functions more powerful and DOM-like, we need to provide default events.

Default Events

We’re all familiar with default events in the DOM. Here’s a few examples:

  • clicking a link -> Follows the link
  • clicking submit in a form -> posts the form
  • mousedown on text -> starts selecting text

And with jQuery, we are used to preventing default events by returning false or calling prevent default:

$('a').click(function(ev){
  ev.preventDefault();  //these 2 lines
  return false;         //do the same thing
})

What if we could set default behaviors for certain events on our widgets and let others prevent them in the same way? How awesome!

_default

In jQuery 1.4.3, a _default function can be set on special events. For more info check out jQuery’s forum. The _default function is called after all event handlers for an event type have been called. For example:

$.event.special.show ={
  _default : function(ev){
    $(ev.target).show()
  }
};
$(':hidden:first').trigger("show")

Underpowered?

jQuery doesn’t go far enough in making default events practical for complex applications. Consider the following problems:

  • What if multiple plugins define a show default event?
  • What if there are multiple tabs plugins, each with its own default tabselect event?

JavaScriptMVC’s jquery.event.default.js plugin makes using default events scalable and organized.

jQuery.Event.Default

The default plugin lets you add default functionality by binding a default event handler. Just prefix the event with “default.”. The following provides default show behavior for ‘.tab’ elements inside a ‘#tab’ element:

$("#tabs").delegate('.tab','default.show',function(){
  $(this).show()
})

Of course, global default events are still supported, but without stomping on other code:

$(document).bind('default.show',function(ev){
  $(ev.target).show()
})

$(':hidden:first').trigger("show")

Use bind, delegate, or live to listen for default events. The default event handler only gets called if preventDefault is not called.

triggerDefault and triggerDefaults

The jquery.event.default.js file provides two useful helpers to trigger events and return if default events were triggered.

// synthetic event bubbles
$('#foo').triggerDefaults('show'); //-> true/false

// synthetic event does not bubble
$('#foo').triggerDefault('show'); //-> true/false

The Demo

The demo uses default events to provide a tabs widget. The default ‘show’ event is prevented on the second tab until the first tab’s checkbox is checked.

Here’s the code:

// create a tabs plugin
$.fn.tabs = function(){

  // finds the tab from the tab button
  var sub = function(el){
    return $(el.find("a").attr("href"))
  }
  
  this.each(function(){
    var tab = $(this);
    
    // set the first tab button as active
    tab.find("li:first").addClass('active')
    
    // hide all the other tabs
    tab.find(".tab:gt(0)").hide();
    
    // listen for a click on a tab button
    tab.delegate("li","click", function(ev){
      ev.preventDefault();
      var el = $(this);
        
      if( // not active button
        !el.hasClass('active') && 
        // default wasn't prevented
        sub(el).triggerDefaults("show")){
            
        // remove active and hide old active    
        sub(tab.find(".active").removeClass("active"))
          .hide();
      
        //mark as active
        el.addClass("active");
      }
  })
    
  // show a tab if default isn't prevented
  .delegate(".tab","default.show", function(ev){
    $(this).show();
  })
})
};

// create tabs widget
$("#tabs").tabs();

// listen on the second tab for show
$("#second").bind("show",function(ev){
  
  //if complete isn't checked
  if(! $("#complete")[0].checked ){
    
    //prevent the default action!
    ev.preventDefault();
  }
});

Here’s how the code works:

  1. When an li in the tabs is clicked, it checks if it isn’t active and uses sub to find the tab content for that li.
  2. It triggers a show action on the tab content.
  3. It listens to show events on the #second tab, if the checkbox isn’t checked, it prevent default events.
  4. Assuming that default events are allowed, the “.tab” default “show” event will show the tab.
  5. After the event has finished, control returns back to the original li click handler. If default events were allowed, it hides the old tab.

Conclusions

Default events and event oriented architecture is, to be sure, an advanced JavaScript technique. However, the JavaScriptMVC default event plugin makes building sophisticated and extendable widgets slightly easier. It’s a powerful technique, use it with care.

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