# Yahoo! UI Library

### event 3.0.0

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

    /**
     * Adds event delegation support to the library.
     *
     * @module event
     * @submodule event-delegate
     */

    var Event = Y.Event,
    	Lang = Y.Lang,

    	delegates = {},

    	specialTypes = {
    		mouseenter: "mouseover",
    		mouseleave: "mouseout"
    	},

    	resolveTextNode = function(n) {
    	    try {
    	        if (n && 3 == n.nodeType) {
    	            return n.parentNode;
    	        }
    	    } catch(e) { }
    	    return n;
    	},

        delegateHandler = function(delegateKey, e, el) {

            var target = resolveTextNode((e.target || e.srcElement)),
                tests  = delegates[delegateKey],
                spec,
    			ename,
    			matched,
    			fn,
    			ev;

    		var getMatch = function(el, selector, container) {

    			var returnVal;

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

    			return returnVal;

    		};

            for (spec in tests) {

                if (tests.hasOwnProperty(spec)) {

                    ename  = tests[spec];
    				fn	= tests.fn;
    				matched = null;

    				if (Y.Selector.test(target, spec, el)) {
    					matched = target;
    				}
    				else if (Y.Selector.test(target, ((spec.replace(/,/gi, " *,")) + " *"), el)) {

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

    					matched = getMatch(target, spec, el);

    				}

    				if (matched) {

                        if (!ev) {
                            ev = new Y.DOMEventFacade(e, el);
                            ev.container = ev.currentTarget;
                        }

                        ev.currentTarget = Y.Node.get(matched);

    					Y.publish(ename, {
    			               contextFn: function() {
    			                   return ev.currentTarget;
    			               }
    			           });

    					if (fn) {
    						fn(ev, ename);
    					}
    					else {
                        	Y.fire(ename, ev);
    					}

    				}

                }
            }

        },

    	attach = function (type, key, element) {

    		var focusMethods = {
    				focus: Event._attachFocus,
    				blur: Event._attachBlur
    			},

    			attachFn = focusMethods[type],

    			args = [type,
    			function (e) {
    	            delegateHandler(key, (e || window.event), element);
    			},
    			element];

    		if (attachFn) {
    			return attachFn(args, { capture: true, facade: false });
    		}
    		else {
    			return Event._attach(args, { facade: false });
    		}

    	},

        sanitize = Y.cached(function(str) {
            return str.replace(/[|,:]/g, '~');
        });

    /**
     * Sets up event delegation on a container element.  The delegated event
     * will use a supplied selector to test if the target or one of the
     * descendants of the target match it.  The supplied callback function
     * will only be executed if a match was encountered, and, in fact,
     * will be executed for each element that matches if you supply an
     * ambiguous selector.
     *
     * The event object for the delegated event is supplied to the callback
     * function.  It is modified slightly in order to support all properties
     * that may be needed for event delegation.  'currentTarget' is set to
     * the element that matched the delegation specifcation.  'container' is
     * set to the element that the listener is bound to (this normally would
     * be the 'currentTarget').
     *
     * @event delegate
     * @param type {string} 'delegate'
     * @param fn {function} the callback function to execute.  This function
     * will be provided the event object for the delegated event.
     * @param el {string|node} the element that is the delegation container
     * @param delegateType {string} the event type to delegate
     * @param spec {string} a selector that must match the target of the
     * event.
     * @param context optional argument that specifies what 'this' refers to.
     * @param args* 0..n additional arguments to pass on to the callback function.
     * These arguments will be added after the event object.
     * @return {EventHandle} the detach handle
     * @for YUI
     * @deprecated use Y.delegate
     */
    Y.Env.evt.plugins.delegate = {

        on: function(type, fn, el, delegateType, spec) {

            Y.log('delegate event is deprecated, use Y.delegate()', 'warn', 'deprecated');

    		var args = Y.Array(arguments, 0, true);

    		args.splice(3, 1);

    		args[0] = delegateType;

    		return Y.delegate.apply(Y, args);

        }

    };

    /**
     * Sets up event delegation on a container element.  The delegated event
     * will use a supplied selector to test if the target or one of the
     * descendants of the target match it.  The supplied callback function
     * will only be executed if a match was encountered, and, in fact,
     * will be executed for each element that matches if you supply an
     * ambiguous selector.
     *
     * The event object for the delegated event is supplied to the callback
     * function.  It is modified slightly in order to support all properties
     * that may be needed for event delegation.  'currentTarget' is set to
     * the element that matched the delegation specifcation.  'container' is
     * set to the element that the listener is bound to (this normally would
     * be the 'currentTarget').
     *
     * @method delegate
     * @param type {string} the event type to delegate
     * @param fn {function} the callback function to execute.  This function
     * will be provided the event object for the delegated event.
     * @param el {string|node} the element that is the delegation container
     * @param spec {string} a selector that must match the target of the
     * event.
     * @param context optional argument that specifies what 'this' refers to.
     * @param args* 0..n additional arguments to pass on to the callback function.
     * These arguments will be added after the event object.
     * @return {EventHandle} the detach handle
     * @for YUI
     */
    Event.delegate = function (type, fn, el, spec) {

        if (!spec) {
            Y.log('delegate: no spec, nothing to do', 'warn', 'event');
            return false;
        }

        var args = Y.Array(arguments, 0, true),
    		element = el,	// HTML element serving as the delegation container
    		availHandle;

    	if (Lang.isString(el)) {

    		//	Y.Selector.query returns an array of matches unless specified
    		//	to return just the first match.  Since the primary use case for
    		//	event delegation is to use a single event handler on a container,
    		//	Y.delegate doesn't currently support being able to bind a
    		//	single listener to multiple containers.

    		element = Y.Selector.query(el, null, true);

    		if (!element) { // Not found, check using onAvailable

    			availHandle = Event.onAvailable(el, function() {

    				availHandle.handle = Event.delegate.apply(Event, args);

                }, Event, true, false);

                return availHandle;

    		}

    	}

    	element = Y.Node.getDOMNode(element);

    	var	guid = Y.stamp(element),

            // The Custom Event for the delegation spec
            ename = 'delegate:' + guid + type + sanitize(spec),

            // The key to the listener for the event type and container
            delegateKey = type + guid,

    		delegate = delegates[delegateKey],

    		domEventHandle,

    		ceHandle,

    		listeners;

        if (!delegate) {

    		delegate = {};

    		if (specialTypes[type]) {

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

    			type = specialTypes[type];
    			delegate.fn = Event._fireMouseEnter;

    		}

    		//	Create the DOM Event wrapper that will fire the Custom Event

    		domEventHandle = attach(type, delegateKey, element);

    		//	Hook into the _delete method for the Custom Event wrapper of this
    		//	DOM Event in order to clean up the 'delegates' map and unsubscribe
    		//	the associated Custom Event listeners fired by this DOM event
    		//	listener if/when the user calls "purgeElement" OR removes all
    		//	listeners of the Custom Event.

    		Y.after(function (sub) {

    			if (domEventHandle.sub == sub) {

    				//	Delete this event from the map of known delegates
    				delete delegates[delegateKey];

    				Y.log("DOM event listener associated with the " + ename + " Custom Event removed.  Removing all " + ename + " listeners.", "info", "Event");

    				//	Unsubscribe all listeners of the Custom Event fired
    				//	by this DOM event.
    				Y.detachAll(ename);

    			}

    		}, domEventHandle.evt, "_delete");

    		delegate.handle = domEventHandle;

            delegates[delegateKey] = delegate;

        }

    	listeners = delegate.listeners;

    	delegate.listeners = listeners ? (listeners + 1) : 1;
        delegate[spec] = ename;

        args[0] = ename;

        // Remove element, delegation spec
        args.splice(2, 2);

        // Subscribe to the Custom Event for the delegation spec

    	ceHandle = Y.on.apply(Y, args);

    	//	Hook into the detach method of the handle in order to clean up the
    	//	'delegates' map and remove the associated DOM event handler
    	//	responsible for firing this Custom Event if all listener for this
    	//	event have been removed.

    	Y.after(function () {

    		delegate.listeners = (delegate.listeners - 1);

    		if (delegate.listeners === 0) {
    			Y.log("No more listeners for the " + ename + " Custom Event.  Removing its associated DOM event listener.", "info", "Event");
    			delegate.handle.detach();
    		}

    	}, ceHandle, "detach");

        return ceHandle;

    };

    Y.delegate = Event.delegate;

Copyright © 2009 Yahoo! Inc. All rights reserved.