3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
7 YUI.add('event-valuechange', function(Y) {
10 Adds a synthetic `valueChange` event that fires when the `value` property of an
11 `<input>` or `<textarea>` node changes as a result of a keystroke, mouse
12 operation, or input method editor (IME) input event.
16 YUI().use('event-valuechange', function (Y) {
17 Y.one('#my-input').on('valueChange', function (e) {
18 Y.log('previous value: ' + e.prevVal);
19 Y.log('new value: ' + e.newVal);
23 @module event-valuechange
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.
32 YUI().use('event-valuechange', function (Y) {
33 Y.one('#my-input').on('valueChange', function (e) {
34 Y.log('previous value: ' + e.prevVal);
35 Y.log('new value: ' + e.newVal);
43 var DATA_KEY = '_valuechange',
46 config, // defined at the end of this file
48 // Just a simple namespace to make methods overridable.
50 // -- Static Constants -----------------------------------------------------
53 Interval (in milliseconds) at which to poll for changes to the value of an
54 element with one or more `valueChange` subscribers when the user is likely
55 to be interacting with it.
57 @property POLL_INTERVAL
65 Timeout (in milliseconds) after which to stop polling when there hasn't been
66 any new activity (keypresses, mouse clicks, etc.) on an element.
75 // -- Protected Static Methods ---------------------------------------------
78 Called at an interval to poll for changes to the value of the specified
82 @param {Node} node Node to poll.
84 @param {Object} options Options object.
85 @param {EventFacade} [options.e] Event facade of the event that
86 initiated the polling.
91 _poll: function (node, options) {
92 var domNode = node._node, // performance cheat; getValue() is a big hit when polling
94 newVal = domNode && domNode.value,
95 vcData = node._data && node._data[DATA_KEY], // another perf cheat
98 if (!domNode || !vcData) {
99 Y.log('_poll: node #' + node.get('id') + ' disappeared; stopping polling and removing all notifiers.', 'warn', 'event-valuechange');
100 VC._stopPolling(node);
104 prevVal = vcData.prevVal;
106 if (newVal !== prevVal) {
107 vcData.prevVal = newVal;
111 currentTarget: (event && event.currentTarget) || node,
114 target : (event && event.target) || node
117 Y.Object.each(vcData.notifiers, function (notifier) {
118 notifier.fire(facade);
121 VC._refreshTimeout(node);
126 Restarts the inactivity timeout for the specified node.
128 @method _refreshTimeout
129 @param {Node} node Node to refresh.
130 @param {SyntheticEvent.Notifier} notifier
134 _refreshTimeout: function (node, notifier) {
135 // The node may have been destroyed, so check that it still exists
136 // before trying to get its data. Otherwise an error will occur.
138 Y.log('_stopPolling: node disappeared', 'warn', 'event-valuechange');
142 var vcData = node.getData(DATA_KEY);
144 VC._stopTimeout(node); // avoid dupes
146 // If we don't see any changes within the timeout period (10 seconds by
147 // default), stop polling.
148 vcData.timeout = setTimeout(function () {
149 Y.log('timeout: #' + node.get('id'), 'info', 'event-valuechange');
150 VC._stopPolling(node, notifier);
153 Y.log('_refreshTimeout: #' + node.get('id'), 'info', 'event-valuechange');
157 Begins polling for changes to the `value` property of the specified node. If
158 polling is already underway for the specified node, it will not be restarted
159 unless the `force` option is `true`
161 @method _startPolling
162 @param {Node} node Node to watch.
163 @param {SyntheticEvent.Notifier} notifier
165 @param {Object} options Options object.
166 @param {EventFacade} [options.e] Event facade of the event that
167 initiated the polling.
168 @param {Boolean} [options.force=false] If `true`, polling will be
169 restarted even if we're already polling this node.
174 _startPolling: function (node, notifier, options) {
175 if (!node.test('input,textarea')) {
176 Y.log('_startPolling: aborting poll on #' + node.get('id') + ' -- not an input or textarea', 'warn', 'event-valuechange');
180 var vcData = node.getData(DATA_KEY);
183 vcData = {prevVal: node.get(VALUE)};
184 node.setData(DATA_KEY, vcData);
187 vcData.notifiers || (vcData.notifiers = {});
189 // Don't bother continuing if we're already polling this node, unless
190 // `options.force` is true.
191 if (vcData.interval) {
193 VC._stopPolling(node, notifier); // restart polling, but avoid dupe polls
195 vcData.notifiers[Y.stamp(notifier)] = notifier;
200 // Poll for changes to the node's value. We can't rely on keyboard
201 // events for this, since the value may change due to a mouse-initiated
202 // paste event, an IME input event, or for some other reason that
203 // doesn't trigger a key event.
204 vcData.notifiers[Y.stamp(notifier)] = notifier;
206 vcData.interval = setInterval(function () {
207 VC._poll(node, vcData, options);
208 }, VC.POLL_INTERVAL);
210 Y.log('_startPolling: #' + node.get('id'), 'info', 'event-valuechange');
212 VC._refreshTimeout(node, notifier);
216 Stops polling for changes to the specified node's `value` attribute.
219 @param {Node} node Node to stop polling on.
220 @param {SyntheticEvent.Notifier} [notifier] Notifier to remove from the
221 node. If not specified, all notifiers will be removed.
225 _stopPolling: function (node, notifier) {
226 // The node may have been destroyed, so check that it still exists
227 // before trying to get its data. Otherwise an error will occur.
229 Y.log('_stopPolling: node disappeared', 'info', 'event-valuechange');
233 var vcData = node.getData(DATA_KEY) || {};
235 clearInterval(vcData.interval);
236 delete vcData.interval;
238 VC._stopTimeout(node);
241 vcData.notifiers && delete vcData.notifiers[Y.stamp(notifier)];
243 vcData.notifiers = {};
246 Y.log('_stopPolling: #' + node.get('id'), 'info', 'event-valuechange');
250 Clears the inactivity timeout for the specified node, if any.
257 _stopTimeout: function (node) {
258 var vcData = node.getData(DATA_KEY) || {};
260 clearTimeout(vcData.timeout);
261 delete vcData.timeout;
264 // -- Protected Static Event Handlers --------------------------------------
267 Stops polling when a node's blur event fires.
270 @param {EventFacade} e
271 @param {SyntheticEvent.Notifier} notifier
275 _onBlur: function (e, notifier) {
276 VC._stopPolling(e.currentTarget, notifier);
280 Resets a node's history and starts polling when a focus event occurs.
283 @param {EventFacade} e
284 @param {SyntheticEvent.Notifier} notifier
288 _onFocus: function (e, notifier) {
289 var node = e.currentTarget,
290 vcData = node.getData(DATA_KEY);
294 node.setData(DATA_KEY, vcData);
297 vcData.prevVal = node.get(VALUE);
299 VC._startPolling(node, notifier, {e: e});
303 Starts polling when a node receives a keyDown event.
306 @param {EventFacade} e
307 @param {SyntheticEvent.Notifier} notifier
311 _onKeyDown: function (e, notifier) {
312 VC._startPolling(e.currentTarget, notifier, {e: e});
316 Starts polling when an IME-related keyUp event occurs on a node.
319 @param {EventFacade} e
320 @param {SyntheticEvent.Notifier} notifier
324 _onKeyUp: function (e, notifier) {
325 // These charCodes indicate that an IME has started. We'll restart
326 // polling and give the IME up to 10 seconds (by default) to finish.
327 if (e.charCode === 229 || e.charCode === 197) {
328 VC._startPolling(e.currentTarget, notifier, {
336 Starts polling when a node receives a mouseDown event.
339 @param {EventFacade} e
340 @param {SyntheticEvent.Notifier} notifier
344 _onMouseDown: function (e, notifier) {
345 VC._startPolling(e.currentTarget, notifier, {e: e});
349 Called when the `valuechange` event receives a new subscriber.
353 @param {Subscription} sub
354 @param {SyntheticEvent.Notifier} notifier
355 @param {Function|String} [filter] Filter function or selector string. Only
356 provided for delegate subscriptions.
360 _onSubscribe: function (node, sub, notifier, filter) {
361 var _valuechange, callbacks, nodes;
366 keydown : VC._onKeyDown,
368 mousedown: VC._onMouseDown
371 // Store a utility object on the notifier to hold stuff that needs to be
372 // passed around to trigger event handlers, polling handlers, etc.
373 _valuechange = notifier._valuechange = {};
376 // If a filter is provided, then this is a delegated subscription.
377 _valuechange.delegated = true;
379 // Add a function to the notifier that we can use to find all
380 // nodes that pass the delegate filter.
381 _valuechange.getNodes = function () {
382 return node.all('input,textarea').filter(filter);
385 // Store the initial values for each descendant of the container
386 // node that passes the delegate filter.
387 _valuechange.getNodes().each(function (child) {
388 if (!child.getData(DATA_KEY)) {
389 child.setData(DATA_KEY, {prevVal: child.get(VALUE)});
393 notifier._handles = Y.delegate(callbacks, node, filter, null,
396 // This is a normal (non-delegated) event subscription.
398 if (!node.test('input,textarea')) {
402 if (!node.getData(DATA_KEY)) {
403 node.setData(DATA_KEY, {prevVal: node.get(VALUE)});
406 notifier._handles = node.on(callbacks, null, null, notifier);
411 Called when the `valuechange` event loses a subscriber.
413 @method _onUnsubscribe
415 @param {Subscription} subscription
416 @param {SyntheticEvent.Notifier} notifier
420 _onUnsubscribe: function (node, subscription, notifier) {
421 var _valuechange = notifier._valuechange;
423 notifier._handles && notifier._handles.detach();
425 if (_valuechange.delegated) {
426 _valuechange.getNodes().each(function (child) {
427 VC._stopPolling(child, notifier);
430 VC._stopPolling(node, notifier);
436 Synthetic event that fires when the `value` property of an `<input>` or
437 `<textarea>` node changes as a result of a user-initiated keystroke, mouse
438 operation, or input method editor (IME) input event.
440 Unlike the `onchange` event, this event fires when the value actually changes
441 and not when the element loses focus. This event also reports IME and
442 multi-stroke input more reliably than `oninput` or the various key events across
445 For performance reasons, only focused nodes are monitored for changes, so
446 programmatic value changes on nodes that don't have focus won't be detected.
450 YUI().use('event-valuechange', function (Y) {
451 Y.one('#my-input').on('valueChange', function (e) {
452 Y.log('previous value: ' + e.prevVal);
453 Y.log('new value: ' + e.newVal);
458 @param {String} prevVal Previous value prior to the latest change.
459 @param {String} newVal New value after the latest change.
464 detach: VC._onUnsubscribe,
465 on : VC._onSubscribe,
467 delegate : VC._onSubscribe,
468 detachDelegate: VC._onUnsubscribe,
475 Y.Event.define('valuechange', config);
476 Y.Event.define('valueChange', config); // deprecated, but supported for backcompat
481 }, '3.5.0' ,{requires:['event-focus', 'event-synthetic']});