# Yahoo! UI Library

### Event Utility Event Delegation Module 2.8.0r4

Yahoo! UI Library > event-delegate > Delegate.js (source view)
Search:
Filters Show Private Show Protected Show Deprecated

    /**
     * Augments the Event Utility with a <code>delegate</code> method that
     * facilitates easy creation of delegated event listeners.  (Note: Using CSS
     * selectors as the filtering criteria for delegated event listeners requires
     * inclusion of the Selector Utility.)
     *
     * @module event-delegate
     * @title Event Utility Event Delegation Module
     * @namespace YAHOO.util
     * @requires event
     */

    (function () {

    	var Event = YAHOO.util.Event,
    		Lang = YAHOO.lang,
    		delegates = [],

    		getMatch = function(el, selector, container) {

    			var returnVal;

    			if (!el || el === container) {
    				returnVal = false;
    			}
    			else {
    				returnVal = YAHOO.util.Selector.test(el, selector) ? el: getMatch(el.parentNode, selector, container);
    			}

    			return returnVal;

    		};

    	Lang.augmentObject(Event, {

    		/**
    		 * Creates a delegate function used to call event listeners specified
    		 * via the <code>YAHOO.util.Event.delegate</code> method.
    		 *
    		 * @method _createDelegate
    		 *
    		 * @param {Function} fn        The method (event listener) to call.
    		 * @param {Function|string} filter Function or CSS selector used to
    		 * determine for what element(s) the event listener should be called.
    		 * @param {Object}   obj	An arbitrary object that will be
    		 *                             passed as a parameter to the listener.
    		 * @param {Boolean|object}  overrideContext  If true, the value of the
    		 * 							obj parameter becomes the execution context
    		 *                          of the listener. If an object, this object
    		 *                          becomes the execution context.
    		 * @return {Function} Function that will call the event listener
    		 * specified by the <code>YAHOO.util.Event.delegate</code> method.
             * @private
             * @for Event
    		 * @static
    		 */
    		_createDelegate: function (fn, filter, obj, overrideContext) {

    			return function (event) {

    				var container = this,
    					target = Event.getTarget(event),
    					selector = filter,

    					//	The user might have specified the document object
    					//	as the delegation container, in which case it is not
    					//	nessary to scope the provided CSS selector(s) to the
    					//	delegation container
    					bDocument = (container.nodeType === 9),

    					matchedEl,
    					context,
    					sID,
    					sIDSelector;

    				if (Lang.isFunction(filter)) {
    					matchedEl = filter(target);
    				}
    				else if (Lang.isString(filter)) {

    					if (!bDocument) {

    						sID = container.id;

    						if (!sID) {
    							sID = Event.generateId(container);
    						}

    						//	Scope all selectors to the container
    						sIDSelector = ("#" + sID + " ");
    						selector = (sIDSelector + filter).replace(/,/gi, ("," + sIDSelector));

    					}

    					if (YAHOO.util.Selector.test(target, selector)) {
    						matchedEl = target;
    					}
    					else if (YAHOO.util.Selector.test(target, ((selector.replace(/,/gi, " *,")) + " *"))) {

    						//	The target is a descendant of an element matching
    						//	the selector, so crawl up to find the ancestor that
    						//	matches the selector

    						matchedEl = getMatch(target, selector, container);

    					}

    				}

    				if (matchedEl) {

    					//	The default context for delegated listeners is the
    					//	element that matched the filter.

    					context = matchedEl;

    		            if (overrideContext) {
    		                if (overrideContext === true) {
    		                    context = obj;
    		                } else {
    		                    context = overrideContext;
    		                }
    		            }

    					//	Call the listener passing in the container and the
    					//	element that matched the filter in case the user
    					//	needs those.

    					return fn.call(context, event, matchedEl, container, obj);

    				}

    			};

    		},

            /**
             * Appends a delegated event listener.  Delegated event listeners
    		 * receive three arguments by default: the DOM event, the element
    		 * specified by the filtering function or CSS selector, and the
    		 * container element (the element to which the event listener is
    		 * bound).  (Note: Using the delegate method requires the event-delegate
    		 * module.  Using CSS selectors as the filtering criteria for delegated
    		 * event listeners requires inclusion of the Selector Utility.)
             *
             * @method delegate
             *
             * @param {String|HTMLElement|Array|NodeList} container An id, an element
             *  reference, or a collection of ids and/or elements to assign the
             *  listener to.
             * @param {String}   type     The type of event listener to append
             * @param {Function} fn        The method the event invokes
    		 * @param {Function|string} filter Function or CSS selector used to
    		 * determine for what element(s) the event listener should be called.
    		 * When a function is specified, the function should return an
    		 * HTML element.  Using a CSS Selector requires the inclusion of the
    		 * CSS Selector Utility.
             * @param {Object}   obj    An arbitrary object that will be
             *                             passed as a parameter to the listener
             * @param {Boolean|object}  overrideContext  If true, the value of the obj parameter becomes
             *                             the execution context of the listener. If an
             *                             object, this object becomes the execution
             *                             context.
             * @return {Boolean} Returns true if the action was successful or defered,
             *                   false if one or more of the elements
             *                   could not have the listener attached,
             *                   or if the operation throws an exception.
             * @static
             * @for Event
             */
    		delegate: function (container, type, fn, filter, obj, overrideContext) {

    			var sType = type,
    				fnMouseDelegate,
    				fnDelegate;

    			if (Lang.isString(filter) && !YAHOO.util.Selector) {
    				YAHOO.log("Using a CSS selector to define the filtering criteria for a delegated listener requires the Selector Utility.", "error", "Event");
    		        return false;
    			}

    			if (type == "mouseenter" || type == "mouseleave") {

    				if (!Event._createMouseDelegate) {
    					YAHOO.log("Delegating a " + type + " event requires the event-mouseenter module.", "error", "Event");
    			        return false;
    				}

    				//	Look up the real event--either mouseover or mouseout
    				sType = Event._getType(type);

    				fnMouseDelegate = Event._createMouseDelegate(fn, obj, overrideContext);

    				fnDelegate = Event._createDelegate(function (event, matchedEl, container) {

    					return fnMouseDelegate.call(matchedEl, event, container);

    				}, filter, obj, overrideContext);

    			}
    			else {

    				fnDelegate = Event._createDelegate(fn, filter, obj, overrideContext);

    			}

    			delegates.push([container, sType, fn, fnDelegate]);

    			return Event.on(container, sType, fnDelegate);

    		},

            /**
             * Removes a delegated event listener.
             *
             * @method removeDelegate
             *
             * @param {String|HTMLElement|Array|NodeList} container An id, an element
             *  reference, or a collection of ids and/or elements to remove
             *  the listener from.
             * @param {String} type The type of event to remove.
             * @param {Function} fn The method the event invokes.  If fn is
             *  undefined, then all event listeners for the type of event are
             *  removed.
             * @return {boolean} Returns true if the unbind was successful, false
             *  otherwise.
             * @static
             * @for Event
             */
    		removeDelegate: function (container, type, fn) {

    			var sType = type,
    				returnVal = false,
    				index,
    				cacheItem;

    			//	Look up the real event--either mouseover or mouseout
    			if (type == "mouseenter" || type == "mouseleave") {
    				sType = Event._getType(type);
    			}

    			index = Event._getCacheIndex(delegates, container, sType, fn);

    		    if (index >= 0) {
    		        cacheItem = delegates[index];
    		    }

    		    if (container && cacheItem) {

    		        returnVal = Event.removeListener(cacheItem[0], cacheItem[1], cacheItem[3]);

    				if (returnVal) {
    	                delete delegates[index][2];
    	                delete delegates[index][3];
    	                delegates.splice(index, 1);
    				}

    		    }

    			return returnVal;

    		}

    	});

    }());

Copyright © 2009 Yahoo! Inc. All rights reserved.