Listen to events on demand

Fixes #381

This is a squashed version of https://github.com/facebook/react/pull/462
This commit is contained in:
SanderSpies
2014-01-03 23:09:59 -08:00
committed by Paul O’Shannessy
parent a575e93ecd
commit 80d7d2d0f8
18 changed files with 313 additions and 234 deletions
+2 -1
View File
@@ -19,6 +19,7 @@
"use strict";
var DOMPropertyOperations = require('DOMPropertyOperations');
var EventPluginUtils = require('EventPluginUtils');
var ReactComponent = require('ReactComponent');
var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactContext = require('ReactContext');
@@ -40,7 +41,7 @@ var React = {
DOM: ReactDOM,
PropTypes: ReactPropTypes,
initializeTouchEvents: function(shouldUseTouch) {
ReactMount.useTouchEvents = shouldUseTouch;
EventPluginUtils.useTouchEvents = shouldUseTouch;
},
createClass: ReactCompositeComponent.createClass,
constructAndRenderComponent: ReactMount.constructAndRenderComponent,
+21 -5
View File
@@ -24,6 +24,7 @@ var DOMProperty = require('DOMProperty');
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactComponent = require('ReactComponent');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactMount = require('ReactMount');
var ReactMultiChild = require('ReactMultiChild');
var ReactPerf = require('ReactPerf');
@@ -33,15 +34,17 @@ var keyOf = require('keyOf');
var merge = require('merge');
var mixInto = require('mixInto');
var putListener = ReactEventEmitter.putListener;
var deleteListener = ReactEventEmitter.deleteListener;
var registrationNames = ReactEventEmitter.registrationNames;
var listenTo = ReactEventEmitter.listenTo;
var registrationNameModules = ReactEventEmitter.registrationNameModules;
// For quickly matching children type, to test if can be treated as content.
var CONTENT_TYPES = {'string': true, 'number': true};
var STYLE = keyOf({style: null});
var ELEMENT_NODE_TYPE = 1;
/**
* @param {?object} props
*/
@@ -61,6 +64,19 @@ function assertValidProps(props) {
);
}
function putListener(id, registrationName, listener) {
var container = ReactMount.findReactContainerForID(id);
if (container) {
var doc = container.nodeType === ELEMENT_NODE_TYPE ?
container.ownerDocument :
container;
listenTo(registrationName, doc);
}
ReactEventEmitter.putListener(id, registrationName, listener);
}
/**
* @constructor ReactDOMComponent
* @extends ReactComponent
@@ -126,7 +142,7 @@ ReactDOMComponent.Mixin = {
if (propValue == null) {
continue;
}
if (registrationNames[propKey]) {
if (registrationNameModules[propKey]) {
putListener(this._rootNodeID, propKey, propValue);
} else {
if (propKey === STYLE) {
@@ -243,7 +259,7 @@ ReactDOMComponent.Mixin = {
styleUpdates[styleName] = '';
}
}
} else if (registrationNames[propKey]) {
} else if (registrationNameModules[propKey]) {
deleteListener(this._rootNodeID, propKey);
} else if (
DOMProperty.isStandardName[propKey] ||
@@ -285,7 +301,7 @@ ReactDOMComponent.Mixin = {
// Relies on `updateStylesByID` not mutating `styleUpdates`.
styleUpdates = nextProp;
}
} else if (registrationNames[propKey]) {
} else if (registrationNameModules[propKey]) {
putListener(this._rootNodeID, propKey, nextProp);
} else if (
DOMProperty.isStandardName[propKey] ||
+123 -139
View File
@@ -22,6 +22,7 @@
var EventConstants = require('EventConstants');
var EventListener = require('EventListener');
var EventPluginHub = require('EventPluginHub');
var EventPluginRegistry = require('EventPluginRegistry');
var ExecutionEnvironment = require('ExecutionEnvironment');
var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
var ViewportMetrics = require('ViewportMetrics');
@@ -75,6 +76,62 @@ var merge = require('merge');
* React Core . General Purpose Event Plugin System
*/
var alreadyListeningTo = {};
var isMonitoringScrollValue = false;
var reactTopListenersCounter = 0;
var topEventMapping = {
topBlur: 'blur',
topChange: 'change',
topClick: 'click',
topCompositionEnd: 'compositionend',
topCompositionStart: 'compositionstart',
topCompositionUpdate: 'compositionupdate',
topContextMenu: 'contextmenu',
topCopy: 'copy',
topCut: 'cut',
topDoubleClick: 'dblclick',
topDrag: 'drag',
topDragEnd: 'dragend',
topDragEnter: 'dragenter',
topDragExit: 'dragexit',
topDragLeave: 'dragleave',
topDragOver: 'dragover',
topDragStart: 'dragstart',
topDrop: 'drop',
topFocus: 'focus',
topInput: 'input',
topKeyDown: 'keydown',
topKeyPress: 'keypress',
topKeyUp: 'keyup',
topMouseDown: 'mousedown',
topMouseMove: 'mousemove',
topMouseOut: 'mouseout',
topMouseOver: 'mouseover',
topMouseUp: 'mouseup',
topPaste: 'paste',
topScroll: 'scroll',
topSelectionChange: 'selectionchange',
topSubmit: 'submit',
topTouchCancel: 'touchcancel',
topTouchEnd: 'touchend',
topTouchMove: 'touchmove',
topTouchStart: 'touchstart',
topWheel: 'wheel'
};
/**
* To ensure no conflicts with other potential React instances on the page
*/
var topListenersIDKey = "_reactListenersID" + String(Math.random()).slice(2);
function getListeningDocument(mountAt) {
if (mountAt[topListenersIDKey] == null) {
mountAt[topListenersIDKey] = reactTopListenersCounter++;
alreadyListeningTo[mountAt[topListenersIDKey]] = {};
}
return alreadyListeningTo[mountAt[topListenersIDKey]];
}
/**
* Traps top-level events by using event bubbling.
*
@@ -111,21 +168,6 @@ function trapCapturedEvent(topLevelType, handlerBaseName, element) {
);
}
/**
* Listens to window scroll and resize events. We cache scroll values so that
* application code can access them without triggering reflows.
*
* NOTE: Scroll events do not bubble.
*
* @private
* @see http://www.quirksmode.org/dom/events/scroll.html
*/
function registerScrollValueMonitoring() {
var refresh = ViewportMetrics.refreshScrollValues;
EventListener.listen(window, 'scroll', refresh);
EventListener.listen(window, 'resize', refresh);
}
/**
* `ReactEventEmitter` is used to attach top-level event listeners. For example:
*
@@ -143,39 +185,6 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
*/
TopLevelCallbackCreator: null,
/**
* Ensures that top-level event delegation listeners are installed.
*
* There are issues with listening to both touch events and mouse events on
* the top-level, so we make the caller choose which one to listen to. (If
* there's a touch top-level listeners, anchors don't receive clicks for some
* reason, and only in some cases).
*
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
* @param {DOMDocument} contentDocument DOM document to listen on
*/
ensureListening: function(touchNotMouse, contentDocument) {
invariant(
ExecutionEnvironment.canUseDOM,
'ensureListening(...): Cannot toggle event listening in a Worker ' +
'thread. This is likely a bug in the framework. Please report ' +
'immediately.'
);
invariant(
ReactEventEmitter.TopLevelCallbackCreator,
'ensureListening(...): Cannot be called without a top level callback ' +
'creator being injected.'
);
// Call out to base implementation.
ReactEventEmitterMixin.ensureListening.call(
ReactEventEmitter,
{
touchNotMouse: touchNotMouse,
contentDocument: contentDocument
}
);
},
/**
* Sets whether or not any created callbacks should be enabled.
*
@@ -220,108 +229,84 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
* Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
* they bubble to document.
*
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
* @param {string} registrationName Name of listener (e.g. `onClick`).
* @param {DOMDocument} contentDocument Document which owns the container
* @private
* @see http://www.quirksmode.org/dom/events/keys.html.
*/
listenAtTopLevel: function(touchNotMouse, contentDocument) {
invariant(
!contentDocument._isListening,
'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
);
var topLevelTypes = EventConstants.topLevelTypes;
listenTo: function(registrationName, contentDocument) {
var mountAt = contentDocument;
var listeningDocument = getListeningDocument(mountAt);
var dependencies = EventPluginRegistry.
registrationNameDependencies[registrationName];
registerScrollValueMonitoring();
trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
trapBubbledEvent(topLevelTypes.topMouseDown, 'mousedown', mountAt);
trapBubbledEvent(topLevelTypes.topMouseUp, 'mouseup', mountAt);
trapBubbledEvent(topLevelTypes.topMouseMove, 'mousemove', mountAt);
trapBubbledEvent(topLevelTypes.topMouseOut, 'mouseout', mountAt);
trapBubbledEvent(topLevelTypes.topClick, 'click', mountAt);
trapBubbledEvent(topLevelTypes.topDoubleClick, 'dblclick', mountAt);
trapBubbledEvent(topLevelTypes.topContextMenu, 'contextmenu', mountAt);
if (touchNotMouse) {
trapBubbledEvent(topLevelTypes.topTouchStart, 'touchstart', mountAt);
trapBubbledEvent(topLevelTypes.topTouchEnd, 'touchend', mountAt);
trapBubbledEvent(topLevelTypes.topTouchMove, 'touchmove', mountAt);
trapBubbledEvent(topLevelTypes.topTouchCancel, 'touchcancel', mountAt);
}
trapBubbledEvent(topLevelTypes.topKeyUp, 'keyup', mountAt);
trapBubbledEvent(topLevelTypes.topKeyPress, 'keypress', mountAt);
trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt);
trapBubbledEvent(topLevelTypes.topInput, 'input', mountAt);
trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt);
trapBubbledEvent(
topLevelTypes.topSelectionChange,
'selectionchange',
mountAt
);
var topLevelTypes = EventConstants.topLevelTypes;
for (var i = 0, l = dependencies.length; i < l; i++) {
var dependency = dependencies[i];
if (!listeningDocument[dependency]) {
var topLevelType = topLevelTypes[dependency];
trapBubbledEvent(
topLevelTypes.topCompositionEnd,
'compositionend',
mountAt
);
trapBubbledEvent(
topLevelTypes.topCompositionStart,
'compositionstart',
mountAt
);
trapBubbledEvent(
topLevelTypes.topCompositionUpdate,
'compositionupdate',
mountAt
);
if (topLevelType === topLevelTypes.topWheel) {
if (isEventSupported('wheel')) {
trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
} else if (isEventSupported('mousewheel')) {
trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt);
} else {
// Firefox needs to capture a different mouse scroll event.
// @see http://www.quirksmode.org/dom/events/tests/scroll.html
trapBubbledEvent(
topLevelTypes.topWheel,
'DOMMouseScroll',
mountAt);
}
} else if (topLevelType === topLevelTypes.topScroll) {
if (isEventSupported('drag')) {
trapBubbledEvent(topLevelTypes.topDrag, 'drag', mountAt);
trapBubbledEvent(topLevelTypes.topDragEnd, 'dragend', mountAt);
trapBubbledEvent(topLevelTypes.topDragEnter, 'dragenter', mountAt);
trapBubbledEvent(topLevelTypes.topDragExit, 'dragexit', mountAt);
trapBubbledEvent(topLevelTypes.topDragLeave, 'dragleave', mountAt);
trapBubbledEvent(topLevelTypes.topDragOver, 'dragover', mountAt);
trapBubbledEvent(topLevelTypes.topDragStart, 'dragstart', mountAt);
trapBubbledEvent(topLevelTypes.topDrop, 'drop', mountAt);
}
if (isEventSupported('scroll', true)) {
trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
} else {
trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
}
} else if (topLevelType === topLevelTypes.topFocus ||
topLevelType === topLevelTypes.topBlur) {
if (isEventSupported('wheel')) {
trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
} else if (isEventSupported('mousewheel')) {
trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt);
} else {
// Firefox needs to capture a different mouse scroll event.
// @see http://www.quirksmode.org/dom/events/tests/scroll.html
trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt);
}
if (isEventSupported('focus', true)) {
trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt);
} else if (isEventSupported('focusin')) {
// IE has `focusin` and `focusout` events which bubble.
// @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt);
trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt);
}
// IE<9 does not support capturing so just trap the bubbled event there.
if (isEventSupported('scroll', true)) {
trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
} else {
trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
}
// to make sure blur and focus event listeners are only attached once
listeningDocument[topLevelTypes.topBlur] = true;
listeningDocument[topLevelTypes.topFocus] = true;
} else {
trapBubbledEvent(topLevelType, topEventMapping[dependency], mountAt);
}
if (isEventSupported('focus', true)) {
trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt);
} else if (isEventSupported('focusin')) {
// IE has `focusin` and `focusout` events which bubble.
// @see
// http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt);
trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt);
}
if (isEventSupported('copy')) {
trapBubbledEvent(topLevelTypes.topCopy, 'copy', mountAt);
trapBubbledEvent(topLevelTypes.topCut, 'cut', mountAt);
trapBubbledEvent(topLevelTypes.topPaste, 'paste', mountAt);
listeningDocument[dependency] = true;
}
}
},
registrationNames: EventPluginHub.registrationNames,
/**
* Listens to window scroll and resize events. We cache scroll values so that
* application code can access them without triggering reflows.
*
* NOTE: Scroll events do not bubble.
*
* @see http://www.quirksmode.org/dom/events/scroll.html
*/
ensureScrollValueMonitoring: function(){
if (!isMonitoringScrollValue) {
var refresh = ViewportMetrics.refreshScrollValues;
EventListener.listen(window, 'scroll', refresh);
EventListener.listen(window, 'resize', refresh);
isMonitoringScrollValue = true;
}
},
registrationNameModules: EventPluginHub.registrationNameModules,
putListener: EventPluginHub.putListener,
@@ -337,5 +322,4 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
});
module.exports = ReactEventEmitter;
-32
View File
@@ -27,38 +27,6 @@ function runEventQueueInBatch(events) {
}
var ReactEventEmitterMixin = {
/**
* Whether or not `ensureListening` has been invoked.
* @type {boolean}
* @private
*/
_isListening: false,
/**
* Function, must be implemented. Listens to events on the top level of the
* application.
*
* @abstract
*
* listenAtTopLevel: null,
*/
/**
* Ensures that top-level event delegation listeners are installed.
*
* There are issues with listening to both touch events and mouse events on
* the top-level, so we make the caller choose which one to listen to. (If
* there's a touch top-level listeners, anchors don't receive clicks for some
* reason, and only in some cases).
*
* @param {*} config Configuration passed through to `listenAtTopLevel`.
*/
ensureListening: function(config) {
if (!config.contentDocument._reactIsListening) {
this.listenAtTopLevel(config.touchNotMouse, config.contentDocument);
config.contentDocument._reactIsListening = true;
}
},
/**
* Streams a fired top-level event to `EventPluginHub` where plugins have the
+11 -23
View File
@@ -208,27 +208,6 @@ var ReactMount = {
renderCallback();
},
/**
* Ensures that the top-level event delegation listener is set up. This will
* be invoked some time before the first time any React component is rendered.
* @param {DOMElement} container container we're rendering into
*
* @private
*/
prepareEnvironmentForDOM: function(container) {
invariant(
container && (
container.nodeType === ELEMENT_NODE_TYPE ||
container.nodeType === DOC_NODE_TYPE
),
'prepareEnvironmentForDOM(...): Target container is not a DOM element.'
);
var doc = container.nodeType === ELEMENT_NODE_TYPE ?
container.ownerDocument :
container;
ReactEventEmitter.ensureListening(ReactMount.useTouchEvents, doc);
},
/**
* Take a component that's already mounted into the DOM and replace its props
* @param {ReactComponent} prevComponent component instance already in the DOM
@@ -256,13 +235,22 @@ var ReactMount = {
},
/**
* Register a component into the instance map and start the events system.
* Register a component into the instance map and starts scroll value
* monitoring
* @param {ReactComponent} nextComponent component instance to render
* @param {DOMElement} container container to render into
* @return {string} reactRoot ID prefix
*/
_registerComponent: function(nextComponent, container) {
ReactMount.prepareEnvironmentForDOM(container);
invariant(
container && (
container.nodeType === ELEMENT_NODE_TYPE ||
container.nodeType === DOC_NODE_TYPE
),
'_registerComponent(...): Target container is not a DOM element.'
);
ReactEventEmitter.ensureScrollValueMonitoring();
var reactRootID = ReactMount.registerContainer(container);
instancesByReactRootID[reactRootID] = nextComponent;
+2 -2
View File
@@ -37,14 +37,14 @@ describe('ReactComponent', function() {
expect(function() {
React.renderComponent(<div></div>, [container]);
}).toThrow(
'Invariant Violation: prepareEnvironmentForDOM(...): Target container ' +
'Invariant Violation: _registerComponent(...): Target container ' +
'is not a DOM element.'
);
expect(function() {
React.renderComponent(<div></div>, null);
}).toThrow(
'Invariant Violation: prepareEnvironmentForDOM(...): Target container ' +
'Invariant Violation: _registerComponent(...): Target container ' +
'is not a DOM element.'
);
});
+42 -1
View File
@@ -41,6 +41,7 @@ var setID = ReactMount.setID;
var ReactEventEmitter;
var ReactTestUtils;
var TapEventPlugin;
var EventListener;
var tapMoveThreshold;
var idCallOrder = [];
@@ -58,6 +59,7 @@ var recordIDAndReturnFalse = function(id, event) {
var LISTENER = mocks.getMockFunction();
var ON_CLICK_KEY = keyOf({onClick: null});
var ON_TOUCH_TAP_KEY = keyOf({onTouchTap: null});
var ON_CHANGE_KEY = keyOf({onChange: null});
/**
@@ -90,13 +92,13 @@ describe('ReactEventEmitter', function() {
EventPluginHub = require('EventPluginHub');
TapEventPlugin = require('TapEventPlugin');
ReactMount = require('ReactMount');
EventListener = require('EventListener');
getID = ReactMount.getID;
setID = ReactMount.setID;
ReactEventEmitter = require('ReactEventEmitter');
ReactTestUtils = require('ReactTestUtils');
idCallOrder = [];
tapMoveThreshold = TapEventPlugin.tapMoveThreshold;
ReactEventEmitter.ensureListening(false, document);
EventPluginHub.injection.injectEventPluginsByName({
TapEventPlugin: TapEventPlugin
});
@@ -315,6 +317,45 @@ describe('ReactEventEmitter', function() {
expect(idCallOrder.length).toBe(0);
});
it('should listen to events only once', function() {
spyOn(EventListener, 'listen');
ReactEventEmitter.listenTo(ON_CLICK_KEY, document);
ReactEventEmitter.listenTo(ON_CLICK_KEY, document);
expect(EventListener.listen.callCount).toBe(1);
});
it('should work with event plugins without dependencies', function() {
spyOn(EventListener, 'listen');
ReactEventEmitter.listenTo(ON_CLICK_KEY, document);
expect(EventListener.listen.argsForCall[0][1]).toBe('click');
});
it('should work with event plugins with dependencies', function() {
spyOn(EventListener, 'listen');
spyOn(EventListener, 'capture');
ReactEventEmitter.listenTo(ON_CHANGE_KEY, document);
var setEventListeners = [];
var listenCalls = EventListener.listen.argsForCall;
var captureCalls = EventListener.capture.argsForCall;
for (var i = 0, l = listenCalls.length; i < l; i++) {
setEventListeners.push(listenCalls[i][1]);
}
for (i = 0, l = captureCalls.length; i < l; i++) {
setEventListeners.push(captureCalls[i][1]);
}
var module = ReactEventEmitter.registrationNameModules[ON_CHANGE_KEY];
var dependencies = module.eventTypes.change.dependencies;
expect(setEventListeners.length).toEqual(dependencies.length);
for (i = 0, l = setEventListeners.length; i < l; i++) {
expect(dependencies.indexOf(setEventListeners[i])).toBeTruthy();
}
});
it('should bubble onTouchTap', function() {
ReactEventEmitter.putListener(
+1 -1
View File
@@ -104,7 +104,7 @@ var EventPluginHub = {
},
registrationNames: EventPluginRegistry.registrationNames,
registrationNameModules: EventPluginRegistry.registrationNameModules,
putListener: CallbackRegistry.putListener,
+21 -14
View File
@@ -63,7 +63,7 @@ function recomputePluginOrdering() {
var publishedEvents = PluginModule.eventTypes;
for (var eventName in publishedEvents) {
invariant(
publishEventForPlugin(publishedEvents[eventName], PluginModule),
publishEventForPlugin(publishedEvents[eventName], PluginModule, eventName),
'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.',
eventName,
pluginName
@@ -80,18 +80,18 @@ function recomputePluginOrdering() {
* @return {boolean} True if the event was successfully published.
* @private
*/
function publishEventForPlugin(dispatchConfig, PluginModule) {
function publishEventForPlugin(dispatchConfig, PluginModule, eventName) {
var phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;
if (phasedRegistrationNames) {
for (var phaseName in phasedRegistrationNames) {
if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
var phasedRegistrationName = phasedRegistrationNames[phaseName];
publishRegistrationName(phasedRegistrationName, PluginModule);
publishRegistrationName(phasedRegistrationName, PluginModule, eventName);
}
}
return true;
} else if (dispatchConfig.registrationName) {
publishRegistrationName(dispatchConfig.registrationName, PluginModule);
publishRegistrationName(dispatchConfig.registrationName, PluginModule, eventName);
return true;
}
return false;
@@ -105,14 +105,16 @@ function publishEventForPlugin(dispatchConfig, PluginModule) {
* @param {object} PluginModule Plugin publishing the event.
* @private
*/
function publishRegistrationName(registrationName, PluginModule) {
function publishRegistrationName(registrationName, PluginModule, eventName) {
invariant(
!EventPluginRegistry.registrationNames[registrationName],
!EventPluginRegistry.registrationNameModules[registrationName],
'EventPluginHub: More than one plugin attempted to publish the same ' +
'registration name, `%s`.',
registrationName
);
EventPluginRegistry.registrationNames[registrationName] = PluginModule;
EventPluginRegistry.registrationNameModules[registrationName] = PluginModule;
EventPluginRegistry.registrationNameDependencies[registrationName] =
PluginModule.eventTypes[eventName].dependencies;
}
/**
@@ -130,7 +132,12 @@ var EventPluginRegistry = {
/**
* Mapping from registration names to plugin modules.
*/
registrationNames: {},
registrationNameModules: {},
/**
* Mapping from registration name to event name
*/
registrationNameDependencies: {},
/**
* Injects an ordering of plugins (by plugin name). This allows the ordering
@@ -194,7 +201,7 @@ var EventPluginRegistry = {
getPluginModuleForEvent: function(event) {
var dispatchConfig = event.dispatchConfig;
if (dispatchConfig.registrationName) {
return EventPluginRegistry.registrationNames[
return EventPluginRegistry.registrationNameModules[
dispatchConfig.registrationName
] || null;
}
@@ -202,7 +209,7 @@ var EventPluginRegistry = {
if (!dispatchConfig.phasedRegistrationNames.hasOwnProperty(phase)) {
continue;
}
var PluginModule = EventPluginRegistry.registrationNames[
var PluginModule = EventPluginRegistry.registrationNameModules[
dispatchConfig.phasedRegistrationNames[phase]
];
if (PluginModule) {
@@ -224,10 +231,10 @@ var EventPluginRegistry = {
}
}
EventPluginRegistry.plugins.length = 0;
var registrationNames = EventPluginRegistry.registrationNames;
for (var registrationName in registrationNames) {
if (registrationNames.hasOwnProperty(registrationName)) {
delete registrationNames[registrationName];
var registrationNameModules = EventPluginRegistry.registrationNameModules;
for (var registrationName in registrationNameModules) {
if (registrationNameModules.hasOwnProperty(registrationName)) {
delete registrationNameModules[registrationName];
}
}
}
+2 -1
View File
@@ -179,7 +179,8 @@ var EventPluginUtils = {
executeDispatchesInOrderStopAtTrue: executeDispatchesInOrderStopAtTrue,
executeDirectDispatch: executeDirectDispatch,
hasDispatches: hasDispatches,
executeDispatch: executeDispatch
executeDispatch: executeDispatch,
useTouchEvents: false
};
module.exports = EventPluginUtils;
@@ -174,16 +174,16 @@ describe('EventPluginRegistry', function() {
EventPluginRegistry.injectEventPluginsByName({one: OnePlugin});
EventPluginRegistry.injectEventPluginOrder(['one', 'two']);
expect(Object.keys(EventPluginRegistry.registrationNames).length).toBe(2);
expect(EventPluginRegistry.registrationNames.onClick).toBe(OnePlugin);
expect(EventPluginRegistry.registrationNames.onFocus).toBe(OnePlugin);
expect(Object.keys(EventPluginRegistry.registrationNameModules).length).toBe(2);
expect(EventPluginRegistry.registrationNameModules.onClick).toBe(OnePlugin);
expect(EventPluginRegistry.registrationNameModules.onFocus).toBe(OnePlugin);
EventPluginRegistry.injectEventPluginsByName({two: TwoPlugin});
expect(Object.keys(EventPluginRegistry.registrationNames).length).toBe(4);
expect(EventPluginRegistry.registrationNames.onMagicBubble).toBe(TwoPlugin);
expect(Object.keys(EventPluginRegistry.registrationNameModules).length).toBe(4);
expect(EventPluginRegistry.registrationNameModules.onMagicBubble).toBe(TwoPlugin);
expect(
EventPluginRegistry.registrationNames.onMagicCapture
EventPluginRegistry.registrationNameModules.onMagicCapture
).toBe(TwoPlugin);
});
+9 -1
View File
@@ -36,7 +36,15 @@ var eventTypes = {
phasedRegistrationNames: {
bubbled: keyOf({onChange: null}),
captured: keyOf({onChangeCapture: null})
}
},
dependencies: [
topLevelTypes.topBlur,
topLevelTypes.topChange,
topLevelTypes.topFocus,
topLevelTypes.topInput,
topLevelTypes.topKeyDown,
topLevelTypes.topKeyUp
]
}
};
+27 -3
View File
@@ -42,19 +42,43 @@ var eventTypes = {
phasedRegistrationNames: {
bubbled: keyOf({onCompositionEnd: null}),
captured: keyOf({onCompositionEndCapture: null})
}
},
dependencies: [
topLevelTypes.topBlur,
topLevelTypes.topCompositionEnd,
topLevelTypes.topKeyDown,
topLevelTypes.topKeyPress,
topLevelTypes.topKeyUp,
topLevelTypes.topMouseDown
]
},
compositionStart: {
phasedRegistrationNames: {
bubbled: keyOf({onCompositionStart: null}),
captured: keyOf({onCompositionStartCapture: null})
}
},
dependencies: [
topLevelTypes.topBlur,
topLevelTypes.topCompositionStart,
topLevelTypes.topKeyDown,
topLevelTypes.topKeyPress,
topLevelTypes.topKeyUp,
topLevelTypes.topMouseDown
]
},
compositionUpdate: {
phasedRegistrationNames: {
bubbled: keyOf({onCompositionUpdate: null}),
captured: keyOf({onCompositionUpdateCapture: null})
}
},
dependencies: [
topLevelTypes.topBlur,
topLevelTypes.topCompositionUpdate,
topLevelTypes.topKeyDown,
topLevelTypes.topKeyPress,
topLevelTypes.topKeyUp,
topLevelTypes.topMouseDown
]
}
};
+14 -2
View File
@@ -30,8 +30,20 @@ var topLevelTypes = EventConstants.topLevelTypes;
var getFirstReactDOM = ReactMount.getFirstReactDOM;
var eventTypes = {
mouseEnter: {registrationName: keyOf({onMouseEnter: null})},
mouseLeave: {registrationName: keyOf({onMouseLeave: null})}
mouseEnter: {
registrationName: keyOf({onMouseEnter: null}),
dependencies: [
topLevelTypes.topMouseOut,
topLevelTypes.topMouseOver
]
},
mouseLeave: {
registrationName: keyOf({onMouseLeave: null}),
dependencies: [
topLevelTypes.topMouseOut,
topLevelTypes.topMouseOver
]
}
};
var extractedEvents = [null, null];
+9 -1
View File
@@ -37,7 +37,15 @@ var eventTypes = {
phasedRegistrationNames: {
bubbled: keyOf({onSelect: null}),
captured: keyOf({onSelectCapture: null})
}
},
dependencies: [
topLevelTypes.topBlur,
topLevelTypes.topFocus,
topLevelTypes.topKeyDown,
topLevelTypes.topMouseDown,
topLevelTypes.topMouseUp,
topLevelTypes.topSelectionChange
]
}
};
+4
View File
@@ -253,6 +253,10 @@ var topLevelEventsToDispatchConfig = {
topWheel: eventTypes.wheel
};
for (var topLevelType in topLevelEventsToDispatchConfig) {
topLevelEventsToDispatchConfig[topLevelType].dependencies = [topLevelType];
}
var SimpleEventPlugin = {
eventTypes: eventTypes,
+19 -1
View File
@@ -19,6 +19,7 @@
"use strict";
var EventConstants = require('EventConstants');
var EventPluginUtils = require('EventPluginUtils');
var EventPropagators = require('EventPropagators');
var SyntheticUIEvent = require('SyntheticUIEvent');
@@ -26,6 +27,7 @@ var TouchEventUtils = require('TouchEventUtils');
var ViewportMetrics = require('ViewportMetrics');
var keyOf = require('keyOf');
var topLevelTypes = EventConstants.topLevelTypes;
var isStartish = EventPluginUtils.isStartish;
var isEndish = EventPluginUtils.isEndish;
@@ -61,12 +63,28 @@ function getDistance(coords, nativeEvent) {
);
}
var dependencies = [
topLevelTypes.topMouseDown,
topLevelTypes.topMouseMove,
topLevelTypes.topMouseUp
];
if (EventPluginUtils.useTouchEvents) {
dependencies.push(
topLevelTypes.topTouchCancel,
topLevelTypes.topTouchEnd,
topLevelTypes.topTouchStart,
topLevelTypes.topTouchMove
);
}
var eventTypes = {
touchTap: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchTap: null}),
captured: keyOf({onTouchTapCapture: null})
}
},
dependencies: dependencies
}
};
@@ -61,7 +61,6 @@ describe('AnalyticsEventPlugin', function() {
'ChangeEventPlugin': ChangeEventPlugin
});
ReactEventEmitter.ensureListening(false, document);
});
it('should count events correctly', function() {