The library for web and native user interfaces.
Refactor based on review feedback
Ben Alpert committed
Jun 11, 2013 at 21:51 UTC
6b572b3f253d26fc1d9edf33e3e7327e9fde4f29
1 file changed
+134
-85
src/eventPlugins/TextChangeEventPlugin.js
+134
-85
@@ -38,10 +38,10 @@ var abstractEventTypes = {
38
}
39
};
40
41
-// IE9 claims to support the input event but fails to trigger it when deleting
42
-// text, so we ignore its input events
41
var isInputSupported;
42
if (ExecutionEnvironment.canUseDOM) {
43
+ // IE9 claims to support the input event but fails to trigger it when
44
+ // deleting text, so we ignore its input events
45
isInputSupported = isEventSupported('input') && (
46
!("documentMode" in document) || document.documentMode > 9
47
);
@@ -65,8 +65,10 @@ var activeElementID = null;
65
var activeElementValue = null;
66
var activeElementValueProp = null;
67
68
-// Replacement getter/setter for the `value` property for old IE that gets set
69
-// on the active element
68
+/**
69
+ * (For old IE.) Replacement getter/setter for the `value` property that gets
70
+ * set on the active element.
71
+ */
72
var newValueProp = {
73
get: function() {
74
return activeElementValueProp.get.call(this);
@@ -77,6 +79,47 @@ var newValueProp = {
79
}
80
};
81
82
+/**
83
+ * (For old IE.) Starts tracking propertychange events on the passed-in element
84
+ * and override the value property so that we can distinguish user events from
85
+ * value changes in JS.
86
+ */
87
+var startWatching = function(target, targetID) {
88
+ activeElement = target;
89
+ activeElementID = targetID;
90
+ activeElementValue = target.value;
91
+ activeElementValueProp = Object.getOwnPropertyDescriptor(
92
+ target.constructor.prototype,
93
+ 'value'
94
+ );
95
+
96
+ Object.defineProperty(activeElement, 'value', newValueProp);
97
+ activeElement.attachEvent('onpropertychange', handlePropertyChange);
98
+};
99
+
100
+/**
101
+ * (For old IE.) Removes the event listeners from the currently-tracked element,
102
+ * if any exists.
103
+ */
104
+var stopWatching = function() {
105
+ if (!activeElement) {
106
+ return;
107
+ }
108
+
109
+ // delete restores the original property definition
110
+ delete activeElement.value;
111
+ activeElement.detachEvent('onpropertychange', handlePropertyChange);
112
+
113
+ activeElement = null;
114
+ activeElementID = null;
115
+ activeElementValue = null;
116
+ activeElementValueProp = null;
117
+};
118
+
119
+/**
120
+ * (For old IE.) Handles a propertychange event, sending a textChange event if
121
+ * the value of the active element has changed.
122
+ */
123
var handlePropertyChange = function(nativeEvent) {
124
var value;
125
var abstractEvent;
@@ -92,12 +135,85 @@ var handlePropertyChange = function(nativeEvent) {
135
nativeEvent
136
);
137
EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
138
+
139
+ // If propertychange bubbled, we'd just bind to it like all the other
140
+ // events and have it go through ReactEventTopLevelCallback. Since it
141
+ // doesn't, we manually listen for the propertychange event and so we
142
+ // have to enqueue and process the abstract event manually.
143
EventPluginHub.enqueueAbstractEvents(abstractEvent);
144
EventPluginHub.processAbstractEventQueue();
145
}
146
}
147
};
148
149
+/**
150
+ * If a textChange event should be fired, returns the target's ID.
151
+ */
152
+var targetIDForTextChangeEvent;
153
+if (isInputSupported) {
154
+ targetIDForTextChangeEvent = function(
155
+ topLevelType,
156
+ topLevelTarget,
157
+ topLevelTargetID,
158
+ nativeEvent) {
159
+ if (topLevelType === topLevelTypes.topInput) {
160
+ // In modern browsers (i.e., not IE8 or IE9), the input event is exactly
161
+ // what we want so fall through here and trigger an abstract event...
162
+ if (topLevelTarget.nodeName === 'TEXTAREA') {
163
+ // ...unless it's a textarea, in which case we don't fire an event (so
164
+ // that we have consistency with our old-IE shim).
165
+ return;
166
+ }
167
+ return topLevelTargetID;
168
+ }
169
+ };
170
+} else {
171
+ targetIDForTextChangeEvent = function(
172
+ topLevelType,
173
+ topLevelTarget,
174
+ topLevelTargetID,
175
+ nativeEvent) {
176
+ if (topLevelType === topLevelTypes.topFocus) {
177
+ // In IE8, we can capture almost all .value changes by adding a
178
+ // propertychange handler and looking for events with propertyName
179
+ // equal to 'value'
180
+ // In IE9, propertychange fires for most input events but is buggy and
181
+ // doesn't fire when text is deleted, but conveniently, selectionchange
182
+ // appears to fire in all of the remaining cases so we catch those and
183
+ // forward the event if the value has changed
184
+ // In either case, we don't want to call the event handler if the value
185
+ // is changed from JS so we redefine a setter for `.value` that updates
186
+ // our activeElementValue variable, allowing us to ignore those changes
187
+ if (hasInputCapabilities(topLevelTarget)) {
188
+ // stopWatching() should be a noop here but we call it just in case we
189
+ // missed a blur event somehow.
190
+ stopWatching();
191
+ startWatching(topLevelTarget, topLevelTargetID);
192
+ }
193
+ } else if (topLevelType === topLevelTypes.topBlur) {
194
+ stopWatching();
195
+ } else if (
196
+ topLevelType === topLevelTypes.topSelectionChange ||
197
+ topLevelType === topLevelTypes.topKeyUp ||
198
+ topLevelType === topLevelTypes.topKeyDown) {
199
+ // On the selectionchange event, the target is just document which isn't
200
+ // helpful for us so just check activeElement instead.
201
+ //
202
+ // 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
203
+ // propertychange on the first input event after setting `value` from a
204
+ // script and fires only keydown, keypress, keyup. Catching keyup usually
205
+ // gets it and catching keydown lets us fire an event for the first
206
+ // keystroke if user does a key repeat (it'll be a little delayed: right
207
+ // before the second keystroke). Other input methods (e.g., paste) seem to
208
+ // fire selectionchange normally.
209
+ if (activeElement && activeElement.value !== activeElementValue) {
210
+ activeElementValue = activeElement.value;
211
+ return activeElementID;
212
+ }
213
+ }
214
+ }
215
+}
216
+
217
/**
218
* @param {string} topLevelType Record from `EventConstants`.
219
* @param {DOMEventTarget} topLevelTarget The listening component root node.
@@ -111,89 +227,22 @@ var extractAbstractEvents = function(
227
topLevelTarget,
228
topLevelTargetID,
229
nativeEvent) {
114
- var targetID;
115
-
116
- if (isInputSupported && topLevelType === topLevelTypes.topInput) {
117
- // In modern browsers (i.e., not IE8 or IE9), the input event is exactly
118
- // what we want so fall through here and trigger an abstract event...
119
- if (topLevelTarget.nodeName === 'TEXTAREA') {
120
- // ...unless it's a textarea, in which case we don't fire an event (so
121
- // that we have consistency with our old-IE shim).
122
- return;
123
- }
124
- targetID = topLevelTargetID;
125
-
126
- } else if (!isInputSupported && topLevelType === topLevelTypes.topFocus) {
127
- // In IE8, we can capture almost all .value changes by adding a
128
- // propertychange handler and looking for events with propertyName 'value'
129
- // In IE9, propertychange fires for most input events but is buggy and
130
- // doesn't fire when text is deleted, but conveniently, selectionchange
131
- // appears to fire in all of the remaining cases so we catch those and
132
- // forward the event if the value has changed
133
- // In either case, we don't want to call the event handler if the value is
134
- // changed from JS so we redefine a setter for `.value` that updates our
135
- // activeElementValue variable, allowing us to ignore those changes
136
- if (hasInputCapabilities(topLevelTarget)) {
137
- activeElement = topLevelTarget;
138
- activeElementID = topLevelTargetID;
139
- activeElementValue = topLevelTarget.value;
140
- activeElementValueProp = Object.getOwnPropertyDescriptor(
141
- topLevelTarget.constructor.prototype,
142
- 'value'
143
- );
144
-
145
- Object.defineProperty(topLevelTarget, 'value', newValueProp);
146
- topLevelTarget.attachEvent('onpropertychange', handlePropertyChange);
147
- }
148
- return;
149
-
150
- } else if (!isInputSupported && topLevelType === topLevelTypes.topBlur) {
151
- // If blur is triggered due to element removal, the target is set to the
152
- // <html> element so ignore that and unbind from activeElement instead
153
- if (activeElement) {
154
- // delete restores the original property definition
155
- delete activeElement.value;
156
- activeElement.detachEvent('onpropertychange', handlePropertyChange);
157
-
158
- activeElement = null;
159
- activeElementID = null;
160
- activeElementValue = null;
161
- activeElementValueProp = null;
162
- }
163
- return;
164
-
165
- } else if (!isInputSupported && (
166
- topLevelType === topLevelTypes.topSelectionChange ||
167
- topLevelType === topLevelTypes.topKeyUp ||
168
- topLevelType === topLevelTypes.topKeyDown)) {
169
- // On the selectionchange event, the target is just document which isn't
170
- // helpful for us so just check activeElement instead.
171
- //
172
- // 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
173
- // propertychange on the first input event after setting `value` from a
174
- // script and fires only keydown, keypress, keyup. Catching keyup usually
175
- // gets it and catching keydown lets us fire an event for the first
176
- // keystroke if user does a key repeat (it'll be a little delayed: right
177
- // before the second keystroke). Other input methods (e.g., paste) seem to
178
- // fire selectionchange normally.
179
- if (!activeElement || activeElement.value === activeElementValue) {
180
- return;
181
- }
182
- activeElementValue = activeElement.value;
183
- targetID = activeElementID;
184
-
185
- } else {
186
- return;
187
- }
188
-
189
- // If we've made it to this point, some value change occurred
190
- var abstractEvent = AbstractEvent.getPooled(
191
- abstractEventTypes.textChange,
192
- targetID,
230
+ var targetID = targetIDForTextChangeEvent(
231
+ topLevelType,
232
+ topLevelTarget,
233
+ topLevelTargetID,
234
nativeEvent
235
);
195
- EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
196
- return abstractEvent;
236
+
237
+ if (targetID) {
238
+ var abstractEvent = AbstractEvent.getPooled(
239
+ abstractEventTypes.textChange,
240
+ targetID,
241
+ nativeEvent
242
+ );
243
+ EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
244
+ return abstractEvent;
245
+ }
246
};
247
248
var TextChangeEventPlugin = {