@samitouri / QOS-React-2 / commits / e6b216bdbb

Extract out core ReactEmitter functionality.

Other environments can make use of some of the logic in ReactEventEmitter.

Jordan Walke committed Aug 15, 2013 at 10:42 UTC e6b216bdbbcba4a5e0d4d6200303b34b65d6d046
2 files changed +188 -134
src/core/ReactEventEmitter.js
+103 -134
@@ -23,11 +23,12 @@ var EventConstants = require('EventConstants');
23 var EventListener = require('EventListener');
24 var EventPluginHub = require('EventPluginHub');
25 var ExecutionEnvironment = require('ExecutionEnvironment');
26 -var ReactUpdates = require('ReactUpdates');
26 +var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
27 var ViewportMetrics = require('ViewportMetrics');
28
29 var invariant = require('invariant');
30 var isEventSupported = require('isEventSupported');
31 +var merge = require('merge');
32
33 /**
34 * Summary of `ReactEventEmitter` event handling:
@@ -74,13 +75,6 @@ var isEventSupported = require('isEventSupported');
75 * React Core . General Purpose Event Plugin System
76 */
77
77 -/**
78 - * Whether or not `ensureListening` has been invoked.
79 - * @type {boolean}
80 - * @private
81 - */
82 -var _isListening = false;
83 -
78 /**
79 * Traps top-level events by using event bubbling.
80 *
@@ -132,105 +126,6 @@ function registerScrollValueMonitoring() {
126 EventListener.listen(window, 'resize', refresh);
127 }
128
135 -/**
136 - * We listen for bubbled touch events on the document object.
137 - *
138 - * Firefox v8.01 (and possibly others) exhibited strange behavior when mounting
139 - * `onmousemove` events at some node that was not the document element. The
140 - * symptoms were that if your mouse is not moving over something contained
141 - * within that mount point (for example on the background) the top-level
142 - * listeners for `onmousemove` won't be called. However, if you register the
143 - * `mousemove` on the document object, then it will of course catch all
144 - * `mousemove`s. This along with iOS quirks, justifies restricting top-level
145 - * listeners to the document object only, at least for these movement types of
146 - * events and possibly all events.
147 - *
148 - * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
149 - *
150 - * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
151 - * they bubble to document.
152 - *
153 - * @param {boolean} touchNotMouse Listen to touch events instead of mouse.
154 - * @private
155 - * @see http://www.quirksmode.org/dom/events/keys.html.
156 - */
157 -function listenAtTopLevel(touchNotMouse) {
158 - invariant(
159 - !_isListening,
160 - 'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
161 - );
162 - var topLevelTypes = EventConstants.topLevelTypes;
163 - var mountAt = document;
164 -
165 - registerScrollValueMonitoring();
166 - trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
167 - trapBubbledEvent(topLevelTypes.topMouseDown, 'mousedown', mountAt);
168 - trapBubbledEvent(topLevelTypes.topMouseUp, 'mouseup', mountAt);
169 - trapBubbledEvent(topLevelTypes.topMouseMove, 'mousemove', mountAt);
170 - trapBubbledEvent(topLevelTypes.topMouseOut, 'mouseout', mountAt);
171 - trapBubbledEvent(topLevelTypes.topClick, 'click', mountAt);
172 - trapBubbledEvent(topLevelTypes.topDoubleClick, 'dblclick', mountAt);
173 - if (touchNotMouse) {
174 - trapBubbledEvent(topLevelTypes.topTouchStart, 'touchstart', mountAt);
175 - trapBubbledEvent(topLevelTypes.topTouchEnd, 'touchend', mountAt);
176 - trapBubbledEvent(topLevelTypes.topTouchMove, 'touchmove', mountAt);
177 - trapBubbledEvent(topLevelTypes.topTouchCancel, 'touchcancel', mountAt);
178 - }
179 - trapBubbledEvent(topLevelTypes.topKeyUp, 'keyup', mountAt);
180 - trapBubbledEvent(topLevelTypes.topKeyPress, 'keypress', mountAt);
181 - trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt);
182 - trapBubbledEvent(topLevelTypes.topInput, 'input', mountAt);
183 - trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt);
184 - trapBubbledEvent(
185 - topLevelTypes.topSelectionChange,
186 - 'selectionchange',
187 - mountAt
188 - );
189 - trapBubbledEvent(
190 - topLevelTypes.topDOMCharacterDataModified,
191 - 'DOMCharacterDataModified',
192 - mountAt
193 - );
194 -
195 - if (isEventSupported('drag')) {
196 - trapBubbledEvent(topLevelTypes.topDrag, 'drag', mountAt);
197 - trapBubbledEvent(topLevelTypes.topDragEnd, 'dragend', mountAt);
198 - trapBubbledEvent(topLevelTypes.topDragEnter, 'dragenter', mountAt);
199 - trapBubbledEvent(topLevelTypes.topDragExit, 'dragexit', mountAt);
200 - trapBubbledEvent(topLevelTypes.topDragLeave, 'dragleave', mountAt);
201 - trapBubbledEvent(topLevelTypes.topDragOver, 'dragover', mountAt);
202 - trapBubbledEvent(topLevelTypes.topDragStart, 'dragstart', mountAt);
203 - trapBubbledEvent(topLevelTypes.topDrop, 'drop', mountAt);
204 - }
205 -
206 - if (isEventSupported('wheel')) {
207 - trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
208 - } else if (isEventSupported('mousewheel')) {
209 - trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt);
210 - } else {
211 - // Firefox needs to capture a different mouse scroll event.
212 - // @see http://www.quirksmode.org/dom/events/tests/scroll.html
213 - trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt);
214 - }
215 -
216 - // IE<9 does not support capturing so just trap the bubbled event there.
217 - if (isEventSupported('scroll', true)) {
218 - trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
219 - } else {
220 - trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
221 - }
222 -
223 - if (isEventSupported('focus', true)) {
224 - trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
225 - trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt);
226 - } else if (isEventSupported('focusin')) {
227 - // IE has `focusin` and `focusout` events which bubble.
228 - // @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
229 - trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt);
230 - trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt);
231 - }
232 -}
233 -
129 /**
130 * `ReactEventEmitter` is used to attach top-level event listeners. For example:
131 *
@@ -240,7 +135,7 @@ function listenAtTopLevel(touchNotMouse) {
135 *
136 * @internal
137 */
243 -var ReactEventEmitter = {
138 +var ReactEventEmitter = merge(ReactEventEmitterMixin, {
139
140 /**
141 * React references `ReactEventTopLevelCallback` using this property in order
@@ -270,10 +165,11 @@ var ReactEventEmitter = {
165 'ensureListening(...): Cannot be called without a top level callback ' +
166 'creator being injected.'
167 );
273 - if (!_isListening) {
274 - listenAtTopLevel(touchNotMouse);
275 - _isListening = true;
276 - }
168 + // Call out to base implementation.
169 + ReactEventEmitterMixin.ensureListening.call(
170 + ReactEventEmitter,
171 + touchNotMouse
172 + );
173 },
174
175 /**
@@ -303,31 +199,103 @@ var ReactEventEmitter = {
199 },
200
201 /**
306 - * Streams a fired top-level event to `EventPluginHub` where plugins have the
307 - * opportunity to create `ReactEvent`s to be dispatched.
202 + * We listen for bubbled touch events on the document object.
203 + *
204 + * Firefox v8.01 (and possibly others) exhibited strange behavior when
205 + * mounting `onmousemove` events at some node that was not the document
206 + * element. The symptoms were that if your mouse is not moving over something
207 + * contained within that mount point (for example on the background) the
208 + * top-level listeners for `onmousemove` won't be called. However, if you
209 + * register the `mousemove` on the document object, then it will of course
210 + * catch all `mousemove`s. This along with iOS quirks, justifies restricting
211 + * top-level listeners to the document object only, at least for these
212 + * movement types of events and possibly all events.
213 + *
214 + * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
215 *
309 - * @param {string} topLevelType Record from `EventConstants`.
310 - * @param {DOMEventTarget} topLevelTarget The listening component root node.
311 - * @param {string} topLevelTargetID ID of `topLevelTarget`.
312 - * @param {object} nativeEvent Native browser event.
216 + * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
217 + * they bubble to document.
218 + *
219 + * @param {boolean} touchNotMouse Listen to touch events instead of mouse.
220 + * @private
221 + * @see http://www.quirksmode.org/dom/events/keys.html.
222 */
314 - handleTopLevel: function(
315 - topLevelType,
316 - topLevelTarget,
317 - topLevelTargetID,
318 - nativeEvent) {
319 - var events = EventPluginHub.extractEvents(
320 - topLevelType,
321 - topLevelTarget,
322 - topLevelTargetID,
323 - nativeEvent
223 + listenAtTopLevel: function(touchNotMouse) {
224 + invariant(
225 + !this._isListening,
226 + 'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
227 + );
228 + var topLevelTypes = EventConstants.topLevelTypes;
229 + var mountAt = document;
230 +
231 + registerScrollValueMonitoring();
232 + trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
233 + trapBubbledEvent(topLevelTypes.topMouseDown, 'mousedown', mountAt);
234 + trapBubbledEvent(topLevelTypes.topMouseUp, 'mouseup', mountAt);
235 + trapBubbledEvent(topLevelTypes.topMouseMove, 'mousemove', mountAt);
236 + trapBubbledEvent(topLevelTypes.topMouseOut, 'mouseout', mountAt);
237 + trapBubbledEvent(topLevelTypes.topClick, 'click', mountAt);
238 + trapBubbledEvent(topLevelTypes.topDoubleClick, 'dblclick', mountAt);
239 + if (touchNotMouse) {
240 + trapBubbledEvent(topLevelTypes.topTouchStart, 'touchstart', mountAt);
241 + trapBubbledEvent(topLevelTypes.topTouchEnd, 'touchend', mountAt);
242 + trapBubbledEvent(topLevelTypes.topTouchMove, 'touchmove', mountAt);
243 + trapBubbledEvent(topLevelTypes.topTouchCancel, 'touchcancel', mountAt);
244 + }
245 + trapBubbledEvent(topLevelTypes.topKeyUp, 'keyup', mountAt);
246 + trapBubbledEvent(topLevelTypes.topKeyPress, 'keypress', mountAt);
247 + trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt);
248 + trapBubbledEvent(topLevelTypes.topInput, 'input', mountAt);
249 + trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt);
250 + trapBubbledEvent(
251 + topLevelTypes.topSelectionChange,
252 + 'selectionchange',
253 + mountAt
254 + );
255 + trapBubbledEvent(
256 + topLevelTypes.topDOMCharacterDataModified,
257 + 'DOMCharacterDataModified',
258 + mountAt
259 );
260
326 - // Event queue being processed in the same cycle allows `preventDefault`.
327 - ReactUpdates.batchedUpdates(function() {
328 - EventPluginHub.enqueueEvents(events);
329 - EventPluginHub.processEventQueue();
330 - });
261 + if (isEventSupported('drag')) {
262 + trapBubbledEvent(topLevelTypes.topDrag, 'drag', mountAt);
263 + trapBubbledEvent(topLevelTypes.topDragEnd, 'dragend', mountAt);
264 + trapBubbledEvent(topLevelTypes.topDragEnter, 'dragenter', mountAt);
265 + trapBubbledEvent(topLevelTypes.topDragExit, 'dragexit', mountAt);
266 + trapBubbledEvent(topLevelTypes.topDragLeave, 'dragleave', mountAt);
267 + trapBubbledEvent(topLevelTypes.topDragOver, 'dragover', mountAt);
268 + trapBubbledEvent(topLevelTypes.topDragStart, 'dragstart', mountAt);
269 + trapBubbledEvent(topLevelTypes.topDrop, 'drop', mountAt);
270 + }
271 +
272 + if (isEventSupported('wheel')) {
273 + trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
274 + } else if (isEventSupported('mousewheel')) {
275 + trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt);
276 + } else {
277 + // Firefox needs to capture a different mouse scroll event.
278 + // @see http://www.quirksmode.org/dom/events/tests/scroll.html
279 + trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt);
280 + }
281 +
282 + // IE<9 does not support capturing so just trap the bubbled event there.
283 + if (isEventSupported('scroll', true)) {
284 + trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
285 + } else {
286 + trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
287 + }
288 +
289 + if (isEventSupported('focus', true)) {
290 + trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
291 + trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt);
292 + } else if (isEventSupported('focusin')) {
293 + // IE has `focusin` and `focusout` events which bubble.
294 + // @see
295 + // http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
296 + trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt);
297 + trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt);
298 + }
299 },
300
301 registrationNames: EventPluginHub.registrationNames,
@@ -344,6 +312,7 @@ var ReactEventEmitter = {
312
313 trapCapturedEvent: trapCapturedEvent
314
347 -};
315 +});
316 +
317
318 module.exports = ReactEventEmitter;
src/core/ReactEventEmitterMixin.js new
+85
@@ -0,0 +1,85 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactEventEmitterMixin
17 + */
18 +
19 +var EventPluginHub = require('EventPluginHub');
20 +var ReactUpdates = require('ReactUpdates');
21 +
22 +var ReactEventEmitterMixin = {
23 + /**
24 + * Whether or not `ensureListening` has been invoked.
25 + * @type {boolean}
26 + * @private
27 + */
28 + _isListening: false,
29 +
30 + /**
31 + * Function, must be implemented. Listens to events on the top level of the
32 + * application.
33 + *
34 + * @abstract
35 + *
36 + * listenAtTopLevel: null,
37 + */
38 +
39 + /**
40 + * Ensures that top-level event delegation listeners are installed.
41 + *
42 + * There are issues with listening to both touch events and mouse events on
43 + * the top-level, so we make the caller choose which one to listen to. (If
44 + * there's a touch top-level listeners, anchors don't receive clicks for some
45 + * reason, and only in some cases).
46 + *
47 + * @param {*} Configuration passed through to `listenAtTopLevel`.
48 + */
49 + ensureListening: function(config) {
50 + if (!this._isListening) {
51 + this.listenAtTopLevel(config);
52 + this._isListening = true;
53 + }
54 + },
55 +
56 + /**
57 + * Streams a fired top-level event to `EventPluginHub` where plugins have the
58 + * opportunity to create `ReactEvent`s to be dispatched.
59 + *
60 + * @param {string} topLevelType Record from `EventConstants`.
61 + * @param {object} topLevelTarget The listening component root node.
62 + * @param {string} topLevelTargetID ID of `topLevelTarget`.
63 + * @param {object} nativeEvent Native environment event.
64 + */
65 + handleTopLevel: function(
66 + topLevelType,
67 + topLevelTarget,
68 + topLevelTargetID,
69 + nativeEvent) {
70 + var events = EventPluginHub.extractEvents(
71 + topLevelType,
72 + topLevelTarget,
73 + topLevelTargetID,
74 + nativeEvent
75 + );
76 +
77 + // Event queue being processed in the same cycle allows `preventDefault`.
78 + ReactUpdates.batchedUpdates(function() {
79 + EventPluginHub.enqueueEvents(events);
80 + EventPluginHub.processEventQueue();
81 + });
82 + }
83 +};
84 +
85 +module.exports = ReactEventEmitterMixin;