/*
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
version: 2.8.0r4
*/
/**
 * 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;

		}
		
	});

}());
YAHOO.register("event-delegate", YAHOO.util.Event, {version: "2.8.0r4", build: "2449"});
