]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/event/event-delegate.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / event / event-delegate.js
1 /*
2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 3.3.0
6 build: 3167
7 */
8 YUI.add('event-delegate', function(Y) {
9
10 /**
11  * Adds event delegation support to the library.
12  * 
13  * @module event
14  * @submodule event-delegate
15  */
16
17 var toArray          = Y.Array,
18     YLang            = Y.Lang,
19     isString         = YLang.isString,
20     isObject         = YLang.isObject,
21     isArray          = YLang.isArray,
22     selectorTest     = Y.Selector.test,
23     detachCategories = Y.Env.evt.handles;
24
25 /**
26  * <p>Sets up event delegation on a container element.  The delegated event
27  * will use a supplied selector or filtering function to test if the event
28  * references at least one node that should trigger the subscription
29  * callback.</p>
30  *
31  * <p>Selector string filters will trigger the callback if the event originated
32  * from a node that matches it or is contained in a node that matches it.
33  * Function filters are called for each Node up the parent axis to the
34  * subscribing container node, and receive at each level the Node and the event
35  * object.  The function should return true (or a truthy value) if that Node
36  * should trigger the subscription callback.  Note, it is possible for filters
37  * to match multiple Nodes for a single event.  In this case, the delegate
38  * callback will be executed for each matching Node.</p>
39  *
40  * <p>For each matching Node, the callback will be executed with its 'this'
41  * object set to the Node matched by the filter (unless a specific context was
42  * provided during subscription), and the provided event's
43  * <code>currentTarget</code> will also be set to the matching Node.  The
44  * containing Node from which the subscription was originally made can be
45  * referenced as <code>e.container</code>.
46  *
47  * @method delegate
48  * @param type {String} the event type to delegate
49  * @param fn {Function} the callback function to execute.  This function
50  *              will be provided the event object for the delegated event.
51  * @param el {String|node} the element that is the delegation container
52  * @param spec {string|Function} a selector that must match the target of the
53  *              event or a function to test target and its parents for a match
54  * @param context optional argument that specifies what 'this' refers to.
55  * @param args* 0..n additional arguments to pass on to the callback function.
56  *              These arguments will be added after the event object.
57  * @return {EventHandle} the detach handle
58  * @for YUI
59  */
60 function delegate(type, fn, el, filter) {
61     var args     = toArray(arguments, 0, true),
62         query    = isString(el) ? el : null,
63         typeBits, synth, container, categories, cat, i, len, handles, handle;
64
65     // Support Y.delegate({ click: fnA, key: fnB }, context, filter, ...);
66     // and Y.delegate(['click', 'key'], fn, context, filter, ...);
67     if (isObject(type)) {
68         handles = [];
69
70         if (isArray(type)) {
71             for (i = 0, len = type.length; i < len; ++i) {
72                 args[0] = type[i];
73                 handles.push(Y.delegate.apply(Y, args));
74             }
75         } else {
76             // Y.delegate({'click', fn}, context, filter) =>
77             // Y.delegate('click', fn, context, filter)
78             args.unshift(null); // one arg becomes two; need to make space
79
80             for (i in type) {
81                 if (type.hasOwnProperty(i)) {
82                     args[0] = i;
83                     args[1] = type[i];
84                     handles.push(Y.delegate.apply(Y, args));
85                 }
86             }
87         }
88
89         return new Y.EventHandle(handles);
90     }
91
92     typeBits = type.split(/\|/);
93
94     if (typeBits.length > 1) {
95         cat  = typeBits.shift();
96         type = typeBits.shift();
97     }
98
99     synth = Y.Node.DOM_EVENTS[type];
100
101     if (isObject(synth) && synth.delegate) {
102         handle = synth.delegate.apply(synth, arguments);
103     }
104
105     if (!handle) {
106         if (!type || !fn || !el || !filter) {
107             return;
108         }
109
110         container = (query) ? Y.Selector.query(query, null, true) : el;
111
112         if (!container && isString(el)) {
113             handle = Y.on('available', function () {
114                 Y.mix(handle, Y.delegate.apply(Y, args), true);
115             }, el);
116         }
117
118         if (!handle && container) {
119             args.splice(2, 2, container); // remove the filter
120
121             handle = Y.Event._attach(args, { facade: false });
122             handle.sub.filter  = filter;
123             handle.sub._notify = delegate.notifySub;
124         }
125     }
126
127     if (handle && cat) {
128         categories = detachCategories[cat]  || (detachCategories[cat] = {});
129         categories = categories[type] || (categories[type] = []);
130         categories.push(handle);
131     }
132
133     return handle;
134 }
135
136 /**
137  * Overrides the <code>_notify</code> method on the normal DOM subscription to
138  * inject the filtering logic and only proceed in the case of a match.
139  * 
140  * @method delegate.notifySub
141  * @param thisObj {Object} default 'this' object for the callback
142  * @param args {Array} arguments passed to the event's <code>fire()</code>
143  * @param ce {CustomEvent} the custom event managing the DOM subscriptions for
144  *              the subscribed event on the subscribing node.
145  * @return {Boolean} false if the event was stopped
146  * @private
147  * @static
148  * @since 3.2.0
149  */
150 delegate.notifySub = function (thisObj, args, ce) {
151     // Preserve args for other subscribers
152     args = args.slice();
153     if (this.args) {
154         args.push.apply(args, this.args);
155     }
156
157     // Only notify subs if the event occurred on a targeted element
158     var currentTarget = delegate._applyFilter(this.filter, args, ce),
159         //container     = e.currentTarget,
160         e, i, len, ret;
161
162     if (currentTarget) {
163         // Support multiple matches up the the container subtree
164         currentTarget = toArray(currentTarget);
165
166         // The second arg is the currentTarget, but we'll be reusing this
167         // facade, replacing the currentTarget for each use, so it doesn't
168         // matter what element we seed it with.
169         e = args[0] = new Y.DOMEventFacade(args[0], ce.el, ce);
170
171         e.container = Y.one(ce.el);
172     
173         for (i = 0, len = currentTarget.length; i < len && !e.stopped; ++i) {
174             e.currentTarget = Y.one(currentTarget[i]);
175
176             ret = this.fn.apply(this.context || e.currentTarget, args);
177
178             if (ret === false) { // stop further notifications
179                 break;
180             }
181         }
182
183         return ret;
184     }
185 };
186
187 /**
188  * <p>Compiles a selector string into a filter function to identify whether
189  * Nodes along the parent axis of an event's target should trigger event
190  * notification.</p>
191  *
192  * <p>This function is memoized, so previously compiled filter functions are
193  * returned if the same selector string is provided.</p>
194  *
195  * <p>This function may be useful when defining synthetic events for delegate
196  * handling.</p>
197  *
198  * @method delegate.compileFilter
199  * @param selector {String} the selector string to base the filtration on
200  * @return {Function}
201  * @since 3.2.0
202  * @static
203  */
204 delegate.compileFilter = Y.cached(function (selector) {
205     return function (target, e) {
206         return selectorTest(target._node, selector, e.currentTarget._node);
207     };
208 });
209
210 /**
211  * Walks up the parent axis of an event's target, and tests each element
212  * against a supplied filter function.  If any Nodes, including the container,
213  * satisfy the filter, the delegated callback will be triggered for each.
214  *
215  * @method delegate._applyFilter
216  * @param filter {Function} boolean function to test for inclusion in event
217  *                  notification
218  * @param args {Array} the arguments that would be passed to subscribers
219  * @param ce   {CustomEvent} the DOM event wrapper
220  * @return {Node|Node[]|undefined} The Node or Nodes that satisfy the filter
221  * @protected
222  */
223 delegate._applyFilter = function (filter, args, ce) {
224     var e         = args[0],
225         container = ce.el, // facadeless events in IE, have no e.currentTarget
226         target    = e.target || e.srcElement,
227         match     = [],
228         isContainer = false;
229
230     // Resolve text nodes to their containing element
231     if (target.nodeType === 3) {
232         target = target.parentNode;
233     }
234
235     // passing target as the first arg rather than leaving well enough alone
236     // making 'this' in the filter function refer to the target.  This is to
237     // support bound filter functions.
238     args.unshift(target);
239
240     if (isString(filter)) {
241         while (target) {
242             isContainer = (target === container);
243             if (selectorTest(target, filter, (isContainer ?null: container))) {
244                 match.push(target);
245             }
246
247             if (isContainer) {
248                 break;
249             }
250
251             target = target.parentNode;
252         }
253     } else {
254         // filter functions are implementer code and should receive wrappers
255         args[0] = Y.one(target);
256         args[1] = new Y.DOMEventFacade(e, container, ce);
257
258         while (target) {
259             // filter(target, e, extra args...) - this === target
260             if (filter.apply(args[0], args)) {
261                 match.push(target);
262             }
263
264             if (target === container) {
265                 break;
266             }
267
268             target = target.parentNode;
269             args[0] = Y.one(target);
270         }
271         args[1] = e; // restore the raw DOM event
272     }
273
274     if (match.length <= 1) {
275         match = match[0]; // single match or undefined
276     }
277
278     // remove the target
279     args.shift();
280
281     return match;
282 };
283
284 /**
285  * Sets up event delegation on a container element.  The delegated event
286  * will use a supplied filter to test if the callback should be executed.
287  * This filter can be either a selector string or a function that returns
288  * a Node to use as the currentTarget for the event.
289  *
290  * The event object for the delegated event is supplied to the callback
291  * function.  It is modified slightly in order to support all properties
292  * that may be needed for event delegation.  'currentTarget' is set to
293  * the element that matched the selector string filter or the Node returned
294  * from the filter function.  'container' is set to the element that the
295  * listener is delegated from (this normally would be the 'currentTarget').
296  *
297  * Filter functions will be called with the arguments that would be passed to
298  * the callback function, including the event object as the first parameter.
299  * The function should return false (or a falsey value) if the success criteria
300  * aren't met, and the Node to use as the event's currentTarget and 'this'
301  * object if they are.
302  *
303  * @method delegate
304  * @param type {string} the event type to delegate
305  * @param fn {function} the callback function to execute.  This function
306  * will be provided the event object for the delegated event.
307  * @param el {string|node} the element that is the delegation container
308  * @param filter {string|function} a selector that must match the target of the
309  * event or a function that returns a Node or false.
310  * @param context optional argument that specifies what 'this' refers to.
311  * @param args* 0..n additional arguments to pass on to the callback function.
312  * These arguments will be added after the event object.
313  * @return {EventHandle} the detach handle
314  * @for YUI
315  */
316 Y.delegate = Y.Event.delegate = delegate;
317
318
319 }, '3.3.0' ,{requires:['node-base']});