@samitouri / QOS-React-2 / commits / 9ceaff7318
Fork 0

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