2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('event-valuechange', function(Y) {
11 * Adds a synthetic <code>valueChange</code> event that fires when the
12 * <code>value</code> property of an input field or textarea changes as a result
13 * of a keystroke, mouse operation, or input method editor (IME) input event.
15 * @module event-valuechange
19 * Provides the implementation for the synthetic <code>valueChange</code> event.
29 // Just a simple namespace to make methods overridable.
31 // -- Static Constants -----------------------------------------------------
34 * Interval (in milliseconds) at which to poll for changes to the value of
35 * an element with one or more <code>valueChange</code> subscribers when the
36 * user is likely to be interacting with it.
38 * @property POLL_INTERVAL
46 * Timeout (in milliseconds) after which to stop polling when there hasn't
47 * been any new activity (keypresses, mouse clicks, etc.) on an element.
56 // -- Protected Static Properties ------------------------------------------
62 // -- Protected Static Methods ---------------------------------------------
65 * Called at an interval to poll for changes to the value of the specified
70 * @param {String} stamp
71 * @param {EventFacade} e
75 _poll: function (node, stamp, e) {
76 var domNode = node._node, // performance cheat; getValue() is a big hit when polling
77 newVal = domNode && domNode.value,
78 prevVal = VC._history[stamp],
82 VC._stopPolling(node, stamp);
86 if (newVal !== prevVal) {
87 VC._history[stamp] = newVal;
95 YArray.each(VC._notifiers[stamp], function (notifier) {
96 notifier.fire(facade);
99 VC._refreshTimeout(node, stamp);
104 * Restarts the inactivity timeout for the specified node.
106 * @method _refreshTimeout
108 * @param {String} stamp
112 _refreshTimeout: function (node, stamp) {
113 VC._stopTimeout(node, stamp); // avoid dupes
115 // If we don't see any changes within the timeout period (10 seconds by
116 // default), stop polling.
117 VC._timeouts[stamp] = setTimeout(function () {
118 VC._stopPolling(node, stamp);
124 * Begins polling for changes to the <code>value</code> property of the
125 * specified node. If polling is already underway for the specified node,
126 * it will not be restarted unless the <i>force</i> parameter is
129 * @method _startPolling
130 * @param {Node} node Node to watch.
131 * @param {String} stamp (optional) Object stamp for the node. Will be
132 * generated if not provided (provide it to improve performance).
133 * @param {EventFacade} e (optional) Event facade of the event that
134 * initiated the polling (if any).
135 * @param {Boolean} force (optional) If <code>true</code>, polling will be
136 * restarted even if we're already polling this node.
140 _startPolling: function (node, stamp, e, force) {
142 stamp = Y.stamp(node);
145 // Don't bother continuing if we're already polling.
146 if (!force && VC._intervals[stamp]) {
150 VC._stopPolling(node, stamp); // avoid dupes
152 // Poll for changes to the node's value. We can't rely on keyboard
153 // events for this, since the value may change due to a mouse-initiated
154 // paste event, an IME input event, or for some other reason that
155 // doesn't trigger a key event.
156 VC._intervals[stamp] = setInterval(function () {
157 VC._poll(node, stamp, e);
158 }, VC.POLL_INTERVAL);
160 VC._refreshTimeout(node, stamp, e);
165 * Stops polling for changes to the specified node's <code>value</code>
168 * @method _stopPolling
170 * @param {String} stamp (optional)
174 _stopPolling: function (node, stamp) {
176 stamp = Y.stamp(node);
179 VC._intervals[stamp] = clearInterval(VC._intervals[stamp]);
180 VC._stopTimeout(node, stamp);
185 * Clears the inactivity timeout for the specified node, if any.
187 * @method _stopTimeout
189 * @param {String} stamp (optional)
193 _stopTimeout: function (node, stamp) {
195 stamp = Y.stamp(node);
198 VC._timeouts[stamp] = clearTimeout(VC._timeouts[stamp]);
201 // -- Protected Static Event Handlers --------------------------------------
204 * Stops polling when a node's blur event fires.
207 * @param {EventFacade} e
211 _onBlur: function (e) {
212 VC._stopPolling(e.currentTarget);
216 * Resets a node's history and starts polling when a focus event occurs.
219 * @param {EventFacade} e
223 _onFocus: function (e) {
224 var node = e.currentTarget;
226 VC._history[Y.stamp(node)] = node.get(VALUE);
227 VC._startPolling(node, null, e);
231 * Starts polling when a node receives a keyDown event.
234 * @param {EventFacade} e
238 _onKeyDown: function (e) {
239 VC._startPolling(e.currentTarget, null, e);
243 * Starts polling when an IME-related keyUp event occurs on a node.
246 * @param {EventFacade} e
250 _onKeyUp: function (e) {
251 // These charCodes indicate that an IME has started. We'll restart
252 // polling and give the IME up to 10 seconds (by default) to finish.
253 if (e.charCode === 229 || e.charCode === 197) {
254 VC._startPolling(e.currentTarget, null, e, true);
259 * Starts polling when a node receives a mouseDown event.
261 * @method _onMouseDown
262 * @param {EventFacade} e
266 _onMouseDown: function (e) {
267 VC._startPolling(e.currentTarget, null, e);
271 * Called when event-valuechange receives a new subscriber.
273 * @method _onSubscribe
275 * @param {Subscription} subscription
276 * @param {SyntheticEvent.Notifier} notifier
280 _onSubscribe: function (node, subscription, notifier) {
281 var stamp = Y.stamp(node),
282 notifiers = VC._notifiers[stamp];
284 VC._history[stamp] = node.get(VALUE);
286 notifier._handles = node.on({
289 keydown : VC._onKeyDown,
291 mousedown: VC._onMouseDown
295 notifiers = VC._notifiers[stamp] = [];
298 notifiers.push(notifier);
302 * Called when event-valuechange loses a subscriber.
304 * @method _onUnsubscribe
306 * @param {Subscription} subscription
307 * @param {SyntheticEvent.Notifier} notifier
311 _onUnsubscribe: function (node, subscription, notifier) {
312 var stamp = Y.stamp(node),
313 notifiers = VC._notifiers[stamp],
314 index = YArray.indexOf(notifiers, notifier);
316 notifier._handles.detach();
319 notifiers.splice(index, 1);
321 if (!notifiers.length) {
322 VC._stopPolling(node, stamp);
324 delete VC._notifiers[stamp];
325 delete VC._history[stamp];
333 * Synthetic event that fires when the <code>value</code> property of an input
334 * field or textarea changes as a result of a keystroke, mouse operation, or
335 * input method editor (IME) input event.
339 * Unlike the <code>onchange</code> event, this event fires when the value
340 * actually changes and not when the element loses focus. This event also
341 * reports IME and multi-stroke input more reliably than <code>oninput</code> or
342 * the various key events across browsers.
346 * This event is provided by the <code>event-valuechange</code> module.
350 * <strong>Usage example:</strong>
354 * YUI().use('event-valuechange', function (Y) {
355 * Y.one('input').on('valueChange', function (e) {
356 * // Handle valueChange events on the first input element on the page.
362 * @param {EventFacade} e Event facade with the following additional
366 * <dt>prevVal (String)</dt>
368 * Previous value before the latest change.
371 * <dt>newVal (String)</dt>
373 * New value after the latest change.
380 Y.Event.define('valueChange', {
381 detach: VC._onUnsubscribe,
382 on : VC._onSubscribe,
392 }, '3.3.0' ,{requires:['event-focus', 'event-synthetic']});