11
12
'use strict';
13
14
+var EventPluginHub = require('EventPluginHub');
15
var EventPropagators = require('EventPropagators');
16
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
17
var ReactControlledComponent = require('ReactControlledComponent');
18
var ReactDOMComponentTree = require('ReactDOMComponentTree');
19
+var ReactGenericBatching = require('ReactGenericBatching');
20
var SyntheticEvent = require('SyntheticEvent');
19
-var getActiveElement = require('fbjs/lib/getActiveElement');
21
22
var inputValueTracking = require('inputValueTracking');
23
+var getEventTarget = require('getEventTarget');
24
+var isEventSupported = require('isEventSupported');
25
var isTextInputElement = require('isTextInputElement');
26
27
var eventTypes = {
68
EventPropagators.accumulateTwoPhaseDispatches(event);
69
return event;
70
}
71
+/**
72
+ * For IE shims
73
+ */
74
+var activeElement = null;
75
+var activeElementInst = null;
76
69
-function getInstIfValueChanged(targetInst, targetNode) {
77
+/**
78
+ * SECTION: handle `change` event
79
+ */
80
+function shouldUseChangeEvent(elem) {
81
+ var nodeName = elem.nodeName && elem.nodeName.toLowerCase();
82
+ return (
83
+ nodeName === 'select' || (nodeName === 'input' && elem.type === 'file')
84
+ );
85
+}
86
+
87
+function manualDispatchChangeEvent(nativeEvent) {
88
+ var event = createAndAccumulateChangeEvent(
89
+ activeElementInst,
90
+ nativeEvent,
91
+ getEventTarget(nativeEvent),
92
+ );
93
+
94
+ // If change and propertychange bubbled, we'd just bind to it like all the
95
+ // other events and have it go through ReactBrowserEventEmitter. Since it
96
+ // doesn't, we manually listen for the events and so we have to enqueue and
97
+ // process the abstract event manually.
98
+ //
99
+ // Batching is necessary here in order to ensure that all event handlers run
100
+ // before the next rerender (including event handlers attached to ancestor
101
+ // elements instead of directly on the input). Without this, controlled
102
+ // components don't work properly in conjunction with event bubbling because
103
+ // the component is rerendered and the value reverted before all the event
104
+ // handlers can run. See https://github.com/facebook/react/issues/708.
105
+ ReactGenericBatching.batchedUpdates(runEventInBatch, event);
106
+}
107
+
108
+function runEventInBatch(event) {
109
+ EventPluginHub.enqueueEvents(event);
110
+ EventPluginHub.processEventQueue(false);
111
+}
112
+
113
+function getInstIfValueChanged(targetInst) {
114
+ const targetNode = ReactDOMComponentTree.getNodeFromInstance(targetInst);
115
if (inputValueTracking.updateValueIfChanged(targetNode)) {
116
return targetInst;
117
}
118
}
119
120
+function getTargetInstForChangeEvent(topLevelType, targetInst) {
121
+ if (topLevelType === 'topChange') {
122
+ return targetInst;
123
+ }
124
+}
125
+
126
/**
127
* SECTION: handle `input` event
128
*/
78
-
79
-var isTextInputEventSupported = false;
129
+var isInputEventSupported = false;
130
if (ExecutionEnvironment.canUseDOM) {
81
- isTextInputEventSupported =
82
- !document.documentMode || document.documentMode > 9;
131
+ // IE9 claims to support the input event but fails to trigger it when
132
+ // deleting text, so we ignore its input events.
133
+ isInputEventSupported =
134
+ isEventSupported('input') &&
135
+ (!document.documentMode || document.documentMode > 9);
136
}
137
85
-function getTargetInstForInputEventPolyfill(
86
- topLevelType,
87
- targetInst,
88
- targetNode,
89
-) {
138
+/**
139
+ * (For IE <=9) Starts tracking propertychange events on the passed-in element
140
+ * and override the value property so that we can distinguish user events from
141
+ * value changes in JS.
142
+ */
143
+function startWatchingForValueChange(target, targetInst) {
144
+ activeElement = target;
145
+ activeElementInst = targetInst;
146
+ activeElement.attachEvent('onpropertychange', handlePropertyChange);
147
+}
148
+
149
+/**
150
+ * (For IE <=9) Removes the event listeners from the currently-tracked element,
151
+ * if any exists.
152
+ */
153
+function stopWatchingForValueChange() {
154
+ if (!activeElement) {
155
+ return;
156
+ }
157
+ activeElement.detachEvent('onpropertychange', handlePropertyChange);
158
+ activeElement = null;
159
+ activeElementInst = null;
160
+}
161
+
162
+/**
163
+ * (For IE <=9) Handles a propertychange event, sending a `change` event if
164
+ * the value of the active element has changed.
165
+ */
166
+function handlePropertyChange(nativeEvent) {
167
+ if (nativeEvent.propertyName !== 'value') {
168
+ return;
169
+ }
170
+ if (getInstIfValueChanged(activeElementInst)) {
171
+ manualDispatchChangeEvent(nativeEvent);
172
+ }
173
+}
174
+
175
+function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
176
+ if (topLevelType === 'topFocus') {
177
+ // In IE9, propertychange fires for most input events but is buggy and
178
+ // doesn't fire when text is deleted, but conveniently, selectionchange
179
+ // appears to fire in all of the remaining cases so we catch those and
180
+ // forward the event if the value has changed
181
+ // In either case, we don't want to call the event handler if the value
182
+ // is changed from JS so we redefine a setter for `.value` that updates
183
+ // our activeElementValue variable, allowing us to ignore those changes
184
+ //
185
+ // stopWatching() should be a noop here but we call it just in case we
186
+ // missed a blur event somehow.
187
+ stopWatchingForValueChange();
188
+ startWatchingForValueChange(target, targetInst);
189
+ } else if (topLevelType === 'topBlur') {
190
+ stopWatchingForValueChange();
191
+ }
192
+}
193
+
194
+// For IE8 and IE9.
195
+function getTargetInstForInputEventPolyfill(topLevelType, targetInst) {
196
if (
91
- topLevelType === 'topInput' ||
92
- topLevelType === 'topChange' ||
93
- // These events catch anything the IE9 onInput misses
197
topLevelType === 'topSelectionChange' ||
198
topLevelType === 'topKeyUp' ||
199
topLevelType === 'topKeyDown'
200
) {
98
- return getInstIfValueChanged(targetInst, targetNode);
201
+ // On the selectionchange event, the target is just document which isn't
202
+ // helpful for us so just check activeElement instead.
203
+ //
204
+ // 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
205
+ // propertychange on the first input event after setting `value` from a
206
+ // script and fires only keydown, keypress, keyup. Catching keyup usually
207
+ // gets it and catching keydown lets us fire an event for the first
208
+ // keystroke if user does a key repeat (it'll be a little delayed: right
209
+ // before the second keystroke). Other input methods (e.g., paste) seem to
210
+ // fire selectionchange normally.
211
+ return getInstIfValueChanged(activeElementInst);
212
}
213
}
214
102
-function getTargetInstForInputOrChangeEvent(
103
- topLevelType,
104
- targetInst,
105
- targetNode,
106
-) {
107
- if (topLevelType === 'topInput' || topLevelType === 'topChange') {
108
- return getInstIfValueChanged(targetInst, targetNode);
215
+/**
216
+ * SECTION: handle `click` event
217
+ */
218
+function shouldUseClickEvent(elem) {
219
+ // Use the `click` event to detect changes to checkbox and radio inputs.
220
+ // This approach works across all browsers, whereas `change` does not fire
221
+ // until `blur` in IE8.
222
+ var nodeName = elem.nodeName;
223
+ return (
224
+ nodeName &&
225
+ nodeName.toLowerCase() === 'input' &&
226
+ (elem.type === 'checkbox' || elem.type === 'radio')
227
+ );
228
+}
229
+
230
+function getTargetInstForClickEvent(topLevelType, targetInst) {
231
+ if (topLevelType === 'topClick') {
232
+ return getInstIfValueChanged(targetInst);
233
}
234
}
235
112
-function getTargetInstForChangeEvent(topLevelType, targetInst, targetNode) {
113
- if (topLevelType === 'topChange') {
114
- return getInstIfValueChanged(targetInst, targetNode);
236
+function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {
237
+ if (topLevelType === 'topInput' || topLevelType === 'topChange') {
238
+ return getInstIfValueChanged(targetInst);
239
}
240
}
241
272
var ChangeEventPlugin = {
273
eventTypes: eventTypes,
274
151
- extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
275
+ _isInputEventSupported: isInputEventSupported,
276
+
277
+ extractEvents: function(
278
+ topLevelType,
279
+ targetInst,
280
+ nativeEvent,
281
+ nativeEventTarget,
282
+ ) {
283
var targetNode = targetInst
284
? ReactDOMComponentTree.getNodeFromInstance(targetInst)
285
: window;
286
156
- // On the selectionchange event, the target is the document which isn't
157
- // helpful becasue we need the input, so we use the activeElement instead.
158
- if (!isTextInputEventSupported && topLevelType === 'topSelectionChange') {
159
- nativeEventTarget = targetNode = getActiveElement();
160
-
161
- if (targetNode) {
162
- targetInst = ReactDOMComponentTree.getInstanceFromNode(targetNode);
163
- }
164
- }
165
-
287
var getTargetInstFunc, handleEventFunc;
167
-
288
if (shouldUseChangeEvent(targetNode)) {
289
getTargetInstFunc = getTargetInstForChangeEvent;
170
- } else if (isTextInputElement(targetNode) && !isTextInputEventSupported) {
171
- getTargetInstFunc = getTargetInstForInputEventPolyfill;
172
- } else {
173
- getTargetInstFunc = getTargetInstForInputOrChangeEvent;
290
+ } else if (isTextInputElement(targetNode)) {
291
+ if (isInputEventSupported) {
292
+ getTargetInstFunc = getTargetInstForInputOrChangeEvent;
293
+ } else {
294
+ getTargetInstFunc = getTargetInstForInputEventPolyfill;
295
+ handleEventFunc = handleEventsForInputEventPolyfill;
296
+ }
297
+ } else if (shouldUseClickEvent(targetNode)) {
298
+ getTargetInstFunc = getTargetInstForClickEvent;
299
}
300
301
if (getTargetInstFunc) {
177
- var inst = getTargetInstFunc(topLevelType, targetInst, targetNode);
302
+ var inst = getTargetInstFunc(topLevelType, targetInst);
303
if (inst) {
304
var event = createAndAccumulateChangeEvent(
305
inst,