Merge branch 'MDL-32509' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / yui / 3.5.0 / build / event-valuechange / event-valuechange-debug.js
blob19509e151ba6af618b9dc4a24e97d68c3405e56e
1 /*
2 YUI 3.5.0 (build 5089)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('event-valuechange', function(Y) {
9 /**
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.
14 Usage:
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);
20         });
21     });
23 @module event-valuechange
24 **/
26 /**
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.
30 Usage:
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);
36         });
37     });
39 @class ValueChange
40 @static
43 var DATA_KEY = '_valuechange',
44     VALUE    = 'value',
46     config, // defined at the end of this file
48 // Just a simple namespace to make methods overridable.
49 VC = {
50     // -- Static Constants -----------------------------------------------------
52     /**
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
58     @type Number
59     @default 50
60     @static
61     **/
62     POLL_INTERVAL: 50,
64     /**
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.
68     @property TIMEOUT
69     @type Number
70     @default 10000
71     @static
72     **/
73     TIMEOUT: 10000,
75     // -- Protected Static Methods ---------------------------------------------
77     /**
78     Called at an interval to poll for changes to the value of the specified
79     node.
81     @method _poll
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.
88     @protected
89     @static
90     **/
91     _poll: function (node, options) {
92         var domNode = node._node, // performance cheat; getValue() is a big hit when polling
93             event   = options.e,
94             newVal  = domNode && domNode.value,
95             vcData  = node._data && node._data[DATA_KEY], // another perf cheat
96             facade, prevVal;
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);
101             return;
102         }
104         prevVal = vcData.prevVal;
106         if (newVal !== prevVal) {
107             vcData.prevVal = newVal;
109             facade = {
110                 _event       : event,
111                 currentTarget: (event && event.currentTarget) || node,
112                 newVal       : newVal,
113                 prevVal      : prevVal,
114                 target       : (event && event.target) || node
115             };
117             Y.Object.each(vcData.notifiers, function (notifier) {
118                 notifier.fire(facade);
119             });
121             VC._refreshTimeout(node);
122         }
123     },
125     /**
126     Restarts the inactivity timeout for the specified node.
128     @method _refreshTimeout
129     @param {Node} node Node to refresh.
130     @param {SyntheticEvent.Notifier} notifier
131     @protected
132     @static
133     **/
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.
137         if (!node._node) {
138             Y.log('_stopPolling: node disappeared', 'warn', 'event-valuechange');
139             return;
140         }
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);
151         }, VC.TIMEOUT);
153         Y.log('_refreshTimeout: #' + node.get('id'), 'info', 'event-valuechange');
154     },
156     /**
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.
171     @protected
172     @static
173     **/
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');
177             return;
178         }
180         var vcData = node.getData(DATA_KEY);
182         if (!vcData) {
183             vcData = {prevVal: node.get(VALUE)};
184             node.setData(DATA_KEY, vcData);
185         }
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) {
192             if (options.force) {
193                 VC._stopPolling(node, notifier); // restart polling, but avoid dupe polls
194             } else {
195                 vcData.notifiers[Y.stamp(notifier)] = notifier;
196                 return;
197             }
198         }
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);
213     },
215     /**
216     Stops polling for changes to the specified node's `value` attribute.
218     @method _stopPolling
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.
222     @protected
223     @static
224     **/
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.
228         if (!node._node) {
229             Y.log('_stopPolling: node disappeared', 'info', 'event-valuechange');
230             return;
231         }
233         var vcData = node.getData(DATA_KEY) || {};
235         clearInterval(vcData.interval);
236         delete vcData.interval;
238         VC._stopTimeout(node);
240         if (notifier) {
241             vcData.notifiers && delete vcData.notifiers[Y.stamp(notifier)];
242         } else {
243             vcData.notifiers = {};
244         }
246         Y.log('_stopPolling: #' + node.get('id'), 'info', 'event-valuechange');
247     },
249     /**
250     Clears the inactivity timeout for the specified node, if any.
252     @method _stopTimeout
253     @param {Node} node
254     @protected
255     @static
256     **/
257     _stopTimeout: function (node) {
258         var vcData = node.getData(DATA_KEY) || {};
260         clearTimeout(vcData.timeout);
261         delete vcData.timeout;
262     },
264     // -- Protected Static Event Handlers --------------------------------------
266     /**
267     Stops polling when a node's blur event fires.
269     @method _onBlur
270     @param {EventFacade} e
271     @param {SyntheticEvent.Notifier} notifier
272     @protected
273     @static
274     **/
275     _onBlur: function (e, notifier) {
276         VC._stopPolling(e.currentTarget, notifier);
277     },
279     /**
280     Resets a node's history and starts polling when a focus event occurs.
282     @method _onFocus
283     @param {EventFacade} e
284     @param {SyntheticEvent.Notifier} notifier
285     @protected
286     @static
287     **/
288     _onFocus: function (e, notifier) {
289         var node   = e.currentTarget,
290             vcData = node.getData(DATA_KEY);
292         if (!vcData) {
293             vcData = {};
294             node.setData(DATA_KEY, vcData);
295         }
297         vcData.prevVal = node.get(VALUE);
299         VC._startPolling(node, notifier, {e: e});
300     },
302     /**
303     Starts polling when a node receives a keyDown event.
305     @method _onKeyDown
306     @param {EventFacade} e
307     @param {SyntheticEvent.Notifier} notifier
308     @protected
309     @static
310     **/
311     _onKeyDown: function (e, notifier) {
312         VC._startPolling(e.currentTarget, notifier, {e: e});
313     },
315     /**
316     Starts polling when an IME-related keyUp event occurs on a node.
318     @method _onKeyUp
319     @param {EventFacade} e
320     @param {SyntheticEvent.Notifier} notifier
321     @protected
322     @static
323     **/
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, {
329                 e    : e,
330                 force: true
331             });
332         }
333     },
335     /**
336     Starts polling when a node receives a mouseDown event.
338     @method _onMouseDown
339     @param {EventFacade} e
340     @param {SyntheticEvent.Notifier} notifier
341     @protected
342     @static
343     **/
344     _onMouseDown: function (e, notifier) {
345         VC._startPolling(e.currentTarget, notifier, {e: e});
346     },
348     /**
349     Called when the `valuechange` event receives a new subscriber.
351     @method _onSubscribe
352     @param {Node} node
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.
357     @protected
358     @static
359     **/
360     _onSubscribe: function (node, sub, notifier, filter) {
361         var _valuechange, callbacks, nodes;
363         callbacks = {
364             blur     : VC._onBlur,
365             focus    : VC._onFocus,
366             keydown  : VC._onKeyDown,
367             keyup    : VC._onKeyUp,
368             mousedown: VC._onMouseDown
369         };
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 = {};
375         if (filter) {
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);
383             };
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)});
390                 }
391             });
393             notifier._handles = Y.delegate(callbacks, node, filter, null,
394                 notifier);
395         } else {
396             // This is a normal (non-delegated) event subscription.
398             if (!node.test('input,textarea')) {
399                 return;
400             }
402             if (!node.getData(DATA_KEY)) {
403                 node.setData(DATA_KEY, {prevVal: node.get(VALUE)});
404             }
406             notifier._handles = node.on(callbacks, null, null, notifier);
407         }
408     },
410     /**
411     Called when the `valuechange` event loses a subscriber.
413     @method _onUnsubscribe
414     @param {Node} node
415     @param {Subscription} subscription
416     @param {SyntheticEvent.Notifier} notifier
417     @protected
418     @static
419     **/
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);
428             });
429         } else {
430             VC._stopPolling(node, notifier);
431         }
432     }
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
443 browsers.
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.
448 @example
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);
454         });
455     });
457 @event valuechange
458 @param {String} prevVal Previous value prior to the latest change.
459 @param {String} newVal New value after the latest change.
460 @for YUI
463 config = {
464     detach: VC._onUnsubscribe,
465     on    : VC._onSubscribe,
467     delegate      : VC._onSubscribe,
468     detachDelegate: VC._onUnsubscribe,
470     publishConfig: {
471         emitFacade: true
472     }
475 Y.Event.define('valuechange', config);
476 Y.Event.define('valueChange', config); // deprecated, but supported for backcompat
478 Y.ValueChange = VC;
481 }, '3.5.0' ,{requires:['event-focus', 'event-synthetic']});