[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/event-delegate/ -> event-delegate-debug.js (source)

   1  /*
   2  YUI 3.17.2 (build 9c3c78e)
   3  Copyright 2014 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.17.2', {"requires": ["node-base"]});


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1