Merge branch 'MDL-32509' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / yui / 3.5.0 / build / event-valuechange / event-valuechange.js
blob78e5600fd7ed21f21efca84485cebd5edad49f93
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         });
19     });
21 @module event-valuechange
22 **/
24 /**
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.
28 Usage:
30     YUI().use('event-valuechange', function (Y) {
31         Y.one('#my-input').on('valueChange', function (e) {
32         });
33     });
35 @class ValueChange
36 @static
39 var DATA_KEY = '_valuechange',
40     VALUE    = 'value',
42     config, // defined at the end of this file
44 // Just a simple namespace to make methods overridable.
45 VC = {
46     // -- Static Constants -----------------------------------------------------
48     /**
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
54     @type Number
55     @default 50
56     @static
57     **/
58     POLL_INTERVAL: 50,
60     /**
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.
64     @property TIMEOUT
65     @type Number
66     @default 10000
67     @static
68     **/
69     TIMEOUT: 10000,
71     // -- Protected Static Methods ---------------------------------------------
73     /**
74     Called at an interval to poll for changes to the value of the specified
75     node.
77     @method _poll
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.
84     @protected
85     @static
86     **/
87     _poll: function (node, options) {
88         var domNode = node._node, // performance cheat; getValue() is a big hit when polling
89             event   = options.e,
90             newVal  = domNode && domNode.value,
91             vcData  = node._data && node._data[DATA_KEY], // another perf cheat
92             facade, prevVal;
94         if (!domNode || !vcData) {
95             VC._stopPolling(node);
96             return;
97         }
99         prevVal = vcData.prevVal;
101         if (newVal !== prevVal) {
102             vcData.prevVal = newVal;
104             facade = {
105                 _event       : event,
106                 currentTarget: (event && event.currentTarget) || node,
107                 newVal       : newVal,
108                 prevVal      : prevVal,
109                 target       : (event && event.target) || node
110             };
112             Y.Object.each(vcData.notifiers, function (notifier) {
113                 notifier.fire(facade);
114             });
116             VC._refreshTimeout(node);
117         }
118     },
120     /**
121     Restarts the inactivity timeout for the specified node.
123     @method _refreshTimeout
124     @param {Node} node Node to refresh.
125     @param {SyntheticEvent.Notifier} notifier
126     @protected
127     @static
128     **/
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.
132         if (!node._node) {
133             return;
134         }
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);
144         }, VC.TIMEOUT);
146     },
148     /**
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.
163     @protected
164     @static
165     **/
166     _startPolling: function (node, notifier, options) {
167         if (!node.test('input,textarea')) {
168             return;
169         }
171         var vcData = node.getData(DATA_KEY);
173         if (!vcData) {
174             vcData = {prevVal: node.get(VALUE)};
175             node.setData(DATA_KEY, vcData);
176         }
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) {
183             if (options.force) {
184                 VC._stopPolling(node, notifier); // restart polling, but avoid dupe polls
185             } else {
186                 vcData.notifiers[Y.stamp(notifier)] = notifier;
187                 return;
188             }
189         }
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);
203     },
205     /**
206     Stops polling for changes to the specified node's `value` attribute.
208     @method _stopPolling
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.
212     @protected
213     @static
214     **/
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.
218         if (!node._node) {
219             return;
220         }
222         var vcData = node.getData(DATA_KEY) || {};
224         clearInterval(vcData.interval);
225         delete vcData.interval;
227         VC._stopTimeout(node);
229         if (notifier) {
230             vcData.notifiers && delete vcData.notifiers[Y.stamp(notifier)];
231         } else {
232             vcData.notifiers = {};
233         }
235     },
237     /**
238     Clears the inactivity timeout for the specified node, if any.
240     @method _stopTimeout
241     @param {Node} node
242     @protected
243     @static
244     **/
245     _stopTimeout: function (node) {
246         var vcData = node.getData(DATA_KEY) || {};
248         clearTimeout(vcData.timeout);
249         delete vcData.timeout;
250     },
252     // -- Protected Static Event Handlers --------------------------------------
254     /**
255     Stops polling when a node's blur event fires.
257     @method _onBlur
258     @param {EventFacade} e
259     @param {SyntheticEvent.Notifier} notifier
260     @protected
261     @static
262     **/
263     _onBlur: function (e, notifier) {
264         VC._stopPolling(e.currentTarget, notifier);
265     },
267     /**
268     Resets a node's history and starts polling when a focus event occurs.
270     @method _onFocus
271     @param {EventFacade} e
272     @param {SyntheticEvent.Notifier} notifier
273     @protected
274     @static
275     **/
276     _onFocus: function (e, notifier) {
277         var node   = e.currentTarget,
278             vcData = node.getData(DATA_KEY);
280         if (!vcData) {
281             vcData = {};
282             node.setData(DATA_KEY, vcData);
283         }
285         vcData.prevVal = node.get(VALUE);
287         VC._startPolling(node, notifier, {e: e});
288     },
290     /**
291     Starts polling when a node receives a keyDown event.
293     @method _onKeyDown
294     @param {EventFacade} e
295     @param {SyntheticEvent.Notifier} notifier
296     @protected
297     @static
298     **/
299     _onKeyDown: function (e, notifier) {
300         VC._startPolling(e.currentTarget, notifier, {e: e});
301     },
303     /**
304     Starts polling when an IME-related keyUp event occurs on a node.
306     @method _onKeyUp
307     @param {EventFacade} e
308     @param {SyntheticEvent.Notifier} notifier
309     @protected
310     @static
311     **/
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, {
317                 e    : e,
318                 force: true
319             });
320         }
321     },
323     /**
324     Starts polling when a node receives a mouseDown event.
326     @method _onMouseDown
327     @param {EventFacade} e
328     @param {SyntheticEvent.Notifier} notifier
329     @protected
330     @static
331     **/
332     _onMouseDown: function (e, notifier) {
333         VC._startPolling(e.currentTarget, notifier, {e: e});
334     },
336     /**
337     Called when the `valuechange` event receives a new subscriber.
339     @method _onSubscribe
340     @param {Node} node
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.
345     @protected
346     @static
347     **/
348     _onSubscribe: function (node, sub, notifier, filter) {
349         var _valuechange, callbacks, nodes;
351         callbacks = {
352             blur     : VC._onBlur,
353             focus    : VC._onFocus,
354             keydown  : VC._onKeyDown,
355             keyup    : VC._onKeyUp,
356             mousedown: VC._onMouseDown
357         };
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 = {};
363         if (filter) {
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);
371             };
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)});
378                 }
379             });
381             notifier._handles = Y.delegate(callbacks, node, filter, null,
382                 notifier);
383         } else {
384             // This is a normal (non-delegated) event subscription.
386             if (!node.test('input,textarea')) {
387                 return;
388             }
390             if (!node.getData(DATA_KEY)) {
391                 node.setData(DATA_KEY, {prevVal: node.get(VALUE)});
392             }
394             notifier._handles = node.on(callbacks, null, null, notifier);
395         }
396     },
398     /**
399     Called when the `valuechange` event loses a subscriber.
401     @method _onUnsubscribe
402     @param {Node} node
403     @param {Subscription} subscription
404     @param {SyntheticEvent.Notifier} notifier
405     @protected
406     @static
407     **/
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);
416             });
417         } else {
418             VC._stopPolling(node, notifier);
419         }
420     }
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
431 browsers.
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.
436 @example
438     YUI().use('event-valuechange', function (Y) {
439         Y.one('#my-input').on('valueChange', function (e) {
440         });
441     });
443 @event valuechange
444 @param {String} prevVal Previous value prior to the latest change.
445 @param {String} newVal New value after the latest change.
446 @for YUI
449 config = {
450     detach: VC._onUnsubscribe,
451     on    : VC._onSubscribe,
453     delegate      : VC._onSubscribe,
454     detachDelegate: VC._onUnsubscribe,
456     publishConfig: {
457         emitFacade: true
458     }
461 Y.Event.define('valuechange', config);
462 Y.Event.define('valueChange', config); // deprecated, but supported for backcompat
464 Y.ValueChange = VC;
467 }, '3.5.0' ,{requires:['event-focus', 'event-synthetic']});