src/cm/media/js/lib/yui/yui_3.10.3/build/event-delegate/event-delegate-debug.js
changeset 525 89ef5ed3c48b
equal deleted inserted replaced
524:322d0feea350 525:89ef5ed3c48b
       
     1 /*
       
     2 YUI 3.10.3 (build 2fb5187)
       
     3 Copyright 2013 Yahoo! Inc. All rights reserved.
       
     4 Licensed under the BSD License.
       
     5 http://yuilibrary.com/license/
       
     6 */
       
     7 
       
     8 YUI.add('event-delegate', function (Y, NAME) {
       
     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 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;
       
    65 
       
    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 = [];
       
    70 
       
    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
       
    80 
       
    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         }
       
    89 
       
    90         return new Y.EventHandle(handles);
       
    91     }
       
    92 
       
    93     typeBits = type.split(/\|/);
       
    94 
       
    95     if (typeBits.length > 1) {
       
    96         cat  = typeBits.shift();
       
    97         args[0] = type = typeBits.shift();
       
    98     }
       
    99 
       
   100     synth = Y.Node.DOM_EVENTS[type];
       
   101 
       
   102     if (isObject(synth) && synth.delegate) {
       
   103         handle = synth.delegate.apply(synth, arguments);
       
   104     }
       
   105 
       
   106     if (!handle) {
       
   107         if (!type || !fn || !el || !filter) {
       
   108             Y.log("delegate requires type, callback, parent, & filter", "warn");
       
   109             return;
       
   110         }
       
   111 
       
   112         container = (query) ? Y.Selector.query(query, null, true) : el;
       
   113 
       
   114         if (!container && isString(el)) {
       
   115             handle = Y.on('available', function () {
       
   116                 Y.mix(handle, Y.delegate.apply(Y, args), true);
       
   117             }, el);
       
   118         }
       
   119 
       
   120         if (!handle && container) {
       
   121             args.splice(2, 2, container); // remove the filter
       
   122 
       
   123             handle = Y.Event._attach(args, { facade: false });
       
   124             handle.sub.filter  = filter;
       
   125             handle.sub._notify = delegate.notifySub;
       
   126         }
       
   127     }
       
   128 
       
   129     if (handle && cat) {
       
   130         categories = detachCategories[cat]  || (detachCategories[cat] = {});
       
   131         categories = categories[type] || (categories[type] = []);
       
   132         categories.push(handle);
       
   133     }
       
   134 
       
   135     return handle;
       
   136 }
       
   137 
       
   138 /**
       
   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.
       
   141 
       
   142 This method is hosted as a private property of the `delegate` method
       
   143 (e.g. `Y.delegate.notifySub`)
       
   144 
       
   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
       
   154 **/
       
   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     }
       
   161 
       
   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;
       
   166 
       
   167     if (currentTarget) {
       
   168         // Support multiple matches up the the container subtree
       
   169         currentTarget = toArray(currentTarget);
       
   170 
       
   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);
       
   175 
       
   176         e.container = Y.one(ce.el);
       
   177 
       
   178         for (i = 0, len = currentTarget.length; i < len && !e.stopped; ++i) {
       
   179             e.currentTarget = Y.one(currentTarget[i]);
       
   180 
       
   181             ret = this.fn.apply(this.context || e.currentTarget, args);
       
   182 
       
   183             if (ret === false) { // stop further notifications
       
   184                 break;
       
   185             }
       
   186         }
       
   187 
       
   188         return ret;
       
   189     }
       
   190 };
       
   191 
       
   192 /**
       
   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.
       
   196 
       
   197 This function is memoized, so previously compiled filter functions are
       
   198 returned if the same selector string is provided.
       
   199 
       
   200 This function may be useful when defining synthetic events for delegate
       
   201 handling.
       
   202 
       
   203 Hosted as a property of the `delegate` method (e.g. `Y.delegate.compileFilter`).
       
   204 
       
   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
       
   210 **/
       
   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     };
       
   216 });
       
   217 
       
   218 /**
       
   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.
       
   227 
       
   228 @property _disabledRE
       
   229 @type {RegExp}
       
   230 @protected
       
   231 @since 3.8.1
       
   232 **/
       
   233 delegate._disabledRE = /^(?:button|input|select|textarea)$/i;
       
   234 
       
   235 /**
       
   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.
       
   239 
       
   240 Hosted as a protected property of the `delegate` method (e.g.
       
   241 `Y.delegate._applyFilter`).
       
   242 
       
   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
       
   250 **/
       
   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;
       
   257 
       
   258     // Resolve text nodes to their containing element
       
   259     if (target.nodeType === 3) {
       
   260         target = target.parentNode;
       
   261     }
       
   262 
       
   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     }
       
   270 
       
   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);
       
   275 
       
   276     if (isString(filter)) {
       
   277         while (target) {
       
   278             isContainer = (target === container);
       
   279             if (selectorTest(target, filter, (isContainer ? null: container))) {
       
   280                 match.push(target);
       
   281             }
       
   282 
       
   283             if (isContainer) {
       
   284                 break;
       
   285             }
       
   286 
       
   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);
       
   293 
       
   294         while (target) {
       
   295             // filter(target, e, extra args...) - this === target
       
   296             if (filter.apply(args[0], args)) {
       
   297                 match.push(target);
       
   298             }
       
   299 
       
   300             if (target === container) {
       
   301                 break;
       
   302             }
       
   303 
       
   304             target = target.parentNode;
       
   305             args[0] = Y.one(target);
       
   306         }
       
   307         args[1] = e; // restore the raw DOM event
       
   308     }
       
   309 
       
   310     if (match.length <= 1) {
       
   311         match = match[0]; // single match or undefined
       
   312     }
       
   313 
       
   314     // remove the target
       
   315     args.shift();
       
   316 
       
   317     return match;
       
   318 };
       
   319 
       
   320 /**
       
   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.
       
   325  *
       
   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').
       
   332  *
       
   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.
       
   338  *
       
   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;
       
   353 
       
   354 
       
   355 }, '3.10.3', {"requires": ["node-base"]});