@samitouri / QOS-React-2 / commits / 6b572b3f25
Fork 0

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 = {