|
525
|
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"]}); |