[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

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


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