jQuery Default Events

by Justin Meyer

jQuery Default Events

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.

posted in Development on June 2, 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.

CanJS 2.2 Release

Justin Meyerposted in Development, Open Source, Uncategorized on April 5, 2015 by Justin MeyerCanJS 2.2 is out. It's awesome. This article covers the top 10 enhancements added since 2.1. Some of the improvements include Browserify and StealJS support, can-EVENT arguments, observable promises, and in-page automatically rendered templates. The article includes a lot of good JSBins to learn from too.

Getting Started with Cordova

Brian Moschelposted in Development on March 26, 2015 by Brian MoschelAt Bitovi, we’re big fans of building applications with web technologies and using build tools to target other platforms like iOS, Android, and desktop. This article will provide a quick guide to getting up and running quickly with Cordova.

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