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