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