NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / event-delegate / event-delegate-debug.js
blob3eebe99c9ebac4ef14046e2491ed9e12f997a069
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('event-delegate', function (Y, NAME) {
10 /**
11  * Adds event delegation support to the library.
12  *
13  * @module event
14  * @submodule event-delegate
15  */
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;
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 filter {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  * @static
59  * @for Event
60  */
61 function delegate(type, fn, el, filter) {
62     var args     = toArray(arguments, 0, true),
63         query    = isString(el) ? el : null,
64         typeBits, synth, container, categories, cat, i, len, handles, handle;
66     // Support Y.delegate({ click: fnA, key: fnB }, el, filter, ...);
67     // and Y.delegate(['click', 'key'], fn, el, filter, ...);
68     if (isObject(type)) {
69         handles = [];
71         if (isArray(type)) {
72             for (i = 0, len = type.length; i < len; ++i) {
73                 args[0] = type[i];
74                 handles.push(Y.delegate.apply(Y, args));
75             }
76         } else {
77             // Y.delegate({'click', fn}, el, filter) =>
78             // Y.delegate('click', fn, el, filter)
79             args.unshift(null); // one arg becomes two; need to make space
81             for (i in type) {
82                 if (type.hasOwnProperty(i)) {
83                     args[0] = i;
84                     args[1] = type[i];
85                     handles.push(Y.delegate.apply(Y, args));
86                 }
87             }
88         }
90         return new Y.EventHandle(handles);
91     }
93     typeBits = type.split(/\|/);
95     if (typeBits.length > 1) {
96         cat  = typeBits.shift();
97         args[0] = type = typeBits.shift();
98     }
100     synth = Y.Node.DOM_EVENTS[type];
102     if (isObject(synth) && synth.delegate) {
103         handle = synth.delegate.apply(synth, arguments);
104     }
106     if (!handle) {
107         if (!type || !fn || !el || !filter) {
108             Y.log("delegate requires type, callback, parent, & filter", "warn");
109             return;
110         }
112         container = (query) ? Y.Selector.query(query, null, true) : el;
114         if (!container && isString(el)) {
115             handle = Y.on('available', function () {
116                 Y.mix(handle, Y.delegate.apply(Y, args), true);
117             }, el);
118         }
120         if (!handle && container) {
121             args.splice(2, 2, container); // remove the filter
123             handle = Y.Event._attach(args, { facade: false });
124             handle.sub.filter  = filter;
125             handle.sub._notify = delegate.notifySub;
126         }
127     }
129     if (handle && cat) {
130         categories = detachCategories[cat]  || (detachCategories[cat] = {});
131         categories = categories[type] || (categories[type] = []);
132         categories.push(handle);
133     }
135     return handle;
139 Overrides the <code>_notify</code> method on the normal DOM subscription to
140 inject the filtering logic and only proceed in the case of a match.
142 This method is hosted as a private property of the `delegate` method
143 (e.g. `Y.delegate.notifySub`)
145 @method notifySub
146 @param thisObj {Object} default 'this' object for the callback
147 @param args {Array} arguments passed to the event's <code>fire()</code>
148 @param ce {CustomEvent} the custom event managing the DOM subscriptions for
149              the subscribed event on the subscribing node.
150 @return {Boolean} false if the event was stopped
151 @private
152 @static
153 @since 3.2.0
155 delegate.notifySub = function (thisObj, args, ce) {
156     // Preserve args for other subscribers
157     args = args.slice();
158     if (this.args) {
159         args.push.apply(args, this.args);
160     }
162     // Only notify subs if the event occurred on a targeted element
163     var currentTarget = delegate._applyFilter(this.filter, args, ce),
164         //container     = e.currentTarget,
165         e, i, len, ret;
167     if (currentTarget) {
168         // Support multiple matches up the the container subtree
169         currentTarget = toArray(currentTarget);
171         // The second arg is the currentTarget, but we'll be reusing this
172         // facade, replacing the currentTarget for each use, so it doesn't
173         // matter what element we seed it with.
174         e = args[0] = new Y.DOMEventFacade(args[0], ce.el, ce);
176         e.container = Y.one(ce.el);
178         for (i = 0, len = currentTarget.length; i < len && !e.stopped; ++i) {
179             e.currentTarget = Y.one(currentTarget[i]);
181             ret = this.fn.apply(this.context || e.currentTarget, args);
183             if (ret === false) { // stop further notifications
184                 break;
185             }
186         }
188         return ret;
189     }
193 Compiles a selector string into a filter function to identify whether
194 Nodes along the parent axis of an event's target should trigger event
195 notification.
197 This function is memoized, so previously compiled filter functions are
198 returned if the same selector string is provided.
200 This function may be useful when defining synthetic events for delegate
201 handling.
203 Hosted as a property of the `delegate` method (e.g. `Y.delegate.compileFilter`).
205 @method compileFilter
206 @param selector {String} the selector string to base the filtration on
207 @return {Function}
208 @since 3.2.0
209 @static
211 delegate.compileFilter = Y.cached(function (selector) {
212     return function (target, e) {
213         return selectorTest(target._node, selector,
214             (e.currentTarget === e.target) ? null : e.currentTarget._node);
215     };
219 Regex to test for disabled elements during filtering. This is only relevant to
220 IE to normalize behavior with other browsers, which swallow events that occur
221 to disabled elements. IE fires the event from the parent element instead of the
222 original target, though it does preserve `event.srcElement` as the disabled
223 element. IE also supports disabled on `<a>`, but the event still bubbles, so it
224 acts more like `e.preventDefault()` plus styling. That issue is not handled here
225 because other browsers fire the event on the `<a>`, so delegate is supported in
226 both cases.
228 @property _disabledRE
229 @type {RegExp}
230 @protected
231 @since 3.8.1
233 delegate._disabledRE = /^(?:button|input|select|textarea)$/i;
236 Walks up the parent axis of an event's target, and tests each element
237 against a supplied filter function.  If any Nodes, including the container,
238 satisfy the filter, the delegated callback will be triggered for each.
240 Hosted as a protected property of the `delegate` method (e.g.
241 `Y.delegate._applyFilter`).
243 @method _applyFilter
244 @param filter {Function} boolean function to test for inclusion in event
245                  notification
246 @param args {Array} the arguments that would be passed to subscribers
247 @param ce   {CustomEvent} the DOM event wrapper
248 @return {Node|Node[]|undefined} The Node or Nodes that satisfy the filter
249 @protected
251 delegate._applyFilter = function (filter, args, ce) {
252     var e         = args[0],
253         container = ce.el, // facadeless events in IE, have no e.currentTarget
254         target    = e.target || e.srcElement,
255         match     = [],
256         isContainer = false;
258     // Resolve text nodes to their containing element
259     if (target.nodeType === 3) {
260         target = target.parentNode;
261     }
263     // For IE. IE propagates events from the parent element of disabled
264     // elements, where other browsers swallow the event entirely. To normalize
265     // this in IE, filtering for matching elements should abort if the target
266     // is a disabled form control.
267     if (target.disabled && delegate._disabledRE.test(target.nodeName)) {
268         return match;
269     }
271     // passing target as the first arg rather than leaving well enough alone
272     // making 'this' in the filter function refer to the target.  This is to
273     // support bound filter functions.
274     args.unshift(target);
276     if (isString(filter)) {
277         while (target) {
278             isContainer = (target === container);
279             if (selectorTest(target, filter, (isContainer ? null: container))) {
280                 match.push(target);
281             }
283             if (isContainer) {
284                 break;
285             }
287             target = target.parentNode;
288         }
289     } else {
290         // filter functions are implementer code and should receive wrappers
291         args[0] = Y.one(target);
292         args[1] = new Y.DOMEventFacade(e, container, ce);
294         while (target) {
295             // filter(target, e, extra args...) - this === target
296             if (filter.apply(args[0], args)) {
297                 match.push(target);
298             }
300             if (target === container) {
301                 break;
302             }
304             target = target.parentNode;
305             args[0] = Y.one(target);
306         }
307         args[1] = e; // restore the raw DOM event
308     }
310     if (match.length <= 1) {
311         match = match[0]; // single match or undefined
312     }
314     // remove the target
315     args.shift();
317     return match;
321  * Sets up event delegation on a container element.  The delegated event
322  * will use a supplied filter to test if the callback should be executed.
323  * This filter can be either a selector string or a function that returns
324  * a Node to use as the currentTarget for the event.
326  * The event object for the delegated event is supplied to the callback
327  * function.  It is modified slightly in order to support all properties
328  * that may be needed for event delegation.  'currentTarget' is set to
329  * the element that matched the selector string filter or the Node returned
330  * from the filter function.  'container' is set to the element that the
331  * listener is delegated from (this normally would be the 'currentTarget').
333  * Filter functions will be called with the arguments that would be passed to
334  * the callback function, including the event object as the first parameter.
335  * The function should return false (or a falsey value) if the success criteria
336  * aren't met, and the Node to use as the event's currentTarget and 'this'
337  * object if they are.
339  * @method delegate
340  * @param type {string} the event type to delegate
341  * @param fn {function} the callback function to execute.  This function
342  * will be provided the event object for the delegated event.
343  * @param el {string|node} the element that is the delegation container
344  * @param filter {string|function} a selector that must match the target of the
345  * event or a function that returns a Node or false.
346  * @param context optional argument that specifies what 'this' refers to.
347  * @param args* 0..n additional arguments to pass on to the callback function.
348  * These arguments will be added after the event object.
349  * @return {EventHandle} the detach handle
350  * @for YUI
351  */
352 Y.delegate = Y.Event.delegate = delegate;
355 }, '3.13.0', {"requires": ["node-base"]});