;(function($) {
  $.fn.extend(/** @lends jQuery.fn */ {
    /**
     * Adds the ability to easily handle event bubbling by delegating the
     * event handler to use based on selector rules.
     * 
     * @see http://www.danwebb.net/2008/2/8/event-delegation-made-easy-in-jquery
     * @param {String} type The event type to bind to
     * @param {Object} rules The selector rules to delegate
     * 
     * == Examples
     * 
     *   jQuery('#thing').delegate('click', {
     *     '.quit': function() { ...do quit stuff... },
     *     '.edit': function() { ...do edit stuf... }
     *   });
     */
    delegate: function(type, rules) {
      var self = this;
      
      return this.bind(type, function(e) {
        // The first element that can't be considered for finding candidates
        var invalidParent = self.parent();
        
        // The potential elements that match the delegate's selectors
        var parent = $(e.target).parent();
        var candidates = [e.target, parent[0]];
        
        // Fake bubbling by walking up the the target's parents and searching
        // for an element that matches one of the selectors
        while (parent.length && parent[0] != invalidParent[0]) {
          // Keep walking up while more parents are available to search
          for (var selector in rules) {
            // Find elements in the current parent that match one of the rules
            // and is a candidate element
            var matches = parent.find(selector).filter(function() {return $.inArray(this, candidates) >= 0;});
            
            if (matches.length) {
              // One of the valid elements in the tree matched the selector:
              // this is the element the delegate bound to.  Change the event
              // target to match the real target bound to by the delegate
              var target = e.target;
              e.target = matches[0];
              
              var result = rules[selector].apply(this, arguments);
              e.target = target;
              return result;
            }
          }
          
          // No parents matched: continue walking the tree
          parent = parent.parent();
          candidates.push(parent[0]);
        }
      });
    },
    
    /**
     * Unbinds all handlers associated with the given event.  This is necessary
     * when using delegation since it's not possible to undelegate specific rules.
     * 
     * @param {String} type The event type to unbind from
     */
    undelegate: function(type) {
      return $(this).each(function() {
        $(this).unbind(type);
      });
    }
  });
})(jQuery);
