Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80529 views
1
/**
2
* Copyright 2013-2015, Facebook, Inc.
3
* All rights reserved.
4
*
5
* This source code is licensed under the BSD-style license found in the
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*
9
* @providesModule ChangeEventPlugin
10
*/
11
12
'use strict';
13
14
var EventConstants = require("./EventConstants");
15
var EventPluginHub = require("./EventPluginHub");
16
var EventPropagators = require("./EventPropagators");
17
var ExecutionEnvironment = require("./ExecutionEnvironment");
18
var ReactUpdates = require("./ReactUpdates");
19
var SyntheticEvent = require("./SyntheticEvent");
20
21
var isEventSupported = require("./isEventSupported");
22
var isTextInputElement = require("./isTextInputElement");
23
var keyOf = require("./keyOf");
24
25
var topLevelTypes = EventConstants.topLevelTypes;
26
27
var eventTypes = {
28
change: {
29
phasedRegistrationNames: {
30
bubbled: keyOf({onChange: null}),
31
captured: keyOf({onChangeCapture: null})
32
},
33
dependencies: [
34
topLevelTypes.topBlur,
35
topLevelTypes.topChange,
36
topLevelTypes.topClick,
37
topLevelTypes.topFocus,
38
topLevelTypes.topInput,
39
topLevelTypes.topKeyDown,
40
topLevelTypes.topKeyUp,
41
topLevelTypes.topSelectionChange
42
]
43
}
44
};
45
46
/**
47
* For IE shims
48
*/
49
var activeElement = null;
50
var activeElementID = null;
51
var activeElementValue = null;
52
var activeElementValueProp = null;
53
54
/**
55
* SECTION: handle `change` event
56
*/
57
function shouldUseChangeEvent(elem) {
58
return (
59
elem.nodeName === 'SELECT' ||
60
(elem.nodeName === 'INPUT' && elem.type === 'file')
61
);
62
}
63
64
var doesChangeEventBubble = false;
65
if (ExecutionEnvironment.canUseDOM) {
66
// See `handleChange` comment below
67
doesChangeEventBubble = isEventSupported('change') && (
68
(!('documentMode' in document) || document.documentMode > 8)
69
);
70
}
71
72
function manualDispatchChangeEvent(nativeEvent) {
73
var event = SyntheticEvent.getPooled(
74
eventTypes.change,
75
activeElementID,
76
nativeEvent
77
);
78
EventPropagators.accumulateTwoPhaseDispatches(event);
79
80
// If change and propertychange bubbled, we'd just bind to it like all the
81
// other events and have it go through ReactBrowserEventEmitter. Since it
82
// doesn't, we manually listen for the events and so we have to enqueue and
83
// process the abstract event manually.
84
//
85
// Batching is necessary here in order to ensure that all event handlers run
86
// before the next rerender (including event handlers attached to ancestor
87
// elements instead of directly on the input). Without this, controlled
88
// components don't work properly in conjunction with event bubbling because
89
// the component is rerendered and the value reverted before all the event
90
// handlers can run. See https://github.com/facebook/react/issues/708.
91
ReactUpdates.batchedUpdates(runEventInBatch, event);
92
}
93
94
function runEventInBatch(event) {
95
EventPluginHub.enqueueEvents(event);
96
EventPluginHub.processEventQueue();
97
}
98
99
function startWatchingForChangeEventIE8(target, targetID) {
100
activeElement = target;
101
activeElementID = targetID;
102
activeElement.attachEvent('onchange', manualDispatchChangeEvent);
103
}
104
105
function stopWatchingForChangeEventIE8() {
106
if (!activeElement) {
107
return;
108
}
109
activeElement.detachEvent('onchange', manualDispatchChangeEvent);
110
activeElement = null;
111
activeElementID = null;
112
}
113
114
function getTargetIDForChangeEvent(
115
topLevelType,
116
topLevelTarget,
117
topLevelTargetID) {
118
if (topLevelType === topLevelTypes.topChange) {
119
return topLevelTargetID;
120
}
121
}
122
function handleEventsForChangeEventIE8(
123
topLevelType,
124
topLevelTarget,
125
topLevelTargetID) {
126
if (topLevelType === topLevelTypes.topFocus) {
127
// stopWatching() should be a noop here but we call it just in case we
128
// missed a blur event somehow.
129
stopWatchingForChangeEventIE8();
130
startWatchingForChangeEventIE8(topLevelTarget, topLevelTargetID);
131
} else if (topLevelType === topLevelTypes.topBlur) {
132
stopWatchingForChangeEventIE8();
133
}
134
}
135
136
137
/**
138
* SECTION: handle `input` event
139
*/
140
var isInputEventSupported = false;
141
if (ExecutionEnvironment.canUseDOM) {
142
// IE9 claims to support the input event but fails to trigger it when
143
// deleting text, so we ignore its input events
144
isInputEventSupported = isEventSupported('input') && (
145
(!('documentMode' in document) || document.documentMode > 9)
146
);
147
}
148
149
/**
150
* (For old IE.) Replacement getter/setter for the `value` property that gets
151
* set on the active element.
152
*/
153
var newValueProp = {
154
get: function() {
155
return activeElementValueProp.get.call(this);
156
},
157
set: function(val) {
158
// Cast to a string so we can do equality checks.
159
activeElementValue = '' + val;
160
activeElementValueProp.set.call(this, val);
161
}
162
};
163
164
/**
165
* (For old IE.) Starts tracking propertychange events on the passed-in element
166
* and override the value property so that we can distinguish user events from
167
* value changes in JS.
168
*/
169
function startWatchingForValueChange(target, targetID) {
170
activeElement = target;
171
activeElementID = targetID;
172
activeElementValue = target.value;
173
activeElementValueProp = Object.getOwnPropertyDescriptor(
174
target.constructor.prototype,
175
'value'
176
);
177
178
Object.defineProperty(activeElement, 'value', newValueProp);
179
activeElement.attachEvent('onpropertychange', handlePropertyChange);
180
}
181
182
/**
183
* (For old IE.) Removes the event listeners from the currently-tracked element,
184
* if any exists.
185
*/
186
function stopWatchingForValueChange() {
187
if (!activeElement) {
188
return;
189
}
190
191
// delete restores the original property definition
192
delete activeElement.value;
193
activeElement.detachEvent('onpropertychange', handlePropertyChange);
194
195
activeElement = null;
196
activeElementID = null;
197
activeElementValue = null;
198
activeElementValueProp = null;
199
}
200
201
/**
202
* (For old IE.) Handles a propertychange event, sending a `change` event if
203
* the value of the active element has changed.
204
*/
205
function handlePropertyChange(nativeEvent) {
206
if (nativeEvent.propertyName !== 'value') {
207
return;
208
}
209
var value = nativeEvent.srcElement.value;
210
if (value === activeElementValue) {
211
return;
212
}
213
activeElementValue = value;
214
215
manualDispatchChangeEvent(nativeEvent);
216
}
217
218
/**
219
* If a `change` event should be fired, returns the target's ID.
220
*/
221
function getTargetIDForInputEvent(
222
topLevelType,
223
topLevelTarget,
224
topLevelTargetID) {
225
if (topLevelType === topLevelTypes.topInput) {
226
// In modern browsers (i.e., not IE8 or IE9), the input event is exactly
227
// what we want so fall through here and trigger an abstract event
228
return topLevelTargetID;
229
}
230
}
231
232
// For IE8 and IE9.
233
function handleEventsForInputEventIE(
234
topLevelType,
235
topLevelTarget,
236
topLevelTargetID) {
237
if (topLevelType === topLevelTypes.topFocus) {
238
// In IE8, we can capture almost all .value changes by adding a
239
// propertychange handler and looking for events with propertyName
240
// equal to 'value'
241
// In IE9, propertychange fires for most input events but is buggy and
242
// doesn't fire when text is deleted, but conveniently, selectionchange
243
// appears to fire in all of the remaining cases so we catch those and
244
// forward the event if the value has changed
245
// In either case, we don't want to call the event handler if the value
246
// is changed from JS so we redefine a setter for `.value` that updates
247
// our activeElementValue variable, allowing us to ignore those changes
248
//
249
// stopWatching() should be a noop here but we call it just in case we
250
// missed a blur event somehow.
251
stopWatchingForValueChange();
252
startWatchingForValueChange(topLevelTarget, topLevelTargetID);
253
} else if (topLevelType === topLevelTypes.topBlur) {
254
stopWatchingForValueChange();
255
}
256
}
257
258
// For IE8 and IE9.
259
function getTargetIDForInputEventIE(
260
topLevelType,
261
topLevelTarget,
262
topLevelTargetID) {
263
if (topLevelType === topLevelTypes.topSelectionChange ||
264
topLevelType === topLevelTypes.topKeyUp ||
265
topLevelType === topLevelTypes.topKeyDown) {
266
// On the selectionchange event, the target is just document which isn't
267
// helpful for us so just check activeElement instead.
268
//
269
// 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
270
// propertychange on the first input event after setting `value` from a
271
// script and fires only keydown, keypress, keyup. Catching keyup usually
272
// gets it and catching keydown lets us fire an event for the first
273
// keystroke if user does a key repeat (it'll be a little delayed: right
274
// before the second keystroke). Other input methods (e.g., paste) seem to
275
// fire selectionchange normally.
276
if (activeElement && activeElement.value !== activeElementValue) {
277
activeElementValue = activeElement.value;
278
return activeElementID;
279
}
280
}
281
}
282
283
284
/**
285
* SECTION: handle `click` event
286
*/
287
function shouldUseClickEvent(elem) {
288
// Use the `click` event to detect changes to checkbox and radio inputs.
289
// This approach works across all browsers, whereas `change` does not fire
290
// until `blur` in IE8.
291
return (
292
elem.nodeName === 'INPUT' &&
293
(elem.type === 'checkbox' || elem.type === 'radio')
294
);
295
}
296
297
function getTargetIDForClickEvent(
298
topLevelType,
299
topLevelTarget,
300
topLevelTargetID) {
301
if (topLevelType === topLevelTypes.topClick) {
302
return topLevelTargetID;
303
}
304
}
305
306
/**
307
* This plugin creates an `onChange` event that normalizes change events
308
* across form elements. This event fires at a time when it's possible to
309
* change the element's value without seeing a flicker.
310
*
311
* Supported elements are:
312
* - input (see `isTextInputElement`)
313
* - textarea
314
* - select
315
*/
316
var ChangeEventPlugin = {
317
318
eventTypes: eventTypes,
319
320
/**
321
* @param {string} topLevelType Record from `EventConstants`.
322
* @param {DOMEventTarget} topLevelTarget The listening component root node.
323
* @param {string} topLevelTargetID ID of `topLevelTarget`.
324
* @param {object} nativeEvent Native browser event.
325
* @return {*} An accumulation of synthetic events.
326
* @see {EventPluginHub.extractEvents}
327
*/
328
extractEvents: function(
329
topLevelType,
330
topLevelTarget,
331
topLevelTargetID,
332
nativeEvent) {
333
334
var getTargetIDFunc, handleEventFunc;
335
if (shouldUseChangeEvent(topLevelTarget)) {
336
if (doesChangeEventBubble) {
337
getTargetIDFunc = getTargetIDForChangeEvent;
338
} else {
339
handleEventFunc = handleEventsForChangeEventIE8;
340
}
341
} else if (isTextInputElement(topLevelTarget)) {
342
if (isInputEventSupported) {
343
getTargetIDFunc = getTargetIDForInputEvent;
344
} else {
345
getTargetIDFunc = getTargetIDForInputEventIE;
346
handleEventFunc = handleEventsForInputEventIE;
347
}
348
} else if (shouldUseClickEvent(topLevelTarget)) {
349
getTargetIDFunc = getTargetIDForClickEvent;
350
}
351
352
if (getTargetIDFunc) {
353
var targetID = getTargetIDFunc(
354
topLevelType,
355
topLevelTarget,
356
topLevelTargetID
357
);
358
if (targetID) {
359
var event = SyntheticEvent.getPooled(
360
eventTypes.change,
361
targetID,
362
nativeEvent
363
);
364
EventPropagators.accumulateTwoPhaseDispatches(event);
365
return event;
366
}
367
}
368
369
if (handleEventFunc) {
370
handleEventFunc(
371
topLevelType,
372
topLevelTarget,
373
topLevelTargetID
374
);
375
}
376
}
377
378
};
379
380
module.exports = ChangeEventPlugin;
381
382