Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('event-valuechange', function (Y, NAME) {
2
 
3
/**
4
Adds a synthetic `valuechange` event that fires when the `value` property of an
5
`<input>`, `<textarea>`, `<select>`, or `[contenteditable="true"]` node changes
6
as a result of a keystroke, mouse operation, or input method editor (IME)
7
input event.
8
 
9
Usage:
10
 
11
    YUI().use('event-valuechange', function (Y) {
12
        Y.one('#my-input').on('valuechange', function (e) {
13
            Y.log('previous value: ' + e.prevVal);
14
            Y.log('new value: ' + e.newVal);
15
        });
16
    });
17
 
18
@module event-valuechange
19
**/
20
 
21
/**
22
Provides the implementation for the synthetic `valuechange` event. This class
23
isn't meant to be used directly, but is public to make monkeypatching possible.
24
 
25
Usage:
26
 
27
    YUI().use('event-valuechange', function (Y) {
28
        Y.one('#my-input').on('valuechange', function (e) {
29
            Y.log('previous value: ' + e.prevVal);
30
            Y.log('new value: ' + e.newVal);
31
        });
32
    });
33
 
34
@class ValueChange
35
@static
36
*/
37
 
38
var DATA_KEY = '_valuechange',
39
    VALUE    = 'value',
40
    NODE_NAME = 'nodeName',
41
 
42
    config, // defined at the end of this file
43
 
44
// Just a simple namespace to make methods overridable.
45
VC = {
46
    // -- Static Constants -----------------------------------------------------
47
 
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.
52
 
53
    @property POLL_INTERVAL
54
    @type Number
55
    @default 50
56
    @static
57
    **/
58
    POLL_INTERVAL: 50,
59
 
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.
63
 
64
    @property TIMEOUT
65
    @type Number
66
    @default 10000
67
    @static
68
    **/
69
    TIMEOUT: 10000,
70
 
71
    // -- Protected Static Methods ---------------------------------------------
72
 
73
    /**
74
    Called at an interval to poll for changes to the value of the specified
75
    node.
76
 
77
    @method _poll
78
    @param {Node} node Node to poll.
79
 
80
    @param {Object} options Options object.
81
        @param {EventFacade} [options.e] Event facade of the event that
82
            initiated the polling.
83
 
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
            vcData   = node._data && node._data[DATA_KEY], // another perf cheat
91
            stopped  = 0,
92
            facade, prevVal, newVal, nodeName, selectedOption, stopElement;
93
 
94
        if (!(domNode && vcData)) {
95
            Y.log('_poll: node #' + node.get('id') + ' disappeared; stopping polling and removing all notifiers.', 'warn', 'event-valuechange');
96
            VC._stopPolling(node);
97
            return;
98
        }
99
 
100
        prevVal = vcData.prevVal;
101
        nodeName  = vcData.nodeName;
102
 
103
        if (vcData.isEditable) {
104
            // Use innerHTML for performance
105
            newVal = domNode.innerHTML;
106
        } else if (nodeName === 'input' || nodeName === 'textarea') {
107
            // Use value property for performance
108
            newVal = domNode.value;
109
        } else if (nodeName === 'select') {
110
            // Back-compatibility with IE6 <select> element values.
111
            // Huge performance cheat to get past node.get('value').
112
            selectedOption = domNode.options[domNode.selectedIndex];
113
            newVal = selectedOption.value || selectedOption.text;
114
        }
115
 
116
        if (newVal !== prevVal) {
117
            vcData.prevVal = newVal;
118
 
119
            facade = {
120
                _event       : event,
121
                currentTarget: (event && event.currentTarget) || node,
122
                newVal       : newVal,
123
                prevVal      : prevVal,
124
                target       : (event && event.target) || node
125
            };
126
 
127
            Y.Object.some(vcData.notifiers, function (notifier) {
128
                var evt = notifier.handle.evt,
129
                    newStopped;
130
 
131
                // support e.stopPropagation()
132
                if (stopped !== 1) {
133
                    notifier.fire(facade);
134
                } else if (evt.el === stopElement) {
135
                    notifier.fire(facade);
136
                }
137
 
138
                newStopped = evt && evt._facade ? evt._facade.stopped : 0;
139
 
140
                // need to consider the condition in which there are two
141
                // listeners on the same element:
142
                // listener 1 calls e.stopPropagation()
143
                // listener 2 calls e.stopImmediatePropagation()
144
                if (newStopped > stopped) {
145
                    stopped = newStopped;
146
 
147
                    if (stopped === 1) {
148
                        stopElement = evt.el;
149
                    }
150
                }
151
 
152
                // support e.stopImmediatePropagation()
153
                if (stopped === 2) {
154
                    return true;
155
                }
156
            });
157
 
158
            VC._refreshTimeout(node);
159
        }
160
    },
161
 
162
    /**
163
    Restarts the inactivity timeout for the specified node.
164
 
165
    @method _refreshTimeout
166
    @param {Node} node Node to refresh.
167
    @param {SyntheticEvent.Notifier} notifier
168
    @protected
169
    @static
170
    **/
171
    _refreshTimeout: function (node, notifier) {
172
        // The node may have been destroyed, so check that it still exists
173
        // before trying to get its data. Otherwise an error will occur.
174
        if (!node._node) {
175
            Y.log('_stopPolling: node disappeared', 'warn', 'event-valuechange');
176
            return;
177
        }
178
 
179
        var vcData = node.getData(DATA_KEY);
180
 
181
        VC._stopTimeout(node); // avoid dupes
182
 
183
        // If we don't see any changes within the timeout period (10 seconds by
184
        // default), stop polling.
185
        vcData.timeout = setTimeout(function () {
186
            Y.log('timeout: #' + node.get('id'), 'info', 'event-valuechange');
187
            VC._stopPolling(node, notifier);
188
        }, VC.TIMEOUT);
189
 
190
        Y.log('_refreshTimeout: #' + node.get('id'), 'info', 'event-valuechange');
191
    },
192
 
193
    /**
194
    Begins polling for changes to the `value` property of the specified node. If
195
    polling is already underway for the specified node, it will not be restarted
196
    unless the `force` option is `true`
197
 
198
    @method _startPolling
199
    @param {Node} node Node to watch.
200
    @param {SyntheticEvent.Notifier} notifier
201
 
202
    @param {Object} options Options object.
203
        @param {EventFacade} [options.e] Event facade of the event that
204
            initiated the polling.
205
        @param {Boolean} [options.force=false] If `true`, polling will be
206
            restarted even if we're already polling this node.
207
 
208
    @protected
209
    @static
210
    **/
211
    _startPolling: function (node, notifier, options) {
212
        var vcData, isEditable;
213
 
214
        if (!node.test('input,textarea,select') && !(isEditable = VC._isEditable(node))) {
215
            Y.log('_startPolling: aborting poll on #' + node.get('id') + ' -- not a detectable node', 'warn', 'event-valuechange');
216
            return;
217
        }
218
 
219
        vcData = node.getData(DATA_KEY);
220
 
221
        if (!vcData) {
222
            vcData = {
223
                nodeName   : node.get(NODE_NAME).toLowerCase(),
224
                isEditable : isEditable,
225
                prevVal    : isEditable ? node.getDOMNode().innerHTML : node.get(VALUE)
226
            };
227
 
228
            node.setData(DATA_KEY, vcData);
229
        }
230
 
231
        vcData.notifiers || (vcData.notifiers = {});
232
 
233
        // Don't bother continuing if we're already polling this node, unless
234
        // `options.force` is true.
235
        if (vcData.interval) {
236
            if (options.force) {
237
                VC._stopPolling(node, notifier); // restart polling, but avoid dupe polls
238
            } else {
239
                vcData.notifiers[Y.stamp(notifier)] = notifier;
240
                return;
241
            }
242
        }
243
 
244
        // Poll for changes to the node's value. We can't rely on keyboard
245
        // events for this, since the value may change due to a mouse-initiated
246
        // paste event, an IME input event, or for some other reason that
247
        // doesn't trigger a key event.
248
        vcData.notifiers[Y.stamp(notifier)] = notifier;
249
 
250
        vcData.interval = setInterval(function () {
251
            VC._poll(node, options);
252
        }, VC.POLL_INTERVAL);
253
 
254
        Y.log('_startPolling: #' + node.get('id'), 'info', 'event-valuechange');
255
 
256
        VC._refreshTimeout(node, notifier);
257
    },
258
 
259
    /**
260
    Stops polling for changes to the specified node's `value` attribute.
261
 
262
    @method _stopPolling
263
    @param {Node} node Node to stop polling on.
264
    @param {SyntheticEvent.Notifier} [notifier] Notifier to remove from the
265
        node. If not specified, all notifiers will be removed.
266
    @protected
267
    @static
268
    **/
269
    _stopPolling: function (node, notifier) {
270
        // The node may have been destroyed, so check that it still exists
271
        // before trying to get its data. Otherwise an error will occur.
272
        if (!node._node) {
273
            Y.log('_stopPolling: node disappeared', 'info', 'event-valuechange');
274
            return;
275
        }
276
 
277
        var vcData = node.getData(DATA_KEY) || {};
278
 
279
        clearInterval(vcData.interval);
280
        delete vcData.interval;
281
 
282
        VC._stopTimeout(node);
283
 
284
        if (notifier) {
285
            vcData.notifiers && delete vcData.notifiers[Y.stamp(notifier)];
286
        } else {
287
            vcData.notifiers = {};
288
        }
289
 
290
        Y.log('_stopPolling: #' + node.get('id'), 'info', 'event-valuechange');
291
    },
292
 
293
    /**
294
    Clears the inactivity timeout for the specified node, if any.
295
 
296
    @method _stopTimeout
297
    @param {Node} node
298
    @protected
299
    @static
300
    **/
301
    _stopTimeout: function (node) {
302
        var vcData = node.getData(DATA_KEY) || {};
303
 
304
        clearTimeout(vcData.timeout);
305
        delete vcData.timeout;
306
    },
307
 
308
    /**
309
    Check to see if a node has editable content or not.
310
 
311
    TODO: Add additional checks to get it to work for child nodes
312
    that inherit "contenteditable" from parent nodes. This may be
313
    too computationally intensive to be placed inside of the `_poll`
314
    loop, however.
315
 
316
    @method _isEditable
317
    @param {Node} node
318
    @protected
319
    @static
320
    **/
321
    _isEditable: function (node) {
322
        // Performance cheat because this is used inside `_poll`
323
        var domNode = node._node;
324
        return domNode.contentEditable === 'true' ||
325
               domNode.contentEditable === '';
326
    },
327
 
328
 
329
 
330
    // -- Protected Static Event Handlers --------------------------------------
331
 
332
    /**
333
    Stops polling when a node's blur event fires.
334
 
335
    @method _onBlur
336
    @param {EventFacade} e
337
    @param {SyntheticEvent.Notifier} notifier
338
    @protected
339
    @static
340
    **/
341
    _onBlur: function (e, notifier) {
342
        VC._stopPolling(e.currentTarget, notifier);
343
    },
344
 
345
    /**
346
    Resets a node's history and starts polling when a focus event occurs.
347
 
348
    @method _onFocus
349
    @param {EventFacade} e
350
    @param {SyntheticEvent.Notifier} notifier
351
    @protected
352
    @static
353
    **/
354
    _onFocus: function (e, notifier) {
355
        var node       = e.currentTarget,
356
            vcData     = node.getData(DATA_KEY);
357
 
358
        if (!vcData) {
359
            vcData = {
360
                isEditable : VC._isEditable(node),
361
                nodeName   : node.get(NODE_NAME).toLowerCase()
362
            };
363
            node.setData(DATA_KEY, vcData);
364
        }
365
 
366
        vcData.prevVal = vcData.isEditable ? node.getDOMNode().innerHTML : node.get(VALUE);
367
 
368
        VC._startPolling(node, notifier, {e: e});
369
    },
370
 
371
    /**
372
    Starts polling when a node receives a keyDown event.
373
 
374
    @method _onKeyDown
375
    @param {EventFacade} e
376
    @param {SyntheticEvent.Notifier} notifier
377
    @protected
378
    @static
379
    **/
380
    _onKeyDown: function (e, notifier) {
381
        VC._startPolling(e.currentTarget, notifier, {e: e});
382
    },
383
 
384
    /**
385
    Starts polling when an IME-related keyUp event occurs on a node.
386
 
387
    @method _onKeyUp
388
    @param {EventFacade} e
389
    @param {SyntheticEvent.Notifier} notifier
390
    @protected
391
    @static
392
    **/
393
    _onKeyUp: function (e, notifier) {
394
        // These charCodes indicate that an IME has started. We'll restart
395
        // polling and give the IME up to 10 seconds (by default) to finish.
396
        if (e.charCode === 229 || e.charCode === 197) {
397
            VC._startPolling(e.currentTarget, notifier, {
398
                e    : e,
399
                force: true
400
            });
401
        }
402
    },
403
 
404
    /**
405
    Starts polling when a node receives a mouseDown event.
406
 
407
    @method _onMouseDown
408
    @param {EventFacade} e
409
    @param {SyntheticEvent.Notifier} notifier
410
    @protected
411
    @static
412
    **/
413
    _onMouseDown: function (e, notifier) {
414
        VC._startPolling(e.currentTarget, notifier, {e: e});
415
    },
416
 
417
    /**
418
    Called when the `valuechange` event receives a new subscriber.
419
 
420
    Child nodes that aren't initially available when this subscription is
421
    called will still fire the `valuechange` event after their data is
422
    collected when the delegated `focus` event is captured. This includes
423
    elements that haven't been inserted into the DOM yet, as well as
424
    elements that aren't initially `contenteditable`.
425
 
426
    @method _onSubscribe
427
    @param {Node} node
428
    @param {Subscription} sub
429
    @param {SyntheticEvent.Notifier} notifier
430
    @param {Function|String} [filter] Filter function or selector string. Only
431
        provided for delegate subscriptions.
432
    @protected
433
    @static
434
    **/
435
    _onSubscribe: function (node, sub, notifier, filter) {
436
        var _valuechange, callbacks, isEditable, inputNodes, editableNodes;
437
 
438
        callbacks = {
439
            blur     : VC._onBlur,
440
            focus    : VC._onFocus,
441
            keydown  : VC._onKeyDown,
442
            keyup    : VC._onKeyUp,
443
            mousedown: VC._onMouseDown
444
        };
445
 
446
        // Store a utility object on the notifier to hold stuff that needs to be
447
        // passed around to trigger event handlers, polling handlers, etc.
448
        _valuechange = notifier._valuechange = {};
449
 
450
        if (filter) {
451
            // If a filter is provided, then this is a delegated subscription.
452
            _valuechange.delegated = true;
453
 
454
            // Add a function to the notifier that we can use to find all
455
            // nodes that pass the delegate filter.
456
            _valuechange.getNodes = function () {
457
                inputNodes    = node.all('input,textarea,select').filter(filter);
458
                editableNodes = node.all('[contenteditable="true"],[contenteditable=""]').filter(filter);
459
 
460
                return inputNodes.concat(editableNodes);
461
            };
462
 
463
            // Store the initial values for each descendant of the container
464
            // node that passes the delegate filter.
465
            _valuechange.getNodes().each(function (child) {
466
                if (!child.getData(DATA_KEY)) {
467
                    child.setData(DATA_KEY, {
468
                        nodeName   : child.get(NODE_NAME).toLowerCase(),
469
                        isEditable : VC._isEditable(child),
470
                        prevVal    : isEditable ? child.getDOMNode().innerHTML : child.get(VALUE)
471
                    });
472
                }
473
            });
474
 
475
            notifier._handles = Y.delegate(callbacks, node, filter, null,
476
                notifier);
477
        } else {
478
            isEditable = VC._isEditable(node);
479
            // This is a normal (non-delegated) event subscription.
480
            if (!node.test('input,textarea,select') && !isEditable) {
481
                return;
482
            }
483
 
484
            if (!node.getData(DATA_KEY)) {
485
                node.setData(DATA_KEY, {
486
                    nodeName   : node.get(NODE_NAME).toLowerCase(),
487
                    isEditable : isEditable,
488
                    prevVal    : isEditable ? node.getDOMNode().innerHTML : node.get(VALUE)
489
                });
490
            }
491
 
492
            notifier._handles = node.on(callbacks, null, null, notifier);
493
        }
494
    },
495
 
496
    /**
497
    Called when the `valuechange` event loses a subscriber.
498
 
499
    @method _onUnsubscribe
500
    @param {Node} node
501
    @param {Subscription} subscription
502
    @param {SyntheticEvent.Notifier} notifier
503
    @protected
504
    @static
505
    **/
506
    _onUnsubscribe: function (node, subscription, notifier) {
507
        var _valuechange = notifier._valuechange;
508
 
509
        notifier._handles && notifier._handles.detach();
510
 
511
        if (_valuechange.delegated) {
512
            _valuechange.getNodes().each(function (child) {
513
                VC._stopPolling(child, notifier);
514
            });
515
        } else {
516
            VC._stopPolling(node, notifier);
517
        }
518
    }
519
};
520
 
521
/**
522
Synthetic event that fires when the `value` property of an `<input>`,
523
`<textarea>`, `<select>`, or `[contenteditable="true"]` node changes as a
524
result of a user-initiated keystroke, mouse operation, or input method
525
editor (IME) input event.
526
 
527
Unlike the `onchange` event, this event fires when the value actually changes
528
and not when the element loses focus. This event also reports IME and
529
multi-stroke input more reliably than `oninput` or the various key events across
530
browsers.
531
 
532
For performance reasons, only focused nodes are monitored for changes, so
533
programmatic value changes on nodes that don't have focus won't be detected.
534
 
535
@example
536
 
537
    YUI().use('event-valuechange', function (Y) {
538
        Y.one('#my-input').on('valuechange', function (e) {
539
            Y.log('previous value: ' + e.prevVal);
540
            Y.log('new value: ' + e.newVal);
541
        });
542
    });
543
 
544
@event valuechange
545
@param {String} prevVal Previous value prior to the latest change.
546
@param {String} newVal New value after the latest change.
547
@for YUI
548
**/
549
 
550
config = {
551
    detach: VC._onUnsubscribe,
552
    on    : VC._onSubscribe,
553
 
554
    delegate      : VC._onSubscribe,
555
    detachDelegate: VC._onUnsubscribe,
556
 
557
    publishConfig: {
558
        emitFacade: true
559
    }
560
};
561
 
562
Y.Event.define('valuechange', config);
563
Y.Event.define('valueChange', config); // deprecated, but supported for backcompat
564
 
565
Y.ValueChange = VC;
566
 
567
 
568
}, '3.18.1', {"requires": ["event-focus", "event-synthetic"]});