The library for web and native user interfaces.
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;