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) {
21 @module event-valuechange
25 Provides the implementation for the synthetic `valueChange` event. This class
26 isn't meant to be used directly, but is public to make monkeypatching possible.
30 YUI().use('event-valuechange', function (Y) {
31 Y.one('#my-input').on('valueChange', function (e) {
39 var DATA_KEY = '_valuechange',
42 config, // defined at the end of this file
44 // Just a simple namespace to make methods overridable.
46 // -- Static Constants -----------------------------------------------------
49 Interval (in milliseconds) at which to poll for changes to the value of an
50 element with one or more `valueChange` subscribers when the user is likely
51 to be interacting with it.
53 @property POLL_INTERVAL
61 Timeout (in milliseconds) after which to stop polling when there hasn't been
62 any new activity (keypresses, mouse clicks, etc.) on an element.
71 // -- Protected Static Methods ---------------------------------------------
74 Called at an interval to poll for changes to the value of the specified
78 @param {Node} node Node to poll.
80 @param {Object} options Options object.
81 @param {EventFacade} [options.e] Event facade of the event that
82 initiated the polling.
87 _poll: function (node, options) {
88 var domNode = node._node, // performance cheat; getValue() is a big hit when polling
90 newVal = domNode && domNode.value,
91 vcData = node._data && node._data[DATA_KEY], // another perf cheat
94 if (!domNode || !vcData) {
95 VC._stopPolling(node);
99 prevVal = vcData.prevVal;
101 if (newVal !== prevVal) {
102 vcData.prevVal = newVal;
106 currentTarget: (event && event.currentTarget) || node,
109 target : (event && event.target) || node
112 Y.Object.each(vcData.notifiers, function (notifier) {
113 notifier.fire(facade);
116 VC._refreshTimeout(node);
121 Restarts the inactivity timeout for the specified node.
123 @method _refreshTimeout
124 @param {Node} node Node to refresh.
125 @param {SyntheticEvent.Notifier} notifier
129 _refreshTimeout: function (node, notifier) {
130 // The node may have been destroyed, so check that it still exists
131 // before trying to get its data. Otherwise an error will occur.
136 var vcData = node.getData(DATA_KEY);
138 VC._stopTimeout(node); // avoid dupes
140 // If we don't see any changes within the timeout period (10 seconds by
141 // default), stop polling.
142 vcData.timeout = setTimeout(function () {
143 VC._stopPolling(node, notifier);
149 Begins polling for changes to the `value` property of the specified node. If
150 polling is already underway for the specified node, it will not be restarted
151 unless the `force` option is `true`
153 @method _startPolling
154 @param {Node} node Node to watch.
155 @param {SyntheticEvent.Notifier} notifier
157 @param {Object} options Options object.
158 @param {EventFacade} [options.e] Event facade of the event that
159 initiated the polling.
160 @param {Boolean} [options.force=false] If `true`, polling will be
161 restarted even if we're already polling this node.
166 _startPolling: function (node, notifier, options) {
167 if (!node.test('input,textarea')) {
171 var vcData = node.getData(DATA_KEY);
174 vcData = {prevVal: node.get(VALUE)};
175 node.setData(DATA_KEY, vcData);
178 vcData.notifiers || (vcData.notifiers = {});
180 // Don't bother continuing if we're already polling this node, unless
181 // `options.force` is true.
182 if (vcData.interval) {
184 VC._stopPolling(node, notifier); // restart polling, but avoid dupe polls
186 vcData.notifiers[Y.stamp(notifier)] = notifier;
191 // Poll for changes to the node's value. We can't rely on keyboard
192 // events for this, since the value may change due to a mouse-initiated
193 // paste event, an IME input event, or for some other reason that
194 // doesn't trigger a key event.
195 vcData.notifiers[Y.stamp(notifier)] = notifier;
197 vcData.interval = setInterval(function () {
198 VC._poll(node, vcData, options);
199 }, VC.POLL_INTERVAL);
202 VC._refreshTimeout(node, notifier);
206 Stops polling for changes to the specified node's `value` attribute.
209 @param {Node} node Node to stop polling on.
210 @param {SyntheticEvent.Notifier} [notifier] Notifier to remove from the
211 node. If not specified, all notifiers will be removed.
215 _stopPolling: function (node, notifier) {
216 // The node may have been destroyed, so check that it still exists
217 // before trying to get its data. Otherwise an error will occur.
222 var vcData = node.getData(DATA_KEY) || {};
224 clearInterval(vcData.interval);
225 delete vcData.interval;
227 VC._stopTimeout(node);
230 vcData.notifiers && delete vcData.notifiers[Y.stamp(notifier)];
232 vcData.notifiers = {};
238 Clears the inactivity timeout for the specified node, if any.
245 _stopTimeout: function (node) {
246 var vcData = node.getData(DATA_KEY) || {};
248 clearTimeout(vcData.timeout);
249 delete vcData.timeout;
252 // -- Protected Static Event Handlers --------------------------------------
255 Stops polling when a node's blur event fires.
258 @param {EventFacade} e
259 @param {SyntheticEvent.Notifier} notifier
263 _onBlur: function (e, notifier) {
264 VC._stopPolling(e.currentTarget, notifier);
268 Resets a node's history and starts polling when a focus event occurs.
271 @param {EventFacade} e
272 @param {SyntheticEvent.Notifier} notifier
276 _onFocus: function (e, notifier) {
277 var node = e.currentTarget,
278 vcData = node.getData(DATA_KEY);
282 node.setData(DATA_KEY, vcData);
285 vcData.prevVal = node.get(VALUE);
287 VC._startPolling(node, notifier, {e: e});
291 Starts polling when a node receives a keyDown event.
294 @param {EventFacade} e
295 @param {SyntheticEvent.Notifier} notifier
299 _onKeyDown: function (e, notifier) {
300 VC._startPolling(e.currentTarget, notifier, {e: e});
304 Starts polling when an IME-related keyUp event occurs on a node.
307 @param {EventFacade} e
308 @param {SyntheticEvent.Notifier} notifier
312 _onKeyUp: function (e, notifier) {
313 // These charCodes indicate that an IME has started. We'll restart
314 // polling and give the IME up to 10 seconds (by default) to finish.
315 if (e.charCode === 229 || e.charCode === 197) {
316 VC._startPolling(e.currentTarget, notifier, {
324 Starts polling when a node receives a mouseDown event.
327 @param {EventFacade} e
328 @param {SyntheticEvent.Notifier} notifier
332 _onMouseDown: function (e, notifier) {
333 VC._startPolling(e.currentTarget, notifier, {e: e});
337 Called when the `valuechange` event receives a new subscriber.
341 @param {Subscription} sub
342 @param {SyntheticEvent.Notifier} notifier
343 @param {Function|String} [filter] Filter function or selector string. Only
344 provided for delegate subscriptions.
348 _onSubscribe: function (node, sub, notifier, filter) {
349 var _valuechange, callbacks, nodes;
354 keydown : VC._onKeyDown,
356 mousedown: VC._onMouseDown
359 // Store a utility object on the notifier to hold stuff that needs to be
360 // passed around to trigger event handlers, polling handlers, etc.
361 _valuechange = notifier._valuechange = {};
364 // If a filter is provided, then this is a delegated subscription.
365 _valuechange.delegated = true;
367 // Add a function to the notifier that we can use to find all
368 // nodes that pass the delegate filter.
369 _valuechange.getNodes = function () {
370 return node.all('input,textarea').filter(filter);
373 // Store the initial values for each descendant of the container
374 // node that passes the delegate filter.
375 _valuechange.getNodes().each(function (child) {
376 if (!child.getData(DATA_KEY)) {
377 child.setData(DATA_KEY, {prevVal: child.get(VALUE)});
381 notifier._handles = Y.delegate(callbacks, node, filter, null,
384 // This is a normal (non-delegated) event subscription.
386 if (!node.test('input,textarea')) {
390 if (!node.getData(DATA_KEY)) {
391 node.setData(DATA_KEY, {prevVal: node.get(VALUE)});
394 notifier._handles = node.on(callbacks, null, null, notifier);
399 Called when the `valuechange` event loses a subscriber.
401 @method _onUnsubscribe
403 @param {Subscription} subscription
404 @param {SyntheticEvent.Notifier} notifier
408 _onUnsubscribe: function (node, subscription, notifier) {
409 var _valuechange = notifier._valuechange;
411 notifier._handles && notifier._handles.detach();
413 if (_valuechange.delegated) {
414 _valuechange.getNodes().each(function (child) {
415 VC._stopPolling(child, notifier);
418 VC._stopPolling(node, notifier);
424 Synthetic event that fires when the `value` property of an `<input>` or
425 `<textarea>` node changes as a result of a user-initiated keystroke, mouse
426 operation, or input method editor (IME) input event.
428 Unlike the `onchange` event, this event fires when the value actually changes
429 and not when the element loses focus. This event also reports IME and
430 multi-stroke input more reliably than `oninput` or the various key events across
433 For performance reasons, only focused nodes are monitored for changes, so
434 programmatic value changes on nodes that don't have focus won't be detected.
438 YUI().use('event-valuechange', function (Y) {
439 Y.one('#my-input').on('valueChange', function (e) {
444 @param {String} prevVal Previous value prior to the latest change.
445 @param {String} newVal New value after the latest change.
450 detach: VC._onUnsubscribe,
451 on : VC._onSubscribe,
453 delegate : VC._onSubscribe,
454 detachDelegate: VC._onUnsubscribe,
461 Y.Event.define('valuechange', config);
462 Y.Event.define('valueChange', config); // deprecated, but supported for backcompat
467 }, '3.5.0' ,{requires:['event-focus', 'event-synthetic']});