[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/event-delegate/ -> event-delegate.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              return;
 109          }
 110  
 111          container = (query) ? Y.Selector.query(query, null, true) : el;
 112  
 113          if (!container && isString(el)) {
 114              handle = Y.on('available', function () {
 115                  Y.mix(handle, Y.delegate.apply(Y, args), true);
 116              }, el);
 117          }
 118  
 119          if (!handle && container) {
 120              args.splice(2, 2, container); // remove the filter
 121  
 122              handle = Y.Event._attach(args, { facade: false });
 123              handle.sub.filter  = filter;
 124              handle.sub._notify = delegate.notifySub;
 125          }
 126      }
 127  
 128      if (handle && cat) {
 129          categories = detachCategories[cat]  || (detachCategories[cat] = {});
 130          categories = categories[type] || (categories[type] = []);
 131          categories.push(handle);
 132      }
 133  
 134      return handle;
 135  }
 136  
 137  /**
 138  Overrides the <code>_notify</code> method on the normal DOM subscription to
 139  inject the filtering logic and only proceed in the case of a match.
 140  
 141  This method is hosted as a private property of the `delegate` method
 142  (e.g. `Y.delegate.notifySub`)
 143  
 144  @method notifySub
 145  @param thisObj {Object} default 'this' object for the callback
 146  @param args {Array} arguments passed to the event's <code>fire()</code>
 147  @param ce {CustomEvent} the custom event managing the DOM subscriptions for
 148               the subscribed event on the subscribing node.
 149  @return {Boolean} false if the event was stopped
 150  @private
 151  @static
 152  @since 3.2.0
 153  **/
 154  delegate.notifySub = function (thisObj, args, ce) {
 155      // Preserve args for other subscribers
 156      args = args.slice();
 157      if (this.args) {
 158          args.push.apply(args, this.args);
 159      }
 160  
 161      // Only notify subs if the event occurred on a targeted element
 162      var currentTarget = delegate._applyFilter(this.filter, args, ce),
 163          //container     = e.currentTarget,
 164          e, i, len, ret;
 165  
 166      if (currentTarget) {
 167          // Support multiple matches up the the container subtree
 168          currentTarget = toArray(currentTarget);
 169  
 170          // The second arg is the currentTarget, but we'll be reusing this
 171          // facade, replacing the currentTarget for each use, so it doesn't
 172          // matter what element we seed it with.
 173          e = args[0] = new Y.DOMEventFacade(args[0], ce.el, ce);
 174  
 175          e.container = Y.one(ce.el);
 176  
 177          for (i = 0, len = currentTarget.length; i < len && !e.stopped; ++i) {
 178              e.currentTarget = Y.one(currentTarget[i]);
 179  
 180              ret = this.fn.apply(this.context || e.currentTarget, args);
 181  
 182              if (ret === false) { // stop further notifications
 183                  break;
 184              }
 185          }
 186  
 187          return ret;
 188      }
 189  };
 190  
 191  /**
 192  Compiles a selector string into a filter function to identify whether
 193  Nodes along the parent axis of an event's target should trigger event
 194  notification.
 195  
 196  This function is memoized, so previously compiled filter functions are
 197  returned if the same selector string is provided.
 198  
 199  This function may be useful when defining synthetic events for delegate
 200  handling.
 201  
 202  Hosted as a property of the `delegate` method (e.g. `Y.delegate.compileFilter`).
 203  
 204  @method compileFilter
 205  @param selector {String} the selector string to base the filtration on
 206  @return {Function}
 207  @since 3.2.0
 208  @static
 209  **/
 210  delegate.compileFilter = Y.cached(function (selector) {
 211      return function (target, e) {
 212          return selectorTest(target._node, selector,
 213              (e.currentTarget === e.target) ? null : e.currentTarget._node);
 214      };
 215  });
 216  
 217  /**
 218  Regex to test for disabled elements during filtering. This is only relevant to
 219  IE to normalize behavior with other browsers, which swallow events that occur
 220  to disabled elements. IE fires the event from the parent element instead of the
 221  original target, though it does preserve `event.srcElement` as the disabled
 222  element. IE also supports disabled on `<a>`, but the event still bubbles, so it
 223  acts more like `e.preventDefault()` plus styling. That issue is not handled here
 224  because other browsers fire the event on the `<a>`, so delegate is supported in
 225  both cases.
 226  
 227  @property _disabledRE
 228  @type {RegExp}
 229  @protected
 230  @since 3.8.1
 231  **/
 232  delegate._disabledRE = /^(?:button|input|select|textarea)$/i;
 233  
 234  /**
 235  Walks up the parent axis of an event's target, and tests each element
 236  against a supplied filter function.  If any Nodes, including the container,
 237  satisfy the filter, the delegated callback will be triggered for each.
 238  
 239  Hosted as a protected property of the `delegate` method (e.g.
 240  `Y.delegate._applyFilter`).
 241  
 242  @method _applyFilter
 243  @param filter {Function} boolean function to test for inclusion in event
 244                   notification
 245  @param args {Array} the arguments that would be passed to subscribers
 246  @param ce   {CustomEvent} the DOM event wrapper
 247  @return {Node|Node[]|undefined} The Node or Nodes that satisfy the filter
 248  @protected
 249  **/
 250  delegate._applyFilter = function (filter, args, ce) {
 251      var e         = args[0],
 252          container = ce.el, // facadeless events in IE, have no e.currentTarget
 253          target    = e.target || e.srcElement,
 254          match     = [],
 255          isContainer = false;
 256  
 257      // Resolve text nodes to their containing element
 258      if (target.nodeType === 3) {
 259          target = target.parentNode;
 260      }
 261  
 262      // For IE. IE propagates events from the parent element of disabled
 263      // elements, where other browsers swallow the event entirely. To normalize
 264      // this in IE, filtering for matching elements should abort if the target
 265      // is a disabled form control.
 266      if (target.disabled && delegate._disabledRE.test(target.nodeName)) {
 267          return match;
 268      }
 269  
 270      // passing target as the first arg rather than leaving well enough alone
 271      // making 'this' in the filter function refer to the target.  This is to
 272      // support bound filter functions.
 273      args.unshift(target);
 274  
 275      if (isString(filter)) {
 276          while (target) {
 277              isContainer = (target === container);
 278              if (selectorTest(target, filter, (isContainer ? null: container))) {
 279                  match.push(target);
 280              }
 281  
 282              if (isContainer) {
 283                  break;
 284              }
 285  
 286              target = target.parentNode;
 287          }
 288      } else {
 289          // filter functions are implementer code and should receive wrappers
 290          args[0] = Y.one(target);
 291          args[1] = new Y.DOMEventFacade(e, container, ce);
 292  
 293          while (target) {
 294              // filter(target, e, extra args...) - this === target
 295              if (filter.apply(args[0], args)) {
 296                  match.push(target);
 297              }
 298  
 299              if (target === container) {
 300                  break;
 301              }
 302  
 303              target = target.parentNode;
 304              args[0] = Y.one(target);
 305          }
 306          args[1] = e; // restore the raw DOM event
 307      }
 308  
 309      if (match.length <= 1) {
 310          match = match[0]; // single match or undefined
 311      }
 312  
 313      // remove the target
 314      args.shift();
 315  
 316      return match;
 317  };
 318  
 319  /**
 320   * Sets up event delegation on a container element.  The delegated event
 321   * will use a supplied filter to test if the callback should be executed.
 322   * This filter can be either a selector string or a function that returns
 323   * a Node to use as the currentTarget for the event.
 324   *
 325   * The event object for the delegated event is supplied to the callback
 326   * function.  It is modified slightly in order to support all properties
 327   * that may be needed for event delegation.  'currentTarget' is set to
 328   * the element that matched the selector string filter or the Node returned
 329   * from the filter function.  'container' is set to the element that the
 330   * listener is delegated from (this normally would be the 'currentTarget').
 331   *
 332   * Filter functions will be called with the arguments that would be passed to
 333   * the callback function, including the event object as the first parameter.
 334   * The function should return false (or a falsey value) if the success criteria
 335   * aren't met, and the Node to use as the event's currentTarget and 'this'
 336   * object if they are.
 337   *
 338   * @method delegate
 339   * @param type {string} the event type to delegate
 340   * @param fn {function} the callback function to execute.  This function
 341   * will be provided the event object for the delegated event.
 342   * @param el {string|node} the element that is the delegation container
 343   * @param filter {string|function} a selector that must match the target of the
 344   * event or a function that returns a Node or false.
 345   * @param context optional argument that specifies what 'this' refers to.
 346   * @param args* 0..n additional arguments to pass on to the callback function.
 347   * These arguments will be added after the event object.
 348   * @return {EventHandle} the detach handle
 349   * @for YUI
 350   */
 351  Y.delegate = Y.Event.delegate = delegate;
 352  
 353  
 354  }, '3.17.2', {"requires": ["node-base"]});


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