[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/event-valuechange/ -> event-valuechange.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-valuechange', function (Y, NAME) {
   9  
  10  /**
  11  Adds a synthetic `valuechange` event that fires when the `value` property of an
  12  `<input>`, `<textarea>`, `<select>`, or `[contenteditable="true"]` node changes
  13  as a result of a keystroke, mouse operation, or input method editor (IME)
  14  input event.
  15  
  16  Usage:
  17  
  18      YUI().use('event-valuechange', function (Y) {
  19          Y.one('#my-input').on('valuechange', function (e) {
  20          });
  21      });
  22  
  23  @module event-valuechange
  24  **/
  25  
  26  /**
  27  Provides the implementation for the synthetic `valuechange` event. This class
  28  isn't meant to be used directly, but is public to make monkeypatching possible.
  29  
  30  Usage:
  31  
  32      YUI().use('event-valuechange', function (Y) {
  33          Y.one('#my-input').on('valuechange', function (e) {
  34          });
  35      });
  36  
  37  @class ValueChange
  38  @static
  39  */
  40  
  41  var DATA_KEY = '_valuechange',
  42      VALUE    = 'value',
  43      NODE_NAME = 'nodeName',
  44  
  45      config, // defined at the end of this file
  46  
  47  // Just a simple namespace to make methods overridable.
  48  VC = {
  49      // -- Static Constants -----------------------------------------------------
  50  
  51      /**
  52      Interval (in milliseconds) at which to poll for changes to the value of an
  53      element with one or more `valuechange` subscribers when the user is likely
  54      to be interacting with it.
  55  
  56      @property POLL_INTERVAL
  57      @type Number
  58      @default 50
  59      @static
  60      **/
  61      POLL_INTERVAL: 50,
  62  
  63      /**
  64      Timeout (in milliseconds) after which to stop polling when there hasn't been
  65      any new activity (keypresses, mouse clicks, etc.) on an element.
  66  
  67      @property TIMEOUT
  68      @type Number
  69      @default 10000
  70      @static
  71      **/
  72      TIMEOUT: 10000,
  73  
  74      // -- Protected Static Methods ---------------------------------------------
  75  
  76      /**
  77      Called at an interval to poll for changes to the value of the specified
  78      node.
  79  
  80      @method _poll
  81      @param {Node} node Node to poll.
  82  
  83      @param {Object} options Options object.
  84          @param {EventFacade} [options.e] Event facade of the event that
  85              initiated the polling.
  86  
  87      @protected
  88      @static
  89      **/
  90      _poll: function (node, options) {
  91          var domNode  = node._node, // performance cheat; getValue() is a big hit when polling
  92              event    = options.e,
  93              vcData   = node._data && node._data[DATA_KEY], // another perf cheat
  94              stopped  = 0,
  95              facade, prevVal, newVal, nodeName, selectedOption, stopElement;
  96  
  97          if (!(domNode && vcData)) {
  98              VC._stopPolling(node);
  99              return;
 100          }
 101  
 102          prevVal = vcData.prevVal;
 103          nodeName  = vcData.nodeName;
 104  
 105          if (vcData.isEditable) {
 106              // Use innerHTML for performance
 107              newVal = domNode.innerHTML;
 108          } else if (nodeName === 'input' || nodeName === 'textarea') {
 109              // Use value property for performance
 110              newVal = domNode.value;
 111          } else if (nodeName === 'select') {
 112              // Back-compatibility with IE6 <select> element values.
 113              // Huge performance cheat to get past node.get('value').
 114              selectedOption = domNode.options[domNode.selectedIndex];
 115              newVal = selectedOption.value || selectedOption.text;
 116          }
 117  
 118          if (newVal !== prevVal) {
 119              vcData.prevVal = newVal;
 120  
 121              facade = {
 122                  _event       : event,
 123                  currentTarget: (event && event.currentTarget) || node,
 124                  newVal       : newVal,
 125                  prevVal      : prevVal,
 126                  target       : (event && event.target) || node
 127              };
 128  
 129              Y.Object.some(vcData.notifiers, function (notifier) {
 130                  var evt = notifier.handle.evt,
 131                      newStopped;
 132  
 133                  // support e.stopPropagation()
 134                  if (stopped !== 1) {
 135                      notifier.fire(facade);
 136                  } else if (evt.el === stopElement) {
 137                      notifier.fire(facade);
 138                  }
 139  
 140                  newStopped = evt && evt._facade ? evt._facade.stopped : 0;
 141  
 142                  // need to consider the condition in which there are two
 143                  // listeners on the same element:
 144                  // listener 1 calls e.stopPropagation()
 145                  // listener 2 calls e.stopImmediatePropagation()
 146                  if (newStopped > stopped) {
 147                      stopped = newStopped;
 148  
 149                      if (stopped === 1) {
 150                          stopElement = evt.el;
 151                      }
 152                  }
 153  
 154                  // support e.stopImmediatePropagation()
 155                  if (stopped === 2) {
 156                      return true;
 157                  }
 158              });
 159  
 160              VC._refreshTimeout(node);
 161          }
 162      },
 163  
 164      /**
 165      Restarts the inactivity timeout for the specified node.
 166  
 167      @method _refreshTimeout
 168      @param {Node} node Node to refresh.
 169      @param {SyntheticEvent.Notifier} notifier
 170      @protected
 171      @static
 172      **/
 173      _refreshTimeout: function (node, notifier) {
 174          // The node may have been destroyed, so check that it still exists
 175          // before trying to get its data. Otherwise an error will occur.
 176          if (!node._node) {
 177              return;
 178          }
 179  
 180          var vcData = node.getData(DATA_KEY);
 181  
 182          VC._stopTimeout(node); // avoid dupes
 183  
 184          // If we don't see any changes within the timeout period (10 seconds by
 185          // default), stop polling.
 186          vcData.timeout = setTimeout(function () {
 187              VC._stopPolling(node, notifier);
 188          }, VC.TIMEOUT);
 189  
 190      },
 191  
 192      /**
 193      Begins polling for changes to the `value` property of the specified node. If
 194      polling is already underway for the specified node, it will not be restarted
 195      unless the `force` option is `true`
 196  
 197      @method _startPolling
 198      @param {Node} node Node to watch.
 199      @param {SyntheticEvent.Notifier} notifier
 200  
 201      @param {Object} options Options object.
 202          @param {EventFacade} [options.e] Event facade of the event that
 203              initiated the polling.
 204          @param {Boolean} [options.force=false] If `true`, polling will be
 205              restarted even if we're already polling this node.
 206  
 207      @protected
 208      @static
 209      **/
 210      _startPolling: function (node, notifier, options) {
 211          var vcData, isEditable;
 212  
 213          if (!node.test('input,textarea,select') && !(isEditable = VC._isEditable(node))) {
 214              return;
 215          }
 216  
 217          vcData = node.getData(DATA_KEY);
 218  
 219          if (!vcData) {
 220              vcData = {
 221                  nodeName   : node.get(NODE_NAME).toLowerCase(),
 222                  isEditable : isEditable,
 223                  prevVal    : isEditable ? node.getDOMNode().innerHTML : node.get(VALUE)
 224              };
 225  
 226              node.setData(DATA_KEY, vcData);
 227          }
 228  
 229          vcData.notifiers || (vcData.notifiers = {});
 230  
 231          // Don't bother continuing if we're already polling this node, unless
 232          // `options.force` is true.
 233          if (vcData.interval) {
 234              if (options.force) {
 235                  VC._stopPolling(node, notifier); // restart polling, but avoid dupe polls
 236              } else {
 237                  vcData.notifiers[Y.stamp(notifier)] = notifier;
 238                  return;
 239              }
 240          }
 241  
 242          // Poll for changes to the node's value. We can't rely on keyboard
 243          // events for this, since the value may change due to a mouse-initiated
 244          // paste event, an IME input event, or for some other reason that
 245          // doesn't trigger a key event.
 246          vcData.notifiers[Y.stamp(notifier)] = notifier;
 247  
 248          vcData.interval = setInterval(function () {
 249              VC._poll(node, options);
 250          }, VC.POLL_INTERVAL);
 251  
 252  
 253          VC._refreshTimeout(node, notifier);
 254      },
 255  
 256      /**
 257      Stops polling for changes to the specified node's `value` attribute.
 258  
 259      @method _stopPolling
 260      @param {Node} node Node to stop polling on.
 261      @param {SyntheticEvent.Notifier} [notifier] Notifier to remove from the
 262          node. If not specified, all notifiers will be removed.
 263      @protected
 264      @static
 265      **/
 266      _stopPolling: function (node, notifier) {
 267          // The node may have been destroyed, so check that it still exists
 268          // before trying to get its data. Otherwise an error will occur.
 269          if (!node._node) {
 270              return;
 271          }
 272  
 273          var vcData = node.getData(DATA_KEY) || {};
 274  
 275          clearInterval(vcData.interval);
 276          delete vcData.interval;
 277  
 278          VC._stopTimeout(node);
 279  
 280          if (notifier) {
 281              vcData.notifiers && delete vcData.notifiers[Y.stamp(notifier)];
 282          } else {
 283              vcData.notifiers = {};
 284          }
 285  
 286      },
 287  
 288      /**
 289      Clears the inactivity timeout for the specified node, if any.
 290  
 291      @method _stopTimeout
 292      @param {Node} node
 293      @protected
 294      @static
 295      **/
 296      _stopTimeout: function (node) {
 297          var vcData = node.getData(DATA_KEY) || {};
 298  
 299          clearTimeout(vcData.timeout);
 300          delete vcData.timeout;
 301      },
 302  
 303      /**
 304      Check to see if a node has editable content or not.
 305  
 306      TODO: Add additional checks to get it to work for child nodes
 307      that inherit "contenteditable" from parent nodes. This may be
 308      too computationally intensive to be placed inside of the `_poll`
 309      loop, however.
 310  
 311      @method _isEditable
 312      @param {Node} node
 313      @protected
 314      @static
 315      **/
 316      _isEditable: function (node) {
 317          // Performance cheat because this is used inside `_poll`
 318          var domNode = node._node;
 319          return domNode.contentEditable === 'true' ||
 320                 domNode.contentEditable === '';
 321      },
 322  
 323  
 324  
 325      // -- Protected Static Event Handlers --------------------------------------
 326  
 327      /**
 328      Stops polling when a node's blur event fires.
 329  
 330      @method _onBlur
 331      @param {EventFacade} e
 332      @param {SyntheticEvent.Notifier} notifier
 333      @protected
 334      @static
 335      **/
 336      _onBlur: function (e, notifier) {
 337          VC._stopPolling(e.currentTarget, notifier);
 338      },
 339  
 340      /**
 341      Resets a node's history and starts polling when a focus event occurs.
 342  
 343      @method _onFocus
 344      @param {EventFacade} e
 345      @param {SyntheticEvent.Notifier} notifier
 346      @protected
 347      @static
 348      **/
 349      _onFocus: function (e, notifier) {
 350          var node       = e.currentTarget,
 351              vcData     = node.getData(DATA_KEY);
 352  
 353          if (!vcData) {
 354              vcData = {
 355                  isEditable : VC._isEditable(node),
 356                  nodeName   : node.get(NODE_NAME).toLowerCase()
 357              };
 358              node.setData(DATA_KEY, vcData);
 359          }
 360  
 361          vcData.prevVal = vcData.isEditable ? node.getDOMNode().innerHTML : node.get(VALUE);
 362  
 363          VC._startPolling(node, notifier, {e: e});
 364      },
 365  
 366      /**
 367      Starts polling when a node receives a keyDown event.
 368  
 369      @method _onKeyDown
 370      @param {EventFacade} e
 371      @param {SyntheticEvent.Notifier} notifier
 372      @protected
 373      @static
 374      **/
 375      _onKeyDown: function (e, notifier) {
 376          VC._startPolling(e.currentTarget, notifier, {e: e});
 377      },
 378  
 379      /**
 380      Starts polling when an IME-related keyUp event occurs on a node.
 381  
 382      @method _onKeyUp
 383      @param {EventFacade} e
 384      @param {SyntheticEvent.Notifier} notifier
 385      @protected
 386      @static
 387      **/
 388      _onKeyUp: function (e, notifier) {
 389          // These charCodes indicate that an IME has started. We'll restart
 390          // polling and give the IME up to 10 seconds (by default) to finish.
 391          if (e.charCode === 229 || e.charCode === 197) {
 392              VC._startPolling(e.currentTarget, notifier, {
 393                  e    : e,
 394                  force: true
 395              });
 396          }
 397      },
 398  
 399      /**
 400      Starts polling when a node receives a mouseDown event.
 401  
 402      @method _onMouseDown
 403      @param {EventFacade} e
 404      @param {SyntheticEvent.Notifier} notifier
 405      @protected
 406      @static
 407      **/
 408      _onMouseDown: function (e, notifier) {
 409          VC._startPolling(e.currentTarget, notifier, {e: e});
 410      },
 411  
 412      /**
 413      Called when the `valuechange` event receives a new subscriber.
 414  
 415      Child nodes that aren't initially available when this subscription is
 416      called will still fire the `valuechange` event after their data is
 417      collected when the delegated `focus` event is captured. This includes
 418      elements that haven't been inserted into the DOM yet, as well as
 419      elements that aren't initially `contenteditable`.
 420  
 421      @method _onSubscribe
 422      @param {Node} node
 423      @param {Subscription} sub
 424      @param {SyntheticEvent.Notifier} notifier
 425      @param {Function|String} [filter] Filter function or selector string. Only
 426          provided for delegate subscriptions.
 427      @protected
 428      @static
 429      **/
 430      _onSubscribe: function (node, sub, notifier, filter) {
 431          var _valuechange, callbacks, isEditable, inputNodes, editableNodes;
 432  
 433          callbacks = {
 434              blur     : VC._onBlur,
 435              focus    : VC._onFocus,
 436              keydown  : VC._onKeyDown,
 437              keyup    : VC._onKeyUp,
 438              mousedown: VC._onMouseDown
 439          };
 440  
 441          // Store a utility object on the notifier to hold stuff that needs to be
 442          // passed around to trigger event handlers, polling handlers, etc.
 443          _valuechange = notifier._valuechange = {};
 444  
 445          if (filter) {
 446              // If a filter is provided, then this is a delegated subscription.
 447              _valuechange.delegated = true;
 448  
 449              // Add a function to the notifier that we can use to find all
 450              // nodes that pass the delegate filter.
 451              _valuechange.getNodes = function () {
 452                  inputNodes    = node.all('input,textarea,select').filter(filter);
 453                  editableNodes = node.all('[contenteditable="true"],[contenteditable=""]').filter(filter);
 454  
 455                  return inputNodes.concat(editableNodes);
 456              };
 457  
 458              // Store the initial values for each descendant of the container
 459              // node that passes the delegate filter.
 460              _valuechange.getNodes().each(function (child) {
 461                  if (!child.getData(DATA_KEY)) {
 462                      child.setData(DATA_KEY, {
 463                          nodeName   : child.get(NODE_NAME).toLowerCase(),
 464                          isEditable : VC._isEditable(child),
 465                          prevVal    : isEditable ? child.getDOMNode().innerHTML : child.get(VALUE)
 466                      });
 467                  }
 468              });
 469  
 470              notifier._handles = Y.delegate(callbacks, node, filter, null,
 471                  notifier);
 472          } else {
 473              isEditable = VC._isEditable(node);
 474              // This is a normal (non-delegated) event subscription.
 475              if (!node.test('input,textarea,select') && !isEditable) {
 476                  return;
 477              }
 478  
 479              if (!node.getData(DATA_KEY)) {
 480                  node.setData(DATA_KEY, {
 481                      nodeName   : node.get(NODE_NAME).toLowerCase(),
 482                      isEditable : isEditable,
 483                      prevVal    : isEditable ? node.getDOMNode().innerHTML : node.get(VALUE)
 484                  });
 485              }
 486  
 487              notifier._handles = node.on(callbacks, null, null, notifier);
 488          }
 489      },
 490  
 491      /**
 492      Called when the `valuechange` event loses a subscriber.
 493  
 494      @method _onUnsubscribe
 495      @param {Node} node
 496      @param {Subscription} subscription
 497      @param {SyntheticEvent.Notifier} notifier
 498      @protected
 499      @static
 500      **/
 501      _onUnsubscribe: function (node, subscription, notifier) {
 502          var _valuechange = notifier._valuechange;
 503  
 504          notifier._handles && notifier._handles.detach();
 505  
 506          if (_valuechange.delegated) {
 507              _valuechange.getNodes().each(function (child) {
 508                  VC._stopPolling(child, notifier);
 509              });
 510          } else {
 511              VC._stopPolling(node, notifier);
 512          }
 513      }
 514  };
 515  
 516  /**
 517  Synthetic event that fires when the `value` property of an `<input>`,
 518  `<textarea>`, `<select>`, or `[contenteditable="true"]` node changes as a
 519  result of a user-initiated keystroke, mouse operation, or input method
 520  editor (IME) input event.
 521  
 522  Unlike the `onchange` event, this event fires when the value actually changes
 523  and not when the element loses focus. This event also reports IME and
 524  multi-stroke input more reliably than `oninput` or the various key events across
 525  browsers.
 526  
 527  For performance reasons, only focused nodes are monitored for changes, so
 528  programmatic value changes on nodes that don't have focus won't be detected.
 529  
 530  @example
 531  
 532      YUI().use('event-valuechange', function (Y) {
 533          Y.one('#my-input').on('valuechange', function (e) {
 534          });
 535      });
 536  
 537  @event valuechange
 538  @param {String} prevVal Previous value prior to the latest change.
 539  @param {String} newVal New value after the latest change.
 540  @for YUI
 541  **/
 542  
 543  config = {
 544      detach: VC._onUnsubscribe,
 545      on    : VC._onSubscribe,
 546  
 547      delegate      : VC._onSubscribe,
 548      detachDelegate: VC._onUnsubscribe,
 549  
 550      publishConfig: {
 551          emitFacade: true
 552      }
 553  };
 554  
 555  Y.Event.define('valuechange', config);
 556  Y.Event.define('valueChange', config); // deprecated, but supported for backcompat
 557  
 558  Y.ValueChange = VC;
 559  
 560  
 561  }, '3.17.2', {"requires": ["event-focus", "event-synthetic"]});


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