2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 YUI.add('event-delegate', function(Y) {
11 * Adds event delegation support to the library.
14 * @submodule event-delegate
23 mouseenter: "mouseover",
24 mouseleave: "mouseout"
27 resolveTextNode = function(n) {
29 if (n && 3 == n.nodeType) {
36 delegateHandler = function(delegateKey, e, el) {
38 var target = resolveTextNode((e.target || e.srcElement)),
39 tests = delegates[delegateKey],
47 var getMatch = function(el, selector, container) {
51 if (!el || el === container) {
55 returnVal = Y.Selector.test(el, selector) ? el: getMatch(el.parentNode, selector, container);
65 if (tests.hasOwnProperty(spec)) {
72 if (Y.Selector.test(target, spec, el)) {
75 else if (Y.Selector.test(target, ((spec.replace(/,/gi, " *,")) + " *"), el)) {
77 // The target is a descendant of an element matching
78 // the selector, so crawl up to find the ancestor that
79 // matches the selector
81 matched = getMatch(target, spec, el);
89 ev = new Y.DOMEventFacade(e, el);
90 ev.container = ev.currentTarget;
93 ev.currentTarget = Y.Node.get(matched);
96 contextFn: function() {
97 return ev.currentTarget;
115 attach = function (type, key, element) {
118 focus: Event._attachFocus,
119 blur: Event._attachBlur
122 attachFn = focusMethods[type],
126 delegateHandler(key, (e || window.event), element);
132 return attachFn(args, { capture: true, facade: false });
135 return Event._attach(args, { facade: false });
140 sanitize = Y.cached(function(str) {
141 return str.replace(/[|,:]/g, '~');
145 * Sets up event delegation on a container element. The delegated event
146 * will use a supplied selector to test if the target or one of the
147 * descendants of the target match it. The supplied callback function
148 * will only be executed if a match was encountered, and, in fact,
149 * will be executed for each element that matches if you supply an
150 * ambiguous selector.
152 * The event object for the delegated event is supplied to the callback
153 * function. It is modified slightly in order to support all properties
154 * that may be needed for event delegation. 'currentTarget' is set to
155 * the element that matched the delegation specifcation. 'container' is
156 * set to the element that the listener is bound to (this normally would
157 * be the 'currentTarget').
160 * @param type {string} 'delegate'
161 * @param fn {function} the callback function to execute. This function
162 * will be provided the event object for the delegated event.
163 * @param el {string|node} the element that is the delegation container
164 * @param delegateType {string} the event type to delegate
165 * @param spec {string} a selector that must match the target of the
167 * @param context optional argument that specifies what 'this' refers to.
168 * @param args* 0..n additional arguments to pass on to the callback function.
169 * These arguments will be added after the event object.
170 * @return {EventHandle} the detach handle
172 * @deprecated use Y.delegate
174 Y.Env.evt.plugins.delegate = {
176 on: function(type, fn, el, delegateType, spec) {
179 var args = Y.Array(arguments, 0, true);
183 args[0] = delegateType;
185 return Y.delegate.apply(Y, args);
193 * Sets up event delegation on a container element. The delegated event
194 * will use a supplied selector to test if the target or one of the
195 * descendants of the target match it. The supplied callback function
196 * will only be executed if a match was encountered, and, in fact,
197 * will be executed for each element that matches if you supply an
198 * ambiguous selector.
200 * The event object for the delegated event is supplied to the callback
201 * function. It is modified slightly in order to support all properties
202 * that may be needed for event delegation. 'currentTarget' is set to
203 * the element that matched the delegation specifcation. 'container' is
204 * set to the element that the listener is bound to (this normally would
205 * be the 'currentTarget').
208 * @param type {string} the event type to delegate
209 * @param fn {function} the callback function to execute. This function
210 * will be provided the event object for the delegated event.
211 * @param el {string|node} the element that is the delegation container
212 * @param spec {string} a selector that must match the target of the
214 * @param context optional argument that specifies what 'this' refers to.
215 * @param args* 0..n additional arguments to pass on to the callback function.
216 * These arguments will be added after the event object.
217 * @return {EventHandle} the detach handle
220 Event.delegate = function (type, fn, el, spec) {
227 var args = Y.Array(arguments, 0, true),
228 element = el, // HTML element serving as the delegation container
232 if (Lang.isString(el)) {
234 // Y.Selector.query returns an array of matches unless specified
235 // to return just the first match. Since the primary use case for
236 // event delegation is to use a single event handler on a container,
237 // Y.delegate doesn't currently support being able to bind a
238 // single listener to multiple containers.
240 element = Y.Selector.query(el, null, true);
242 if (!element) { // Not found, check using onAvailable
244 availHandle = Event.onAvailable(el, function() {
246 availHandle.handle = Event.delegate.apply(Event, args);
248 }, Event, true, false);
257 element = Y.Node.getDOMNode(element);
260 var guid = Y.stamp(element),
262 // The Custom Event for the delegation spec
263 ename = 'delegate:' + guid + type + sanitize(spec),
265 // The key to the listener for the event type and container
266 delegateKey = type + guid,
268 delegate = delegates[delegateKey],
281 if (specialTypes[type]) {
283 if (!Event._fireMouseEnter) {
287 type = specialTypes[type];
288 delegate.fn = Event._fireMouseEnter;
292 // Create the DOM Event wrapper that will fire the Custom Event
294 domEventHandle = attach(type, delegateKey, element);
297 // Hook into the _delete method for the Custom Event wrapper of this
298 // DOM Event in order to clean up the 'delegates' map and unsubscribe
299 // the associated Custom Event listeners fired by this DOM event
300 // listener if/when the user calls "purgeElement" OR removes all
301 // listeners of the Custom Event.
303 Y.after(function (sub) {
305 if (domEventHandle.sub == sub) {
307 // Delete this event from the map of known delegates
308 delete delegates[delegateKey];
311 // Unsubscribe all listeners of the Custom Event fired
312 // by this DOM event.
317 }, domEventHandle.evt, "_delete");
319 delegate.handle = domEventHandle;
321 delegates[delegateKey] = delegate;
326 listeners = delegate.listeners;
328 delegate.listeners = listeners ? (listeners + 1) : 1;
329 delegate[spec] = ename;
334 // Remove element, delegation spec
338 // Subscribe to the Custom Event for the delegation spec
340 ceHandle = Y.on.apply(Y, args);
343 // Hook into the detach method of the handle in order to clean up the
344 // 'delegates' map and remove the associated DOM event handler
345 // responsible for firing this Custom Event if all listener for this
346 // event have been removed.
348 Y.after(function () {
350 delegate.listeners = (delegate.listeners - 1);
352 if (delegate.listeners === 0) {
353 delegate.handle.detach();
356 }, ceHandle, "detach");
362 Y.delegate = Event.delegate;
365 }, '3.0.0' ,{requires:['node-base']});