The library for web and native user interfaces.
Refactor of ChangeEventPlugin
some refactoring and also handle if `blur` doesn't fire on the form input in IE8 (by always cleaning up on focus). per discussions with @balpert
CommitSyncScript committed
Jun 26, 2013 at 14:29 UTC
9ceaff73183837387835f9e96c80e58fc4aea34c
1 file changed
+131
-120
src/eventPlugins/ChangeEventPlugin.js
+131
-120
@@ -50,12 +50,12 @@ var activeElementValueProp = null;
50
/**
51
* SECTION: handle `change` event
52
*/
53
-var shouldUseChangeEvent = function(elem) {
53
+function shouldUseChangeEvent(elem) {
54
return (
55
elem.nodeName === 'SELECT' ||
56
(elem.nodeName === 'INPUT' && elem.type === 'file')
57
);
58
-};
58
+}
59
60
var doesChangeEventBubble = false;
61
if (ExecutionEnvironment.canUseDOM) {
@@ -65,11 +65,7 @@ if (ExecutionEnvironment.canUseDOM) {
65
);
66
}
67
68
-/**
69
- * (For IE8). The `change` event does not bubble in IE8 so we add a listener
70
- * when the element is focused (and remove when blurred).
71
- */
72
-var handleChange = function(nativeEvent) {
68
+function manualDispatchChangeEvent(nativeEvent) {
69
var event = SyntheticEvent.getPooled(
70
eventTypes.change,
71
activeElementID,
@@ -83,31 +79,43 @@ var handleChange = function(nativeEvent) {
79
// process the abstract event manually.
80
EventPluginHub.enqueueEvents(event);
81
EventPluginHub.processEventQueue();
86
-};
82
+}
83
88
-var getTargetIDForChangeEvent;
89
-if (doesChangeEventBubble) {
90
- getTargetIDForChangeEvent = function(
91
- topLevelType,
92
- topLevelTarget,
93
- topLevelTargetID) {
94
- if (topLevelType === topLevelTypes.topChange) {
95
- return topLevelTargetID;
96
- }
97
- };
98
-} else {
99
- getTargetIDForChangeEvent = function(
100
- topLevelType,
101
- topLevelTarget,
102
- topLevelTargetID) {
103
- if (topLevelType === topLevelTypes.topFocus) {
104
- activeElementID = topLevelTargetID;
105
- topLevelTarget.attachEvent('onchange', handleChange);
106
- } else if (topLevelType === topLevelTypes.topBlur) {
107
- topLevelTarget.detachEvent('onchange', handleChange);
108
- activeElementID = null;
109
- }
110
- };
84
+function startWatchingForChangeEventIE8(target, targetID) {
85
+ activeElement = target;
86
+ activeElementID = targetID;
87
+ activeElement.attachEvent('onchange', manualDispatchChangeEvent);
88
+}
89
+
90
+function stopWatchingForChangeEventIE8() {
91
+ if (!activeElement) {
92
+ return;
93
+ }
94
+ activeElement.detachEvent('onchange', manualDispatchChangeEvent);
95
+ activeElement = null;
96
+ activeElementID = null;
97
+}
98
+
99
+function getTargetIDForChangeEvent(
100
+ topLevelType,
101
+ topLevelTarget,
102
+ topLevelTargetID) {
103
+ if (topLevelType === topLevelTypes.topChange) {
104
+ return topLevelTargetID;
105
+ }
106
+}
107
+function handleEventsForChangeEventIE8(
108
+ topLevelType,
109
+ topLevelTarget,
110
+ topLevelTargetID) {
111
+ if (topLevelType === topLevelTypes.topFocus) {
112
+ // stopWatching() should be a noop here but we call it just in case we
113
+ // missed a blur event somehow.
114
+ stopWatchingForChangeEventIE8();
115
+ startWatchingForChangeEventIE8(topLevelTarget, topLevelTargetID);
116
+ } else if (topLevelType === topLevelTypes.topBlur) {
117
+ stopWatchingForChangeEventIE8();
118
+ }
119
}
120
121
@@ -145,12 +153,12 @@ var supportedInputTypes = {
153
'week': true
154
};
155
148
-var shouldUseInputEvent = function(elem) {
156
+function shouldUseInputEvent(elem) {
157
return (
158
(elem.nodeName === 'INPUT' && supportedInputTypes[elem.type]) ||
159
elem.nodeName === 'TEXTAREA'
160
);
153
-};
161
+}
162
163
/**
164
* (For old IE.) Replacement getter/setter for the `value` property that gets
@@ -171,7 +179,7 @@ var newValueProp = {
179
* and override the value property so that we can distinguish user events from
180
* value changes in JS.
181
*/
174
-var startWatchingForValueChange = function(target, targetID) {
182
+function startWatchingForValueChange(target, targetID) {
183
activeElement = target;
184
activeElementID = targetID;
185
activeElementValue = target.value;
@@ -182,13 +190,13 @@ var startWatchingForValueChange = function(target, targetID) {
190
191
Object.defineProperty(activeElement, 'value', newValueProp);
192
activeElement.attachEvent('onpropertychange', handlePropertyChange);
185
-};
193
+}
194
195
/**
196
* (For old IE.) Removes the event listeners from the currently-tracked element,
197
* if any exists.
198
*/
191
-var stopWatchingForValueChange = function() {
199
+function stopWatchingForValueChange() {
200
if (!activeElement) {
201
return;
202
}
@@ -201,13 +209,13 @@ var stopWatchingForValueChange = function() {
209
activeElementID = null;
210
activeElementValue = null;
211
activeElementValueProp = null;
204
-};
212
+}
213
214
/**
215
* (For old IE.) Handles a propertychange event, sending a `change` event if
216
* the value of the active element has changed.
217
*/
210
-var handlePropertyChange = function(nativeEvent) {
218
+function handlePropertyChange(nativeEvent) {
219
if (nativeEvent.propertyName !== 'value') {
220
return;
221
}
@@ -217,83 +225,72 @@ var handlePropertyChange = function(nativeEvent) {
225
}
226
activeElementValue = value;
227
220
- var event = SyntheticEvent.getPooled(
221
- eventTypes.change,
222
- activeElementID,
223
- nativeEvent
224
- );
225
- EventPropagators.accumulateTwoPhaseDispatches(event);
226
-
227
- // See comment above in handleChange
228
- EventPluginHub.enqueueEvents(event);
229
- EventPluginHub.processEventQueue();
230
-};
228
+ manualDispatchChangeEvent(nativeEvent);
229
+}
230
231
/**
232
* If a `change` event should be fired, returns the target's ID.
233
*/
235
-var getTargetIDForValueChangeEvent;
236
-if (isInputEventSupported) {
237
- getTargetIDForValueChangeEvent = function(
238
- topLevelType,
239
- topLevelTarget,
240
- topLevelTargetID) {
241
- if (topLevelType === topLevelTypes.topInput) {
242
- // In modern browsers (i.e., not IE8 or IE9), the input event is exactly
243
- // what we want so fall through here and trigger an abstract event
244
- return topLevelTargetID;
245
- }
246
- };
247
-} else {
248
- getTargetIDForValueChangeEvent = function(
249
- topLevelType,
250
- topLevelTarget,
251
- topLevelTargetID) {
252
- if (topLevelType === topLevelTypes.topFocus) {
253
- // In IE8, we can capture almost all .value changes by adding a
254
- // propertychange handler and looking for events with propertyName
255
- // equal to 'value'
256
- // In IE9, propertychange fires for most input events but is buggy and
257
- // doesn't fire when text is deleted, but conveniently, selectionchange
258
- // appears to fire in all of the remaining cases so we catch those and
259
- // forward the event if the value has changed
260
- // In either case, we don't want to call the event handler if the value
261
- // is changed from JS so we redefine a setter for `.value` that updates
262
- // our activeElementValue variable, allowing us to ignore those changes
263
- //
264
- // stopWatching() should be a noop here but we call it just in case we
265
- // missed a blur event somehow.
266
- stopWatchingForValueChange();
267
- startWatchingForValueChange(topLevelTarget, topLevelTargetID);
268
- } else if (topLevelType === topLevelTypes.topBlur) {
269
- stopWatchingForValueChange();
270
- } else if (
271
- topLevelType === topLevelTypes.topSelectionChange ||
272
- topLevelType === topLevelTypes.topKeyUp ||
273
- topLevelType === topLevelTypes.topKeyDown) {
274
- // On the selectionchange event, the target is just document which isn't
275
- // helpful for us so just check activeElement instead.
276
- //
277
- // 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
278
- // propertychange on the first input event after setting `value` from a
279
- // script and fires only keydown, keypress, keyup. Catching keyup usually
280
- // gets it and catching keydown lets us fire an event for the first
281
- // keystroke if user does a key repeat (it'll be a little delayed: right
282
- // before the second keystroke). Other input methods (e.g., paste) seem to
283
- // fire selectionchange normally.
284
- if (activeElement && activeElement.value !== activeElementValue) {
285
- activeElementValue = activeElement.value;
286
- return activeElementID;
287
- }
234
+function getTargetIDForInputEvent(
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
240
+ // what we want so fall through here and trigger an abstract event
241
+ return topLevelTargetID;
242
+ }
243
+}
244
+
245
+// For IE8 and IE9
246
+function handleEventsForInputEventIE(
247
+ topLevelType,
248
+ topLevelTarget,
249
+ topLevelTargetID) {
250
+ if (topLevelType === topLevelTypes.topFocus) {
251
+ // In IE8, we can capture almost all .value changes by adding a
252
+ // propertychange handler and looking for events with propertyName
253
+ // equal to 'value'
254
+ // In IE9, propertychange fires for most input events but is buggy and
255
+ // doesn't fire when text is deleted, but conveniently, selectionchange
256
+ // appears to fire in all of the remaining cases so we catch those and
257
+ // forward the event if the value has changed
258
+ // In either case, we don't want to call the event handler if the value
259
+ // is changed from JS so we redefine a setter for `.value` that updates
260
+ // our activeElementValue variable, allowing us to ignore those changes
261
+ //
262
+ // stopWatching() should be a noop here but we call it just in case we
263
+ // missed a blur event somehow.
264
+ stopWatchingForValueChange();
265
+ startWatchingForValueChange(topLevelTarget, topLevelTargetID);
266
+ } else if (topLevelType === topLevelTypes.topBlur) {
267
+ stopWatchingForValueChange();
268
+ } else if (
269
+ topLevelType === topLevelTypes.topSelectionChange ||
270
+ topLevelType === topLevelTypes.topKeyUp ||
271
+ topLevelType === topLevelTypes.topKeyDown) {
272
+ // On the selectionchange event, the target is just document which isn't
273
+ // helpful for us so just check activeElement instead.
274
+ //
275
+ // 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
276
+ // propertychange on the first input event after setting `value` from a
277
+ // script and fires only keydown, keypress, keyup. Catching keyup usually
278
+ // gets it and catching keydown lets us fire an event for the first
279
+ // keystroke if user does a key repeat (it'll be a little delayed: right
280
+ // before the second keystroke). Other input methods (e.g., paste) seem to
281
+ // fire selectionchange normally.
282
+ if (activeElement && activeElement.value !== activeElementValue) {
283
+ activeElementValue = activeElement.value;
284
+ return activeElementID;
285
}
289
- };
286
+ }
287
}
288
289
290
/**
291
* SECTION: handle `click` event
292
*/
296
-var shouldUseClickEvent = function(elem) {
293
+function shouldUseClickEvent(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.
@@ -301,16 +298,16 @@ var shouldUseClickEvent = function(elem) {
298
elem.nodeName === 'INPUT' &&
299
(elem.type === 'checkbox' || elem.type === 'radio')
300
);
304
-};
301
+}
302
306
-var getTargetIDForClickEvent = function(
303
+function getTargetIDForClickEvent(
304
topLevelType,
305
topLevelTarget,
306
topLevelTargetID) {
307
if (topLevelType === topLevelTypes.topClick) {
308
return topLevelTargetID;
309
}
313
-};
310
+}
311
312
/**
313
* This plugin creates an `onChange` event that normalizes change events
@@ -340,30 +337,44 @@ var ChangeEventPlugin = {
337
topLevelTargetID,
338
nativeEvent) {
339
343
- var getTargetIDFunc, targetID;
340
+ var getTargetIDFunc, handleEventFunc;
341
if (shouldUseChangeEvent(topLevelTarget)) {
345
- getTargetIDFunc = getTargetIDForChangeEvent;
342
+ if (doesChangeEventBubble) {
343
+ getTargetIDFunc = getTargetIDForChangeEvent;
344
+ } else {
345
+ handleEventFunc = handleEventsForChangeEventIE8;
346
+ }
347
} else if (shouldUseInputEvent(topLevelTarget)) {
347
- getTargetIDFunc = getTargetIDForValueChangeEvent;
348
+ if (isInputEventSupported) {
349
+ getTargetIDFunc = getTargetIDForInputEvent;
350
+ } else {
351
+ handleEventFunc = handleEventsForInputEventIE;
352
+ }
353
} else if (shouldUseClickEvent(topLevelTarget)) {
354
getTargetIDFunc = getTargetIDForClickEvent;
355
}
356
+
357
if (getTargetIDFunc) {
352
- targetID = getTargetIDFunc(
358
+ var targetID = getTargetIDFunc(
359
topLevelType,
360
topLevelTarget,
361
topLevelTargetID
362
);
357
- }
358
-
359
- if (targetID) {
360
- var event = SyntheticEvent.getPooled(
361
- eventTypes.change,
362
- targetID,
363
- nativeEvent
363
+ if (targetID) {
364
+ var event = SyntheticEvent.getPooled(
365
+ eventTypes.change,
366
+ targetID,
367
+ nativeEvent
368
+ );
369
+ EventPropagators.accumulateTwoPhaseDispatches(event);
370
+ return event;
371
+ }
372
+ } else if (handleEventFunc) {
373
+ handleEventFunc(
374
+ topLevelType,
375
+ topLevelTarget,
376
+ topLevelTargetID
377
);
365
- EventPropagators.accumulateTwoPhaseDispatches(event);
366
- return event;
378
}
379
}
380