Event Delegation with JQuery

If you want to make a web page more interactive, JavaScript is used to listen to events generated on the page. When doing very basic interaction the event handle (code that runs when a event is occurred) is attached to whatever DOM node you want start that interaction. For example adding a click handler to a link to open a menu. It’s good to understand the basics of JavaScript for doing this, but there are several libraries that make this process simple. My library of preference is jQuery and I’ll use it for all my examples.

$("a.menu").click(function() { openMenu(); });

There are a couple of issues with this implementation:

  • The handler is not attached to nodes added dynamically.
  • The handler may not be attached if it is run before the document is finished loading.
  • The event continues to bubble up to the top of the document.

The first two issues can be resolved using jQuery’s live method.

$("a.menu").live("click", function() { openMenu(); });

However, this doesn’t take care of the last issue. We’ve come up with a pretty interesting approach at Propeller to solve this. We create a global click handler that gets attached to the document, registers subhandlers, and stops event propagation. Any sub handlers that need to run for click events are then registered with this global handler with a simple selector.

/**
 * Global click handler.  We use a single global click handler attached to the
 * document.  We do this so we can bind the handler to the document prior to
 * the document is ready so that ajax will work before the page is finished
 * loading.  To add a function to the handler call attach on this object.
 * to remove a handler call detach.
 */
var clickHandler = {
    _handlers: {},

    /**
     * Attach a click handler to the global click handler.  This function
     * takes a jQuery selector and a function as it's arguments.
     *
     * IMPORTANT!!! we use the jquery is function to determine if a clicked
     * on element matches a selector.  is only supports simple selectors.  If
     * you try to use a complex selector i.e. hierarchy selectors (such as +, ~, and >)
     * the function will falsely return a match no matter what the element is.
     *
     * @param  sel  Selector to test target elements against.
     * @param  fn   Function to call if a selector matches.
     */
    attach: function(sel, fn) {
      this._handlers[sel] = fn;
    },

    /**
     * Detach a click handler from the global click handler.
     *
     * @param  sel  Selector to remove.
     */
   detach: function(sel) {
     if (this._handlers.hasOwnProperty(sel))
       delete this._handlers[sel];
   },

   /**
    * Called from the click event.  It loops through the sub handlers and
    * tests to see if the target element matches the selector.  If so
    * the function is called with the target and event as arguments.
    *
    * @param  e  JQuery object of the click event to process.
    * @return    False if a handler is called, otherwise true.
    */
   process: function(e) {
	 if (e.button) {
		 return true;
	 }
     var self = window.clickHandler;
     var target = $(e.target);
     for (var sel in self._handlers) {
       if (target.is(sel)) {
         e.preventDefault();
         e.stopPropagation();
         self._handlers[sel].apply(target, [target, e]);
         return false;
       }
     }
     return true;
   }
}
$(document).bind("click", clickHandler.process);

function menu_handler(target, e) {
  openMenu();
}
clickHandler.attach("a.meny", menu_handler);

The “attach” method is used to attach a subhandler to a node using a simple selector. “detach” can be used later to remove that subhandler. When a click event is generated it bubbles up to the document and then the “process” method is called for that event. The process method is where all the heavy lifting is done. First it checks the button on the event and only processes if it is a left mouse button. It then loops through the subhandlers and determines if their selectors match the node. If a match is found the event propagation is stopped and the subhandler is called.