13
* See the License for the specific language governing permissions and
14
* limitations under the License.
15
*
16
- * @providesModule TextChangeEventPlugin
16
+ * @providesModule ChangeEventPlugin
17
*/
18
19
"use strict";
30
var topLevelTypes = EventConstants.topLevelTypes;
31
32
var eventTypes = {
33
- textChange: {
33
+ change: {
34
phasedRegistrationNames: {
35
- bubbled: keyOf({onTextChange: null}),
36
- captured: keyOf({onTextChangeCapture: null})
35
+ bubbled: keyOf({onChange: null}),
36
+ captured: keyOf({onChangeCapture: null})
37
}
38
}
39
};
40
41
-var isInputSupported;
41
+/**
42
+ * For IE shims
43
+ */
44
+var activeElement = null;
45
+var activeElementID = null;
46
+var activeElementValue = null;
47
+var activeElementValueProp = null;
48
+
49
+
50
+/**
51
+ * SECTION: handle `change` event
52
+ */
53
+var shouldUseChangeEvent = function(elem) {
54
+ return elem.nodeName === 'SELECT';
55
+};
56
+
57
+var doesChangeEventBubble = false;
58
+if (ExecutionEnvironment.canUseDOM) {
59
+ // See `handleChange` comment below
60
+ doesChangeEventBubble = isEventSupported('change') && (
61
+ !('documentMode' in document) || document.documentMode > 8
62
+ );
63
+}
64
+
65
+/**
66
+ * (For IE8). The `change` event does not bubble in IE8 so we add a listener
67
+ * when the element is focused (and remove when blurred).
68
+ */
69
+var handleChange = function(nativeEvent) {
70
+ var event = SyntheticEvent.getPooled(
71
+ eventTypes.change,
72
+ activeElementID,
73
+ nativeEvent
74
+ );
75
+ EventPropagators.accumulateTwoPhaseDispatches(event);
76
+
77
+ // If change bubbled, we'd just bind to it like all the other events
78
+ // and have it go through ReactEventTopLevelCallback. Since it doesn't, we
79
+ // manually listen for the change event and so we have to enqueue and
80
+ // process the abstract event manually.
81
+ EventPluginHub.enqueueEvents(event);
82
+ EventPluginHub.processEventQueue();
83
+};
84
+
85
+var getTargetIDForChangeEvent;
86
+if (doesChangeEventBubble) {
87
+ getTargetIDForChangeEvent = function(
88
+ topLevelType,
89
+ topLevelTarget,
90
+ topLevelTargetID) {
91
+ if (topLevelType === topLevelTypes.topChange) {
92
+ return topLevelTargetID;
93
+ }
94
+ };
95
+} else {
96
+ getTargetIDForChangeEvent = function(
97
+ topLevelType,
98
+ topLevelTarget,
99
+ topLevelTargetID) {
100
+ if (topLevelType === topLevelTypes.topFocus) {
101
+ activeElementID = topLevelTargetID;
102
+ topLevelTarget.attachEvent('onchange', handleChange);
103
+ } else if (topLevelType === topLevelTypes.topBlur) {
104
+ topLevelTarget.detachEvent('onchange', handleChange);
105
+ activeElementID = null;
106
+ }
107
+ };
108
+}
109
+
110
+
111
+/**
112
+ * SECTION: handle `input` event
113
+ */
114
+var isInputEventSupported = false;
115
if (ExecutionEnvironment.canUseDOM) {
116
// IE9 claims to support the input event but fails to trigger it when
117
// deleting text, so we ignore its input events
45
- isInputSupported = isEventSupported('input') && (
46
- !("documentMode" in document) || document.documentMode > 9
118
+ isInputEventSupported = isEventSupported('input') && (
119
+ !('documentMode' in document) || document.documentMode > 9
120
);
121
}
122
50
-var hasInputCapabilities = function(elem) {
51
- // The HTML5 spec lists many more types than `text` and `password` on which
52
- // the input event is triggered but none of them exist in old IE, so we don't
53
- // check them here.
54
- // TODO: <textarea> should be supported too but IE seems to reset the
55
- // selection when changing textarea contents during a selectionchange event
56
- // so it's not listed here for now.
123
+
124
+/**
125
+ * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
126
+ */
127
+var supportedInputTypes = {
128
+ 'color': true,
129
+ 'date': true,
130
+ 'datetime': true,
131
+ 'datetime-local': true,
132
+ 'email': true,
133
+ 'month': true,
134
+ 'number': true,
135
+ 'password': true,
136
+ 'range': true,
137
+ 'search': true,
138
+ 'tel': true,
139
+ 'text': true,
140
+ 'time': true,
141
+ 'url': true,
142
+ 'week': true
143
+};
144
+
145
+var shouldUseInputEvent = function(elem) {
146
return (
58
- elem.nodeName === 'INPUT' &&
59
- (elem.type === 'text' || elem.type === 'password')
147
+ (elem.nodeName === 'INPUT' && supportedInputTypes[elem.type]) ||
148
+ elem.nodeName === 'TEXTAREA'
149
);
150
};
151
63
-var activeElement = null;
64
-var activeElementID = null;
65
-var activeElementValue = null;
66
-var activeElementValueProp = null;
67
-
152
/**
153
* (For old IE.) Replacement getter/setter for the `value` property that gets
154
* set on the active element.
168
* and override the value property so that we can distinguish user events from
169
* value changes in JS.
170
*/
87
-var startWatching = function(target, targetID) {
171
+var startWatchingForValueChange = function(target, targetID) {
172
activeElement = target;
173
activeElementID = targetID;
174
activeElementValue = target.value;
185
* (For old IE.) Removes the event listeners from the currently-tracked element,
186
* if any exists.
187
*/
104
-var stopWatching = function() {
188
+var stopWatchingForValueChange = function() {
189
if (!activeElement) {
190
return;
191
}
201
};
202
203
/**
120
- * (For old IE.) Handles a propertychange event, sending a textChange event if
204
+ * (For old IE.) Handles a propertychange event, sending a `change` event if
205
* the value of the active element has changed.
206
*/
207
var handlePropertyChange = function(nativeEvent) {
124
- if (nativeEvent.propertyName !== "value") {
208
+ if (nativeEvent.propertyName !== 'value') {
209
return;
210
}
211
var value = nativeEvent.srcElement.value;
215
activeElementValue = value;
216
217
var event = SyntheticEvent.getPooled(
134
- eventTypes.textChange,
218
+ eventTypes.change,
219
activeElementID,
220
nativeEvent
221
);
222
EventPropagators.accumulateTwoPhaseDispatches(event);
223
140
- // If propertychange bubbled, we'd just bind to it like all the other events
141
- // and have it go through ReactEventTopLevelCallback. Since it doesn't, we
142
- // manually listen for the propertychange event and so we have to enqueue and
143
- // process the abstract event manually.
224
+ // See comment above in handleChange
225
EventPluginHub.enqueueEvents(event);
226
EventPluginHub.processEventQueue();
227
};
228
229
/**
149
- * If a textChange event should be fired, returns the target's ID.
230
+ * If a `change` event should be fired, returns the target's ID.
231
*/
151
-var targetIDForTextChangeEvent;
152
-if (isInputSupported) {
153
- targetIDForTextChangeEvent = function(
232
+var getTargetIDForValueChangeEvent;
233
+if (isInputEventSupported) {
234
+ getTargetIDForValueChangeEvent = function(
235
topLevelType,
236
topLevelTarget,
237
topLevelTargetID) {
238
if (topLevelType === topLevelTypes.topInput) {
239
// In modern browsers (i.e., not IE8 or IE9), the input event is exactly
159
- // what we want so fall through here and trigger an abstract event...
160
- if (topLevelTarget.nodeName === 'TEXTAREA') {
161
- // ...unless it's a textarea, in which case we don't fire an event (so
162
- // that we have consistency with our old-IE shim).
163
- return;
164
- }
240
+ // what we want so fall through here and trigger an abstract event
241
return topLevelTargetID;
242
}
243
};
244
} else {
169
- targetIDForTextChangeEvent = function(
245
+ getTargetIDForValueChangeEvent = function(
246
topLevelType,
247
topLevelTarget,
248
topLevelTargetID) {
257
// In either case, we don't want to call the event handler if the value
258
// is changed from JS so we redefine a setter for `.value` that updates
259
// our activeElementValue variable, allowing us to ignore those changes
184
- if (hasInputCapabilities(topLevelTarget)) {
185
- // stopWatching() should be a noop here but we call it just in case we
186
- // missed a blur event somehow.
187
- stopWatching();
188
- startWatching(topLevelTarget, topLevelTargetID);
189
- }
260
+ //
261
+ // stopWatching() should be a noop here but we call it just in case we
262
+ // missed a blur event somehow.
263
+ stopWatchingForValueChange();
264
+ startWatchingForValueChange(topLevelTarget, topLevelTargetID);
265
} else if (topLevelType === topLevelTypes.topBlur) {
191
- stopWatching();
266
+ stopWatchingForValueChange();
267
} else if (
268
topLevelType === topLevelTypes.topSelectionChange ||
269
topLevelType === topLevelTypes.topKeyUp ||
286
};
287
}
288
214
-var TextChangeEventPlugin = {
289
+
290
+/**
291
+ * SECTION: handle `click` event
292
+ */
293
+var shouldUseClickEvent = function(elem) {
294
+ // Use the `click` event to detect changes to checkbox and radio inputs.
295
+ // This approach works across all browsers, whereas `change` does not fire
296
+ // until `blur` in IE8.
297
+ return (
298
+ elem.nodeName === 'INPUT' &&
299
+ (elem.type === 'checkbox' || elem.type === 'radio')
300
+ );
301
+};
302
+
303
+var getTargetIDForClickEvent = function(
304
+ topLevelType,
305
+ topLevelTarget,
306
+ topLevelTargetID) {
307
+ if (topLevelType === topLevelTypes.topClick) {
308
+ return topLevelTargetID;
309
+ }
310
+};
311
+
312
+/**
313
+ * This plugin creates an `onChange` event that normalizes change events
314
+ * across form elements. This event fires at a time when it's possible to
315
+ * change the element's value without seeing a flicker.
316
+ *
317
+ * Supported elements are:
318
+ * - input (see `supportedInputTypes`)
319
+ * - textarea
320
+ * - select
321
+ */
322
+var ChangeEventPlugin = {
323
324
eventTypes: eventTypes,
325
336
topLevelTarget,
337
topLevelTargetID,
338
nativeEvent) {
231
- var targetID = targetIDForTextChangeEvent(
232
- topLevelType,
233
- topLevelTarget,
234
- topLevelTargetID
235
- );
339
+
340
+ var getTargetIDFunc, targetID;
341
+ if (shouldUseChangeEvent(topLevelTarget)) {
342
+ getTargetIDFunc = getTargetIDForChangeEvent;
343
+ } else if (shouldUseInputEvent(topLevelTarget)) {
344
+ getTargetIDFunc = getTargetIDForValueChangeEvent;
345
+ } else if (shouldUseClickEvent(topLevelTarget)) {
346
+ getTargetIDFunc = getTargetIDForClickEvent;
347
+ }
348
+ if (getTargetIDFunc) {
349
+ targetID = getTargetIDFunc(
350
+ topLevelType,
351
+ topLevelTarget,
352
+ topLevelTargetID
353
+ );
354
+ }
355
356
if (targetID) {
357
var event = SyntheticEvent.getPooled(
239
- eventTypes.textChange,
358
+ eventTypes.change,
359
targetID,
360
nativeEvent
361
);
366
367
};
368
250
-module.exports = TextChangeEventPlugin;
369
+module.exports = ChangeEventPlugin;