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