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;