14
* limitations under the License.
15
*
16
* @providesModule ReactEventEmitter
17
+ * @typechecks
18
*/
19
20
"use strict";
21
22
var BrowserEnv = require('BrowserEnv');
23
var EventConstants = require('EventConstants');
24
+var EventListener = require('EventListener');
25
var EventPluginHub = require('EventPluginHub');
26
var ExecutionEnvironment = require('ExecutionEnvironment');
25
-var NormalizedEventListener = require('NormalizedEventListener');
27
28
var invariant = require('invariant');
29
var isEventSupported = require('isEventSupported');
30
30
-var registrationNames = EventPluginHub.registrationNames;
31
-var topLevelTypes = EventConstants.topLevelTypes;
32
-var listen = NormalizedEventListener.listen;
33
-var capture = NormalizedEventListener.capture;
34
-
31
/**
36
- * `ReactEventEmitter` is used to attach top-level event listeners. For example:
32
+ * Summary of `ReactEventEmitter` event handling:
33
*
38
- * ReactEventEmitter.putListener('myID', 'onClick', myFunction);
34
+ * - We trap low level 'top-level' events.
35
+ *
36
+ * - We dedupe cross-browser event names into these 'top-level types' so that
37
+ * `DOMMouseScroll` or `mouseWheel` both become `topMouseWheel`.
38
+ *
39
+ * - At this point we have native browser events with the top-level type that
40
+ * was used to catch it at the top-level.
41
+ *
42
+ * - We continuously stream these native events (and their respective top-level
43
+ * types) to the event plugin system `EventPluginHub` and ask the plugin
44
+ * system if it was able to extract `AbstractEvent` objects. `AbstractEvent`
45
+ * objects are the events that applications actually deal with - they are not
46
+ * native browser events but cross-browser wrappers.
47
+ *
48
+ * - When returning the `AbstractEvent` objects, `EventPluginHub` will make
49
+ * sure each abstract event is annotated with "dispatches", which are the
50
+ * sequence of listeners (and IDs) that care about the event.
51
+ *
52
+ * - These `AbstractEvent` objects are fed back into the event plugin system,
53
+ * which in turn executes these dispatches.
54
*
40
- * This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
41
- */
42
-
43
-/**
55
* Overview of React and the event system:
56
*
57
* .
83
*/
84
85
/**
75
- * We listen for bubbled touch events on the document object.
76
- *
77
- * Firefox v8.01 (and possibly others) exhibited strange behavior when mounting
78
- * `onmousemove` events at some node that was not the document element. The
79
- * symptoms were that if your mouse is not moving over something contained
80
- * within that mount point (for example on the background) the top-level
81
- * listeners for `onmousemove` won't be called. However, if you register the
82
- * `mousemove` on the document object, then it will of course catch all
83
- * `mousemove`s. This along with iOS quirks, justifies restricting top-level
84
- * listeners to the document object only, at least for these movement types of
85
- * events and possibly all events.
86
- *
87
- * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
88
- *
89
- * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
90
- * they bubble to document.
91
- *
92
- * @see http://www.quirksmode.org/dom/events/keys.html.
86
+ * Whether or not `ensureListening` has been invoked.
87
+ * @type {boolean}
88
+ * @private
89
*/
94
-
90
var _isListening = false;
91
92
/**
98
- * Traps top-level events that bubble. Delegates to the main dispatcher
99
- * `handleTopLevel` after performing some basic normalization via
100
- * `TopLevelCallbackCreator.createTopLevelCallback`.
93
+ * Traps top-level events by using event bubbling.
94
+ *
95
+ * @param {string} topLevelType Record from `EventConstants`.
96
+ * @param {string} handlerBaseName Event name (e.g. "click").
97
+ * @param {DOMEventTarget} element Element on which to attach listener.
98
+ * @internal
99
*/
102
-function trapBubbledEvent(topLevelType, handlerBaseName, onWhat) {
103
- listen(
104
- onWhat,
100
+function trapBubbledEvent(topLevelType, handlerBaseName, element) {
101
+ EventListener.listen(
102
+ element,
103
handlerBaseName,
104
ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
105
topLevelType
109
110
/**
111
* Traps a top-level event by using event capturing.
112
+ *
113
+ * @param {string} topLevelType Record from `EventConstants`.
114
+ * @param {string} handlerBaseName Event name (e.g. "click").
115
+ * @param {DOMEventTarget} element Element on which to attach listener.
116
+ * @internal
117
*/
115
-function trapCapturedEvent(topLevelType, handlerBaseName, onWhat) {
116
- capture(
117
- onWhat,
118
+function trapCapturedEvent(topLevelType, handlerBaseName, element) {
119
+ EventListener.capture(
120
+ element,
121
handlerBaseName,
122
ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
123
topLevelType
126
}
127
128
/**
126
- * Listens to document scroll and window resize events that may change the
127
- * document scroll values. We store those results so as to discourage
128
- * application code from asking the DOM itself which could trigger additional
129
- * reflows.
129
+ * Listens to window scroll and resize events. We cache scroll values so that
130
+ * application code can access them without triggering reflows.
131
+ *
132
+ * NOTE: Scroll events do not bubble.
133
+ *
134
+ * @private
135
+ * @see http://www.quirksmode.org/dom/events/scroll.html
136
*/
131
-function registerDocumentScrollListener() {
132
- listen(window, 'scroll', function(nativeEvent) {
133
- if (nativeEvent.target === window) {
134
- BrowserEnv.refreshAuthoritativeScrollValues();
135
- }
136
- });
137
-}
138
-
139
-function registerDocumentResizeListener() {
140
- listen(window, 'resize', function(nativeEvent) {
141
- if (nativeEvent.target === window) {
142
- BrowserEnv.refreshAuthoritativeScrollValues();
143
- }
144
- });
137
+function registerScrollValueMonitoring() {
138
+ var refresh = BrowserEnv.refreshAuthoritativeScrollValues;
139
+ EventListener.listen(window, 'scroll', refresh);
140
+ EventListener.listen(window, 'resize', refresh);
141
}
142
143
/**
148
- * Summary of `ReactEventEmitter` event handling:
149
- *
150
- * - We trap low level 'top-level' events.
151
- *
152
- * - We dedupe cross-browser event names into these 'top-level types' so that
153
- * `DOMMouseScroll` or `mouseWheel` both become `topMouseWheel`.
154
- *
155
- * - At this point we have native browser events with the top-level type that
156
- * was used to catch it at the top-level.
144
+ * We listen for bubbled touch events on the document object.
145
*
158
- * - We continuously stream these native events (and their respective top-level
159
- * types) to the event plugin system `EventPluginHub` and ask the plugin
160
- * system if it was able to extract `AbstractEvent` objects. `AbstractEvent`
161
- * objects are the events that applications actually deal with - they are not
162
- * native browser events but cross-browser wrappers.
146
+ * Firefox v8.01 (and possibly others) exhibited strange behavior when mounting
147
+ * `onmousemove` events at some node that was not the document element. The
148
+ * symptoms were that if your mouse is not moving over something contained
149
+ * within that mount point (for example on the background) the top-level
150
+ * listeners for `onmousemove` won't be called. However, if you register the
151
+ * `mousemove` on the document object, then it will of course catch all
152
+ * `mousemove`s. This along with iOS quirks, justifies restricting top-level
153
+ * listeners to the document object only, at least for these movement types of
154
+ * events and possibly all events.
155
*
164
- * - When returning the `AbstractEvent` objects, `EventPluginHub` will make
165
- * sure each abstract event is annotated with "dispatches", which are the
166
- * sequence of listeners (and IDs) that care about the event.
156
+ * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
157
*
168
- * - These `AbstractEvent` objects are fed back into the event plugin system,
169
- * which in turn executes these dispatches.
158
+ * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
159
+ * they bubble to document.
160
*
161
+ * @param {boolean} touchNotMouse Listen to touch events instead of mouse.
162
* @private
163
+ * @see http://www.quirksmode.org/dom/events/keys.html.
164
*/
165
function listenAtTopLevel(touchNotMouse) {
166
invariant(
167
!_isListening,
168
'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
169
);
170
+ var topLevelTypes = EventConstants.topLevelTypes;
171
var mountAt = document;
172
180
- registerDocumentScrollListener();
181
- registerDocumentResizeListener();
173
+ registerScrollValueMonitoring();
174
trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
175
trapBubbledEvent(topLevelTypes.topMouseDown, 'mousedown', mountAt);
176
trapBubbledEvent(topLevelTypes.topMouseUp, 'mouseup', mountAt);
218
}
219
220
/**
229
- * This is the heart of `ReactEventEmitter`. It simply streams the top-level
230
- * native events to `EventPluginHub`.
221
+ * `ReactEventEmitter` is used to attach top-level event listeners. For example:
222
+ *
223
+ * ReactEventEmitter.putListener('myID', 'onClick', myFunction);
224
+ *
225
+ * This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
226
*
232
- * @param {object} topLevelType Record from `EventConstants`.
233
- * @param {Event} nativeEvent A Standard Event with fixed `target` property.
234
- * @param {DOMElement} renderedTarget Element of interest to the framework.
235
- * @param {string} renderedTargetID string ID of `renderedTarget`.
227
* @internal
228
*/
238
-function handleTopLevel(
239
- topLevelType,
240
- nativeEvent,
241
- renderedTargetID,
242
- renderedTarget) {
243
- var abstractEvents = EventPluginHub.extractAbstractEvents(
244
- topLevelType,
245
- nativeEvent,
246
- renderedTargetID,
247
- renderedTarget
248
- );
229
+var ReactEventEmitter = {
230
250
- // The event queue being processed in the same cycle allows preventDefault.
251
- EventPluginHub.enqueueAbstractEvents(abstractEvents);
252
- EventPluginHub.processAbstractEventQueue();
253
-}
231
+ /**
232
+ * React references `ReactEventTopLevelCallback` using this property in order
233
+ * to allow dependency injection via `ensureListening`.
234
+ */
235
+ TopLevelCallbackCreator: null,
236
255
-function setEnabled(enabled) {
256
- invariant(
257
- ExecutionEnvironment.canUseDOM,
258
- 'setEnabled(...): Cannot toggle event listening in a Worker thread. This ' +
259
- 'is likely a bug in the framework. Please report immediately.'
260
- );
261
- ReactEventEmitter.TopLevelCallbackCreator.setEnabled(enabled);
262
-}
237
+ /**
238
+ * Ensures that top-level event delegation listeners are installed.
239
+ *
240
+ * There are issues with listening to both touch events and mouse events on
241
+ * the top-level, so we make the caller choose which one to listen to. (If
242
+ * there's a touch top-level listeners, anchors don't receive clicks for some
243
+ * reason, and only in some cases).
244
+ *
245
+ * @param {boolean} touchNotMouse Listen to touch events instead of mouse.
246
+ * @param {object} TopLevelCallbackCreator
247
+ */
248
+ ensureListening: function(touchNotMouse, TopLevelCallbackCreator) {
249
+ invariant(
250
+ ExecutionEnvironment.canUseDOM,
251
+ 'ensureListening(...): Cannot toggle event listening in a Worker ' +
252
+ 'thread. This is likely a bug in the framework. Please report ' +
253
+ 'immediately.'
254
+ );
255
+ if (!_isListening) {
256
+ ReactEventEmitter.TopLevelCallbackCreator = TopLevelCallbackCreator;
257
+ listenAtTopLevel(touchNotMouse);
258
+ _isListening = true;
259
+ }
260
+ },
261
264
-function isEnabled() {
265
- return ReactEventEmitter.TopLevelCallbackCreator.isEnabled();
266
-}
262
+ /**
263
+ * Sets whether or not any created callbacks should be enabled.
264
+ *
265
+ * @param {boolean} enabled True if callbacks should be enabled.
266
+ */
267
+ setEnabled: function(enabled) {
268
+ invariant(
269
+ ExecutionEnvironment.canUseDOM,
270
+ 'setEnabled(...): Cannot toggle event listening in a Worker thread. ' +
271
+ 'This is likely a bug in the framework. Please report immediately.'
272
+ );
273
+ if (ReactEventEmitter.TopLevelCallbackCreator) {
274
+ ReactEventEmitter.TopLevelCallbackCreator.setEnabled(enabled);
275
+ }
276
+ },
277
268
-/**
269
- * Ensures that top-level event delegation listeners are listening at `mountAt`.
270
- * There are issues with listening to both touch events and mouse events on the
271
- * top-level, so we make the caller choose which one to listen to. (If there's a
272
- * touch top-level listeners, anchors don't receive clicks for some reason, and
273
- * only in some cases).
274
- *
275
- * @param {boolean} touchNotMouse Listen to touch events instead of mouse.
276
- * @param {object} TopLevelCallbackCreator Module that can create top-level
277
- * callback handlers.
278
- * @internal
279
- */
280
-function ensureListening(touchNotMouse, TopLevelCallbackCreator) {
281
- invariant(
282
- ExecutionEnvironment.canUseDOM,
283
- 'ensureListening(...): Cannot toggle event listening in a Worker thread. ' +
284
- 'This is likely a bug in the framework. Please report immediately.'
285
- );
286
- if (!_isListening) {
287
- ReactEventEmitter.TopLevelCallbackCreator = TopLevelCallbackCreator;
288
- listenAtTopLevel(touchNotMouse);
289
- _isListening = true;
290
- }
291
-}
278
+ /**
279
+ * @return {boolean} True if callbacks are enabled.
280
+ */
281
+ isEnabled: function() {
282
+ return !!(
283
+ ReactEventEmitter.TopLevelCallbackCreator &&
284
+ ReactEventEmitter.TopLevelCallbackCreator.isEnabled()
285
+ );
286
+ },
287
+
288
+ /**
289
+ * Streams a fired top-level event to `EventPluginHub` where plugins have the
290
+ * opportunity to create `ReactEvent`s to be dispatched.
291
+ *
292
+ * @param {string} topLevelType Record from `EventConstants`.
293
+ * @param {DOMEventTarget} topLevelTarget The listening component root node.
294
+ * @param {string} topLevelTargetID ID of `topLevelTarget`.
295
+ * @param {object} nativeEvent Native browser event.
296
+ */
297
+ handleTopLevel: function(
298
+ topLevelType,
299
+ topLevelTarget,
300
+ topLevelTargetID,
301
+ nativeEvent) {
302
+ var abstractEvents = EventPluginHub.extractAbstractEvents(
303
+ topLevelType,
304
+ topLevelTarget,
305
+ topLevelTargetID,
306
+ nativeEvent
307
+ );
308
+
309
+ // Event queue being processed in the same cycle allows `preventDefault`.
310
+ EventPluginHub.enqueueAbstractEvents(abstractEvents);
311
+ EventPluginHub.processAbstractEventQueue();
312
+ },
313
+
314
+ registrationNames: EventPluginHub.registrationNames,
315
293
-var ReactEventEmitter = {
294
- TopLevelCallbackCreator: null, // Injectable callback creator.
295
- handleTopLevel: handleTopLevel,
296
- setEnabled: setEnabled,
297
- isEnabled: isEnabled,
298
- ensureListening: ensureListening,
299
- registrationNames: registrationNames,
316
putListener: EventPluginHub.putListener,
317
+
318
getListener: EventPluginHub.getListener,
319
+
320
deleteAllListeners: EventPluginHub.deleteAllListeners,
321
+
322
trapBubbledEvent: trapBubbledEvent,
323
+
324
trapCapturedEvent: trapCapturedEvent
325
+
326
};
327
328
module.exports = ReactEventEmitter;