From 514f9a3ef8c72b0231035a0fdaa7ce592683ed69 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Fri, 1 Nov 2013 15:13:25 +0100 Subject: [PATCH 01/31] #381 - listen to events on demand: Initial work to make sure I have the right idea. --- src/core/ReactDOMComponent.js | 3 + src/core/ReactEventEmitter.js | 110 ++++++++++++------ src/core/ReactEventEmitterMixin.js | 32 ----- src/core/ReactMount.js | 29 ++--- src/core/__tests__/ReactComponent-test.js | 9 +- src/core/__tests__/ReactEventEmitter-test.js | 12 +- src/eventPlugins/ChangeEventPlugin.js | 4 +- src/eventPlugins/TapEventPlugin.js | 10 +- .../__tests__/AnalyticsEventPlugin-test.js | 1 - 9 files changed, 117 insertions(+), 93 deletions(-) diff --git a/src/core/ReactDOMComponent.js b/src/core/ReactDOMComponent.js index 1c37a33cc3db..88ada693e513 100644 --- a/src/core/ReactDOMComponent.js +++ b/src/core/ReactDOMComponent.js @@ -37,6 +37,7 @@ var mixInto = require('mixInto'); var putListener = ReactEventEmitter.putListener; var deleteListener = ReactEventEmitter.deleteListener; var registrationNames = ReactEventEmitter.registrationNames; +var listenTo = ReactEventEmitter.listenTo; // For quickly matching children type, to test if can be treated as content. var CONTENT_TYPES = {'string': true, 'number': true}; @@ -128,6 +129,7 @@ ReactDOMComponent.Mixin = { continue; } if (registrationNames[propKey]) { + listenTo(propKey, ReactMount.findReactContainerForID(this._rootNodeID)); putListener(this._rootNodeID, propKey, propValue); } else { if (propKey === STYLE) { @@ -278,6 +280,7 @@ ReactDOMComponent.Mixin = { styleUpdates = nextProp; } } else if (registrationNames[propKey]) { + listenTo(propKey, ReactMount.findReactContainerForID(this._rootNodeID)); putListener(this._rootNodeID, propKey, nextProp); } else if ( DOMProperty.isStandardName[propKey] || diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index 0721c074f67d..ba4039239507 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -75,6 +75,8 @@ var merge = require('merge'); * React Core . General Purpose Event Plugin System */ +var alreadyListeningTo = {}; + /** * Traps top-level events by using event bubbling. * @@ -143,39 +145,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. * @@ -202,6 +171,81 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { ); }, + /** + * + * @param {string} event + * @param {DOMDocument} contentDocument Document which owns the container + */ + listenTo: function(event, contentDocument) { + var mountAt = contentDocument, + camelCasedEventName = event.substr(2), + camelCasedEventNameAlt = camelCasedEventName.charAt(0).toLowerCase() + + camelCasedEventName.slice(1), + dependencies, + dependency, + i, + l, + topLevelType, + topLevelTypes = EventConstants.topLevelTypes; + + if (!alreadyListeningTo[event]) { + dependencies = ReactEventEmitter.registrationNames[event]. + eventTypes[camelCasedEventNameAlt].dependencies; + + if (dependencies) { + for (i = 0, l = dependencies.length; i < l; i++) { + dependency = dependencies[i]; + ReactEventEmitter.listenTo(dependency, contentDocument); + } + } + else { + topLevelType = topLevelTypes['top' + camelCasedEventName]; + + 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('scroll', true)) { + trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt); + } else { + trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window); + } + } + else if (topLevelType === topLevelTypes.topFocus || + topLevelType === topLevelTypes.topBlur) { + + 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); + } + } + else { + trapBubbledEvent(topLevelType, camelCasedEventName.toLowerCase(), mountAt); + } + + // TODO: add support for exceptions: wheel, scroll, focus, window resize events + // TODO: rename: doubleclick to DblClick + + alreadyListeningTo[event] = true; + } + } + }, + /** * We listen for bubbled touch events on the document object. * diff --git a/src/core/ReactEventEmitterMixin.js b/src/core/ReactEventEmitterMixin.js index 805a091759fd..b4180a2e70ae 100644 --- a/src/core/ReactEventEmitterMixin.js +++ b/src/core/ReactEventEmitterMixin.js @@ -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 diff --git a/src/core/ReactMount.js b/src/core/ReactMount.js index 217411e41b4d..419bd70d0549 100644 --- a/src/core/ReactMount.js +++ b/src/core/ReactMount.js @@ -203,27 +203,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 @@ -257,7 +236,13 @@ var ReactMount = { * @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.' + ); var reactRootID = ReactMount.registerContainer(container); instancesByReactRootID[reactRootID] = nextComponent; diff --git a/src/core/__tests__/ReactComponent-test.js b/src/core/__tests__/ReactComponent-test.js index 95e0882bd5f7..062014ec7d9b 100644 --- a/src/core/__tests__/ReactComponent-test.js +++ b/src/core/__tests__/ReactComponent-test.js @@ -37,14 +37,14 @@ describe('ReactComponent', function() { expect(function() { React.renderComponent(
, [container]); }).toThrow( - 'Invariant Violation: prepareEnvironmentForDOM(...): Target container ' + + 'Invariant Violation: _registerComponent(...): Target container ' + 'is not a DOM element.' ); expect(function() { React.renderComponent(
, null); }).toThrow( - 'Invariant Violation: prepareEnvironmentForDOM(...): Target container ' + + 'Invariant Violation: _registerComponent(...): Target container ' + 'is not a DOM element.' ); }); @@ -208,4 +208,9 @@ describe('ReactComponent', function() { expect(root.refs.switcher.refs.box.refs.boxDiv._mountDepth).toBe(3); expect(root.refs.child.refs.span._mountDepth).toBe(6); }); + + it("should listen to events on demand", function() { + // TODO + change component version + }); + }); diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index 40e81c32b1a6..ae3741252cc8 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -95,7 +95,6 @@ describe('ReactEventEmitter', function() { ReactTestUtils = require('ReactTestUtils'); idCallOrder = []; tapMoveThreshold = TapEventPlugin.tapMoveThreshold; - ReactEventEmitter.ensureListening(false, document); EventPluginHub.injection.injectEventPluginsByName({ TapEventPlugin: TapEventPlugin }); @@ -314,6 +313,17 @@ describe('ReactEventEmitter', function() { expect(idCallOrder.length).toBe(0); }); + it('should listen to events only once', function() { + // TODO: no need to do it multiple times + }); + + it('should work with event plugins without dependencies', function() { + // TODO: defaults are chosen + }); + + it('should listen to event plugin dependencies', function() { + // TODO: dependencies are chosen (so no defaults) + }); it('should bubble onTouchTap', function() { ReactEventEmitter.putListener( diff --git a/src/eventPlugins/ChangeEventPlugin.js b/src/eventPlugins/ChangeEventPlugin.js index ad8fa70f1202..d8a771e5d1f0 100644 --- a/src/eventPlugins/ChangeEventPlugin.js +++ b/src/eventPlugins/ChangeEventPlugin.js @@ -35,7 +35,9 @@ var eventTypes = { phasedRegistrationNames: { bubbled: keyOf({onChange: null}), captured: keyOf({onChangeCapture: null}) - } + }, + + dependencies: ["onInput", "onKeyUp", "onKeyDown", "onFocus", "onBlur"] } }; diff --git a/src/eventPlugins/TapEventPlugin.js b/src/eventPlugins/TapEventPlugin.js index c748da79e72b..1887c94d10df 100644 --- a/src/eventPlugins/TapEventPlugin.js +++ b/src/eventPlugins/TapEventPlugin.js @@ -66,7 +66,15 @@ var eventTypes = { phasedRegistrationNames: { bubbled: keyOf({onTouchTap: null}), captured: keyOf({onTouchTapCapture: null}) - } + }, + dependencies: [ + "onTouchStart", + "onTouchMove", + "onTouchEnd", + "onTouchCancel", + "onMouseDown", + "onMouseMove", + "onMouseUp"] } }; diff --git a/src/eventPlugins/__tests__/AnalyticsEventPlugin-test.js b/src/eventPlugins/__tests__/AnalyticsEventPlugin-test.js index 428352997495..2bf79820ff72 100644 --- a/src/eventPlugins/__tests__/AnalyticsEventPlugin-test.js +++ b/src/eventPlugins/__tests__/AnalyticsEventPlugin-test.js @@ -61,7 +61,6 @@ describe('AnalyticsEventPlugin', function() { 'ChangeEventPlugin': ChangeEventPlugin }); - ReactEventEmitter.ensureListening(false, document); }); it('should count events correctly', function() { From f9523727da811f41784b29143d8628a350ad1080 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Thu, 7 Nov 2013 21:29:52 +0100 Subject: [PATCH 02/31] On demand events are now working. --- src/core/ReactEventEmitter.js | 179 ++++-------------- src/core/ReactMount.js | 5 +- src/eventPlugins/ChangeEventPlugin.js | 8 +- src/eventPlugins/CompositionEventPlugin.js | 30 ++- src/eventPlugins/EnterLeaveEventPlugin.js | 16 +- src/eventPlugins/SelectEventPlugin.js | 10 +- src/eventPlugins/TapEventPlugin.js | 14 +- .../__tests__/ChangeEventPlugin-test.js | 1 + .../__tests__/CompositionEventPlugin-test.js | 1 + .../__tests__/EnterLeaveEventPlugin-test.js | 1 + .../__tests__/SelectEventPlugin-test.js | 1 + .../__tests__/TapEventPlugin-test.js | 1 + 12 files changed, 112 insertions(+), 155 deletions(-) create mode 100644 src/eventPlugins/__tests__/ChangeEventPlugin-test.js create mode 100644 src/eventPlugins/__tests__/CompositionEventPlugin-test.js create mode 100644 src/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js create mode 100644 src/eventPlugins/__tests__/SelectEventPlugin-test.js create mode 100644 src/eventPlugins/__tests__/TapEventPlugin-test.js diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index ba4039239507..d24628a7b503 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -76,6 +76,7 @@ var merge = require('merge'); */ var alreadyListeningTo = {}; +var isMonitoringScrollValue = false; /** * Traps top-level events by using event bubbling. @@ -113,21 +114,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: * @@ -172,21 +158,37 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { }, /** + * We listen for bubbled touch events on the document object. + * + * Firefox v8.01 (and possibly others) exhibited strange behavior when + * mounting `onmousemove` events at some node that was not the document + * element. The symptoms were that if your mouse is not moving over something + * contained within that mount point (for example on the background) the + * top-level listeners for `onmousemove` won't be called. However, if you + * register the `mousemove` on the document object, then it will of course + * catch all `mousemove`s. This along with iOS quirks, justifies restricting + * top-level listeners to the document object only, at least for these + * movement types of events and possibly all events. + * + * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html + * + * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but + * they bubble to document. * * @param {string} event * @param {DOMDocument} contentDocument Document which owns the container */ listenTo: function(event, contentDocument) { - var mountAt = contentDocument, - camelCasedEventName = event.substr(2), - camelCasedEventNameAlt = camelCasedEventName.charAt(0).toLowerCase() + - camelCasedEventName.slice(1), - dependencies, - dependency, - i, - l, - topLevelType, - topLevelTypes = EventConstants.topLevelTypes; + var mountAt = contentDocument; + var camelCasedEventName = event.substr(2); + var camelCasedEventNameAlt = + camelCasedEventName.charAt(0).toLowerCase() + camelCasedEventName.slice(1); + var dependencies; + var dependency; + var i; + var l; + var topLevelType; + var topLevelTypes = EventConstants.topLevelTypes; if (!alreadyListeningTo[event]) { dependencies = ReactEventEmitter.registrationNames[event]. @@ -235,132 +237,31 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { } } else { + if(camelCasedEventName === 'DoubleClick') { + camelCasedEventName = 'DblClick'; + } trapBubbledEvent(topLevelType, camelCasedEventName.toLowerCase(), mountAt); } - // TODO: add support for exceptions: wheel, scroll, focus, window resize events - // TODO: rename: doubleclick to DblClick - alreadyListeningTo[event] = true; } } }, /** - * We listen for bubbled touch events on the document object. - * - * Firefox v8.01 (and possibly others) exhibited strange behavior when - * mounting `onmousemove` events at some node that was not the document - * element. The symptoms were that if your mouse is not moving over something - * contained within that mount point (for example on the background) the - * top-level listeners for `onmousemove` won't be called. However, if you - * register the `mousemove` on the document object, then it will of course - * catch all `mousemove`s. This along with iOS quirks, justifies restricting - * top-level listeners to the document object only, at least for these - * movement types of events and possibly all events. - * - * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html + * Listens to window scroll and resize events. We cache scroll values so that + * application code can access them without triggering reflows. * - * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but - * they bubble to document. + * NOTE: Scroll events do not bubble. * - * @param {boolean} touchNotMouse Listen to touch events instead of mouse. - * @param {DOMDocument} contentDocument Document which owns the container - * @private - * @see http://www.quirksmode.org/dom/events/keys.html. + * @see http://www.quirksmode.org/dom/events/scroll.html */ - listenAtTopLevel: function(touchNotMouse, contentDocument) { - invariant( - !contentDocument._isListening, - 'listenAtTopLevel(...): Cannot setup top-level listener more than once.' - ); - var topLevelTypes = EventConstants.topLevelTypes; - var mountAt = contentDocument; - - 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); - 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 - ); - - trapBubbledEvent( - topLevelTypes.topCompositionEnd, - 'compositionend', - mountAt - ); - trapBubbledEvent( - topLevelTypes.topCompositionStart, - 'compositionstart', - mountAt - ); - trapBubbledEvent( - topLevelTypes.topCompositionUpdate, - 'compositionupdate', - mountAt - ); - - 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('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); - } - - // 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); - } - - 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); + ensureScrollValueMonitoring: function(){ + if (!isMonitoringScrollValue) { + var refresh = ViewportMetrics.refreshScrollValues; + EventListener.listen(window, 'scroll', refresh); + EventListener.listen(window, 'resize', refresh); + isMonitoringScrollValue = true; } }, diff --git a/src/core/ReactMount.js b/src/core/ReactMount.js index 71ef76ef4e1c..949ab730aefe 100644 --- a/src/core/ReactMount.js +++ b/src/core/ReactMount.js @@ -231,7 +231,7 @@ 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 @@ -245,6 +245,9 @@ var ReactMount = { '_registerComponent(...): Target container is not a DOM element.' ); + // TODO: add unit tests + ReactEventEmitter.ensureScrollValueMonitoring(); + var reactRootID = ReactMount.registerContainer(container); instancesByReactRootID[reactRootID] = nextComponent; return reactRootID; diff --git a/src/eventPlugins/ChangeEventPlugin.js b/src/eventPlugins/ChangeEventPlugin.js index d8a771e5d1f0..ec90d6dd94d5 100644 --- a/src/eventPlugins/ChangeEventPlugin.js +++ b/src/eventPlugins/ChangeEventPlugin.js @@ -36,8 +36,12 @@ var eventTypes = { bubbled: keyOf({onChange: null}), captured: keyOf({onChangeCapture: null}) }, - - dependencies: ["onInput", "onKeyUp", "onKeyDown", "onFocus", "onBlur"] + dependencies: [ + keyOf({onInput: null}), + keyOf({onKeyUp: null}), + keyOf({onKeyDown: null}), + keyOf({onFocus: null}), + keyOf({onBlur: null})] } }; diff --git a/src/eventPlugins/CompositionEventPlugin.js b/src/eventPlugins/CompositionEventPlugin.js index 9387cd2c4894..d5da7a21087e 100644 --- a/src/eventPlugins/CompositionEventPlugin.js +++ b/src/eventPlugins/CompositionEventPlugin.js @@ -42,19 +42,43 @@ var eventTypes = { phasedRegistrationNames: { bubbled: keyOf({onCompositionEnd: null}), captured: keyOf({onCompositionEndCapture: null}) - } + }, + dependencies: [ + keyOf({onCompositionEnd: null}), + keyOf({onKeyDown: null}), + keyOf({onKeyUp: null}), + keyOf({onKeyPress: null}), + keyOf({onMouseDown: null}), + keyOf({onBlur: null}) + ] }, compositionStart: { phasedRegistrationNames: { bubbled: keyOf({onCompositionStart: null}), captured: keyOf({onCompositionStartCapture: null}) - } + }, + dependencies: [ + keyOf({onCompositionStart: null}), + keyOf({onKeyDown: null}), + keyOf({onKeyUp: null}), + keyOf({onKeyPress: null}), + keyOf({onMouseDown: null}), + keyOf({onBlur: null}) + ] }, compositionUpdate: { phasedRegistrationNames: { bubbled: keyOf({onCompositionUpdate: null}), captured: keyOf({onCompositionUpdateCapture: null}) - } + }, + dependencies: [ + keyOf({onCompositionUpdate: null}), + keyOf({onKeyDown: null}), + keyOf({onKeyUp: null}), + keyOf({onKeyPress: null}), + keyOf({onMouseDown: null}), + keyOf({onBlur: null}) + ] } }; diff --git a/src/eventPlugins/EnterLeaveEventPlugin.js b/src/eventPlugins/EnterLeaveEventPlugin.js index d11df76d4e61..000ee4286c7f 100644 --- a/src/eventPlugins/EnterLeaveEventPlugin.js +++ b/src/eventPlugins/EnterLeaveEventPlugin.js @@ -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: [ + keyOf({onMouseOut: null}), + keyOf({onMouseOver: null}) + ] + }, + mouseLeave: { + registrationName: keyOf({onMouseLeave: null}), + dependencies: [ + keyOf({onMouseOut: null}), + keyOf({onMouseOver: null}) + ] + } }; var extractedEvents = [null, null]; diff --git a/src/eventPlugins/SelectEventPlugin.js b/src/eventPlugins/SelectEventPlugin.js index ed164baa6624..de9a3a372137 100644 --- a/src/eventPlugins/SelectEventPlugin.js +++ b/src/eventPlugins/SelectEventPlugin.js @@ -37,7 +37,15 @@ var eventTypes = { phasedRegistrationNames: { bubbled: keyOf({onSelect: null}), captured: keyOf({onSelectCapture: null}) - } + }, + dependencies: [ + keyOf({onBlur: null}), + keyOf({onFocus: null}), + keyOf({onMouseUp: null}), + keyOf({onMouseDown: null}), + keyOf({onSelectionChange: null}), + keyOf({onKeyDown: null}) + ] } }; diff --git a/src/eventPlugins/TapEventPlugin.js b/src/eventPlugins/TapEventPlugin.js index 1887c94d10df..8730353f013a 100644 --- a/src/eventPlugins/TapEventPlugin.js +++ b/src/eventPlugins/TapEventPlugin.js @@ -68,13 +68,13 @@ var eventTypes = { captured: keyOf({onTouchTapCapture: null}) }, dependencies: [ - "onTouchStart", - "onTouchMove", - "onTouchEnd", - "onTouchCancel", - "onMouseDown", - "onMouseMove", - "onMouseUp"] + keyOf({onTouchStart: null}), + keyOf({onTouchMove: null}), + keyOf({onTouchEnd: null}), + keyOf({onTouchCancel: null}), + keyOf({onMouseDown: null}), + keyOf({onMouseMove: null}), + keyOf({onMouseUp: null})] } }; diff --git a/src/eventPlugins/__tests__/ChangeEventPlugin-test.js b/src/eventPlugins/__tests__/ChangeEventPlugin-test.js new file mode 100644 index 000000000000..5d252e57caba --- /dev/null +++ b/src/eventPlugins/__tests__/ChangeEventPlugin-test.js @@ -0,0 +1 @@ +// TODO: make sure correct dependencies are being used diff --git a/src/eventPlugins/__tests__/CompositionEventPlugin-test.js b/src/eventPlugins/__tests__/CompositionEventPlugin-test.js new file mode 100644 index 000000000000..5d252e57caba --- /dev/null +++ b/src/eventPlugins/__tests__/CompositionEventPlugin-test.js @@ -0,0 +1 @@ +// TODO: make sure correct dependencies are being used diff --git a/src/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js b/src/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js new file mode 100644 index 000000000000..5d252e57caba --- /dev/null +++ b/src/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js @@ -0,0 +1 @@ +// TODO: make sure correct dependencies are being used diff --git a/src/eventPlugins/__tests__/SelectEventPlugin-test.js b/src/eventPlugins/__tests__/SelectEventPlugin-test.js new file mode 100644 index 000000000000..5d252e57caba --- /dev/null +++ b/src/eventPlugins/__tests__/SelectEventPlugin-test.js @@ -0,0 +1 @@ +// TODO: make sure correct dependencies are being used diff --git a/src/eventPlugins/__tests__/TapEventPlugin-test.js b/src/eventPlugins/__tests__/TapEventPlugin-test.js new file mode 100644 index 000000000000..5d252e57caba --- /dev/null +++ b/src/eventPlugins/__tests__/TapEventPlugin-test.js @@ -0,0 +1 @@ +// TODO: make sure correct dependencies are being used From 104a2498e4a13e3dac8beebc41f42133329af431 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Tue, 19 Nov 2013 23:43:22 -0800 Subject: [PATCH 03/31] Make ReactEventTopLevelCallback-test pass This module-level mock() seems to have been interfering with other tests (26 specs failing). We don't have any other tests that do a module-level mock() so I'm going to assume it isn't supported in the open-source test runner right now. I changed it so only ReactEventEmitter.handleTopLevel is mocked; doing so made ReactEventEmitter complain that TopLevelCallbackCreator wasn't defined so I switched the ReactMount references to use React directly so that ReactDefaultInjection would kick in properly. With this, all the tests pass. --- .../ReactEventTopLevelCallback-test.js | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/core/__tests__/ReactEventTopLevelCallback-test.js b/src/core/__tests__/ReactEventTopLevelCallback-test.js index 3b63e1513188..8252263e2839 100644 --- a/src/core/__tests__/ReactEventTopLevelCallback-test.js +++ b/src/core/__tests__/ReactEventTopLevelCallback-test.js @@ -22,23 +22,28 @@ require('mock-modules') .dontMock('ReactEventTopLevelCallback') .dontMock('ReactMount') .dontMock('ReactInstanceHandles') - .dontMock('ReactDOM') - .mock('ReactEventEmitter'); + .dontMock('ReactDOM'); var EVENT_TARGET_PARAM = 1; describe('ReactEventTopLevelCallback', function() { + var mocks; + + var React; var ReactEventTopLevelCallback; - var ReactMount; var ReactDOM; - var ReactEventEmitter; // mocked + var ReactEventEmitter; beforeEach(function() { require('mock-modules').dumpCache(); + mocks = require('mocks'); + + React = require('React'); ReactEventTopLevelCallback = require('ReactEventTopLevelCallback'); - ReactMount = require('ReactMount'); ReactDOM = require('ReactDOM'); - ReactEventEmitter = require('ReactEventEmitter'); // mocked + ReactEventEmitter = require('ReactEventEmitter'); + + ReactEventEmitter.handleTopLevel = mocks.getMockFunction(); }); describe('Propagation', function() { @@ -47,8 +52,8 @@ describe('ReactEventTopLevelCallback', function() { var childControl = ReactDOM.div({}, 'Child'); var parentContainer = document.createElement('div'); var parentControl = ReactDOM.div({}, 'Parent'); - ReactMount.renderComponent(childControl, childContainer); - ReactMount.renderComponent(parentControl, parentContainer); + React.renderComponent(childControl, childContainer); + React.renderComponent(parentControl, parentContainer); parentControl.getDOMNode().appendChild(childContainer); var callback = ReactEventTopLevelCallback.createTopLevelCallback('test'); @@ -69,9 +74,9 @@ describe('ReactEventTopLevelCallback', function() { var parentControl = ReactDOM.div({}, 'Parent'); var grandParentContainer = document.createElement('div'); var grandParentControl = ReactDOM.div({}, 'Parent'); - ReactMount.renderComponent(childControl, childContainer); - ReactMount.renderComponent(parentControl, parentContainer); - ReactMount.renderComponent(grandParentControl, grandParentContainer); + React.renderComponent(childControl, childContainer); + React.renderComponent(parentControl, parentContainer); + React.renderComponent(grandParentControl, grandParentContainer); parentControl.getDOMNode().appendChild(childContainer); grandParentControl.getDOMNode().appendChild(parentContainer); @@ -95,7 +100,7 @@ describe('ReactEventTopLevelCallback', function() { var control = ReactDOM.div({}, [ ReactDOM.div({id: 'outer'}, inner) ]); - ReactMount.renderComponent(control, container); + React.renderComponent(control, container); var callback = ReactEventTopLevelCallback.createTopLevelCallback('test'); callback({ From 8ef50490826d12429df314f1be4d366a4e944ceb Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Wed, 20 Nov 2013 22:50:35 +0100 Subject: [PATCH 04/31] - Implemented tests - Implemented EventPlugin dependencies - Moved supportTouch to EventPlugin level --- src/core/React.js | 3 +- src/core/ReactEventEmitter.js | 26 +++++--- src/core/__tests__/ReactEventEmitter-test.js | 42 +++++++++++-- src/event/EventPluginUtils.js | 3 +- src/eventPlugins/ChangeEventPlugin.js | 3 +- src/eventPlugins/TapEventPlugin.js | 24 +++++--- .../__tests__/ChangeEventPlugin-test.js | 36 ++++++++++- .../__tests__/CompositionEventPlugin-test.js | 59 ++++++++++++++++++- .../__tests__/EnterLeaveEventPlugin-test.js | 40 ++++++++++++- .../__tests__/SelectEventPlugin-test.js | 37 +++++++++++- .../__tests__/TapEventPlugin-test.js | 57 +++++++++++++++++- 11 files changed, 302 insertions(+), 28 deletions(-) diff --git a/src/core/React.js b/src/core/React.js index 40b3321535e0..42bcbaa6e5f3 100644 --- a/src/core/React.js +++ b/src/core/React.js @@ -32,6 +32,7 @@ var ReactPerf = require('ReactPerf'); var ReactPropTypes = require('ReactPropTypes'); var ReactServerRendering = require('ReactServerRendering'); var ReactTextComponent = require('ReactTextComponent'); +var EventPluginUtils = require('EventPluginUtils'); ReactDefaultInjection.inject(); @@ -39,7 +40,7 @@ var React = { DOM: ReactDOM, PropTypes: ReactPropTypes, initializeTouchEvents: function(shouldUseTouch) { - ReactMount.useTouchEvents = shouldUseTouch; + EventPluginUtils.supportTouch = shouldUseTouch; }, createClass: ReactCompositeComponent.createClass, constructAndRenderComponent: ReactMount.constructAndRenderComponent, diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index d24628a7b503..9d9298d8520f 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -29,6 +29,7 @@ var ViewportMetrics = require('ViewportMetrics'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); var merge = require('merge'); +var keyOf = require('keyOf'); /** * Summary of `ReactEventEmitter` event handling: @@ -179,21 +180,27 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { * @param {DOMDocument} contentDocument Document which owns the container */ listenTo: function(event, contentDocument) { - var mountAt = contentDocument; - var camelCasedEventName = event.substr(2); - var camelCasedEventNameAlt = - camelCasedEventName.charAt(0).toLowerCase() + camelCasedEventName.slice(1); + var mountAt; + var camelCasedEventName; + var camelCasedEventNameAlt; var dependencies; var dependency; var i; var l; var topLevelType; - var topLevelTypes = EventConstants.topLevelTypes; + var topLevelTypes; + var registration; if (!alreadyListeningTo[event]) { - dependencies = ReactEventEmitter.registrationNames[event]. + mountAt = contentDocument; + camelCasedEventName = event.substr(2); + camelCasedEventNameAlt = + camelCasedEventName.charAt(0).toLowerCase() + camelCasedEventName.slice(1); + registration = ReactEventEmitter.registrationNames[event]; + if (registration && registration.eventTypes[camelCasedEventNameAlt]) { + dependencies = registration. eventTypes[camelCasedEventNameAlt].dependencies; - + } if (dependencies) { for (i = 0, l = dependencies.length; i < l; i++) { dependency = dependencies[i]; @@ -201,6 +208,7 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { } } else { + topLevelTypes = EventConstants.topLevelTypes; topLevelType = topLevelTypes['top' + camelCasedEventName]; if (topLevelType === topLevelTypes.topWheel) { @@ -235,6 +243,10 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt); trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt); } + + // to make sure event listeners are only attached once + alreadyListeningTo[keyOf({onBlur: null})] = true; + alreadyListeningTo[keyOf({onFocus: null})] = true; } else { if(camelCasedEventName === 'DoubleClick') { diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index ae3741252cc8..81dbba8252e9 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -41,6 +41,7 @@ var setID = ReactMount.setID; var ReactEventEmitter; var ReactTestUtils; var TapEventPlugin; +var EventListener; var tapMoveThreshold; var idCallOrder = []; @@ -58,7 +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}); /** * Since `ReactEventEmitter` is fairly well separated from the DOM, we can test @@ -89,6 +90,7 @@ describe('ReactEventEmitter', function() { EventPluginHub = require('EventPluginHub'); TapEventPlugin = require('TapEventPlugin'); ReactMount = require('ReactMount'); + EventListener = require('EventListener'); getID = ReactMount.getID; setID = ReactMount.setID; ReactEventEmitter = require('ReactEventEmitter'); @@ -314,15 +316,45 @@ describe('ReactEventEmitter', function() { }); it('should listen to events only once', function() { - // TODO: no need to do it multiple times + 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() { - // TODO: defaults are chosen + spyOn(EventListener, 'listen'); + + ReactEventEmitter.listenTo(ON_CLICK_KEY, document); + + expect(EventListener.listen.argsForCall[0][1]).toBe('click'); }); - it('should listen to event plugin dependencies', function() { - // TODO: dependencies are chosen (so no defaults) + 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 dependencies = + ReactEventEmitter.registrationNames[ON_CHANGE_KEY].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() { diff --git a/src/event/EventPluginUtils.js b/src/event/EventPluginUtils.js index c9d063f6206b..ab5faaf6b36b 100644 --- a/src/event/EventPluginUtils.js +++ b/src/event/EventPluginUtils.js @@ -179,7 +179,8 @@ var EventPluginUtils = { executeDispatchesInOrderStopAtTrue: executeDispatchesInOrderStopAtTrue, executeDirectDispatch: executeDirectDispatch, hasDispatches: hasDispatches, - executeDispatch: executeDispatch + executeDispatch: executeDispatch, + supportTouch: false }; module.exports = EventPluginUtils; diff --git a/src/eventPlugins/ChangeEventPlugin.js b/src/eventPlugins/ChangeEventPlugin.js index ec90d6dd94d5..bb7648485271 100644 --- a/src/eventPlugins/ChangeEventPlugin.js +++ b/src/eventPlugins/ChangeEventPlugin.js @@ -41,7 +41,8 @@ var eventTypes = { keyOf({onKeyUp: null}), keyOf({onKeyDown: null}), keyOf({onFocus: null}), - keyOf({onBlur: null})] + keyOf({onBlur: null}) + ] } }; diff --git a/src/eventPlugins/TapEventPlugin.js b/src/eventPlugins/TapEventPlugin.js index 8730353f013a..06b53a06c6d8 100644 --- a/src/eventPlugins/TapEventPlugin.js +++ b/src/eventPlugins/TapEventPlugin.js @@ -61,20 +61,28 @@ function getDistance(coords, nativeEvent) { ); } +var dependencies = [ + keyOf({onMouseDown: null}), + keyOf({onMouseMove: null}), + keyOf({onMouseUp: null}) +]; + +if (EventPluginUtils.supportTouch) { + dependencies = dependencies.concat([ + keyOf({onTouchStart: null}), + keyOf({onTouchMove: null}), + keyOf({onTouchEnd: null}), + keyOf({onTouchCancel: null}) + ]); +} + var eventTypes = { touchTap: { phasedRegistrationNames: { bubbled: keyOf({onTouchTap: null}), captured: keyOf({onTouchTapCapture: null}) }, - dependencies: [ - keyOf({onTouchStart: null}), - keyOf({onTouchMove: null}), - keyOf({onTouchEnd: null}), - keyOf({onTouchCancel: null}), - keyOf({onMouseDown: null}), - keyOf({onMouseMove: null}), - keyOf({onMouseUp: null})] + dependencies: dependencies } }; diff --git a/src/eventPlugins/__tests__/ChangeEventPlugin-test.js b/src/eventPlugins/__tests__/ChangeEventPlugin-test.js index 5d252e57caba..1445521e78cf 100644 --- a/src/eventPlugins/__tests__/ChangeEventPlugin-test.js +++ b/src/eventPlugins/__tests__/ChangeEventPlugin-test.js @@ -1 +1,35 @@ -// TODO: make sure correct dependencies are being used +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @emails react-core + */ + +"use strict"; + +var ChangeEventPlugin = require('ChangeEventPlugin'); +var keyOf = require('keyOf'); + +describe('ChangeEventPlugin', function() { + it('should have dependencies', function() { + expect(ChangeEventPlugin.eventTypes).toBeDefined(); + expect(ChangeEventPlugin.eventTypes.change).toBeDefined(); + expect(ChangeEventPlugin.eventTypes.change.dependencies).toContain(keyOf({onInput: null})); + expect(ChangeEventPlugin.eventTypes.change.dependencies).toContain(keyOf({onKeyUp: null})); + expect(ChangeEventPlugin.eventTypes.change.dependencies).toContain(keyOf({onKeyDown: null})); + expect(ChangeEventPlugin.eventTypes.change.dependencies).toContain(keyOf({onFocus: null})); + expect(ChangeEventPlugin.eventTypes.change.dependencies).toContain(keyOf({onBlur: null})); + expect(ChangeEventPlugin.eventTypes.change.dependencies.length).toBe(5); + }); +}); \ No newline at end of file diff --git a/src/eventPlugins/__tests__/CompositionEventPlugin-test.js b/src/eventPlugins/__tests__/CompositionEventPlugin-test.js index 5d252e57caba..dbb13f89c8ba 100644 --- a/src/eventPlugins/__tests__/CompositionEventPlugin-test.js +++ b/src/eventPlugins/__tests__/CompositionEventPlugin-test.js @@ -1 +1,58 @@ -// TODO: make sure correct dependencies are being used +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @emails react-core + */ + +"use strict"; + +var CompositionEventPlugin = require('CompositionEventPlugin'); +var keyOf = require('keyOf'); + +describe('CompositionEventPlugin', function() { + it('should have dependencies for compositionEnd', function() { + expect(CompositionEventPlugin.eventTypes).toBeDefined(); + expect(CompositionEventPlugin.eventTypes.compositionEnd).toBeDefined(); + expect(CompositionEventPlugin.eventTypes.compositionEnd.dependencies).toContain(keyOf({onKeyDown: null})); + expect(CompositionEventPlugin.eventTypes.compositionEnd.dependencies).toContain(keyOf({onKeyUp: null})); + expect(CompositionEventPlugin.eventTypes.compositionEnd.dependencies).toContain(keyOf({onKeyPress: null})); + expect(CompositionEventPlugin.eventTypes.compositionEnd.dependencies).toContain(keyOf({onMouseDown: null})); + expect(CompositionEventPlugin.eventTypes.compositionEnd.dependencies).toContain(keyOf({onBlur: null})); + expect(CompositionEventPlugin.eventTypes.compositionEnd.dependencies).toContain(keyOf({onCompositionEnd: null})); + expect(CompositionEventPlugin.eventTypes.compositionEnd.dependencies.length).toBe(6); + }); + it('should have dependencies for compositionStart', function() { + expect(CompositionEventPlugin.eventTypes).toBeDefined(); + expect(CompositionEventPlugin.eventTypes.compositionStart).toBeDefined(); + expect(CompositionEventPlugin.eventTypes.compositionStart.dependencies).toContain(keyOf({onKeyDown: null})); + expect(CompositionEventPlugin.eventTypes.compositionStart.dependencies).toContain(keyOf({onKeyUp: null})); + expect(CompositionEventPlugin.eventTypes.compositionStart.dependencies).toContain(keyOf({onKeyPress: null})); + expect(CompositionEventPlugin.eventTypes.compositionStart.dependencies).toContain(keyOf({onMouseDown: null})); + expect(CompositionEventPlugin.eventTypes.compositionStart.dependencies).toContain(keyOf({onBlur: null})); + expect(CompositionEventPlugin.eventTypes.compositionStart.dependencies).toContain(keyOf({onCompositionStart: null})); + expect(CompositionEventPlugin.eventTypes.compositionStart.dependencies.length).toBe(6); + }); + it('should have dependencies for compositionUpdate', function() { + expect(CompositionEventPlugin.eventTypes).toBeDefined(); + expect(CompositionEventPlugin.eventTypes.compositionUpdate).toBeDefined(); + expect(CompositionEventPlugin.eventTypes.compositionUpdate.dependencies).toContain(keyOf({onKeyDown: null})); + expect(CompositionEventPlugin.eventTypes.compositionUpdate.dependencies).toContain(keyOf({onKeyUp: null})); + expect(CompositionEventPlugin.eventTypes.compositionUpdate.dependencies).toContain(keyOf({onKeyPress: null})); + expect(CompositionEventPlugin.eventTypes.compositionUpdate.dependencies).toContain(keyOf({onMouseDown: null})); + expect(CompositionEventPlugin.eventTypes.compositionUpdate.dependencies).toContain(keyOf({onBlur: null})); + expect(CompositionEventPlugin.eventTypes.compositionUpdate.dependencies).toContain(keyOf({onCompositionUpdate: null})); + expect(CompositionEventPlugin.eventTypes.compositionUpdate.dependencies.length).toBe(6); + }); +}); \ No newline at end of file diff --git a/src/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js b/src/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js index 5d252e57caba..1aedbf13f367 100644 --- a/src/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js +++ b/src/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js @@ -1 +1,39 @@ -// TODO: make sure correct dependencies are being used +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @emails react-core + */ + +"use strict"; + +var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin'); +var keyOf = require('keyOf'); + +describe('EnterLeavePlugin', function() { + it('should have dependencies for mouseEnter', function() { + expect(EnterLeaveEventPlugin.eventTypes).toBeDefined(); + expect(EnterLeaveEventPlugin.eventTypes.mouseEnter).toBeDefined(); + expect(EnterLeaveEventPlugin.eventTypes.mouseEnter.dependencies).toContain(keyOf({onMouseOut: null})); + expect(EnterLeaveEventPlugin.eventTypes.mouseEnter.dependencies).toContain(keyOf({onMouseOver: null})); + expect(EnterLeaveEventPlugin.eventTypes.mouseEnter.dependencies.length).toBe(2); + }); + it('should have dependencies for mouseLeave', function() { + expect(EnterLeaveEventPlugin.eventTypes).toBeDefined(); + expect(EnterLeaveEventPlugin.eventTypes.mouseLeave).toBeDefined(); + expect(EnterLeaveEventPlugin.eventTypes.mouseLeave.dependencies).toContain(keyOf({onMouseOut: null})); + expect(EnterLeaveEventPlugin.eventTypes.mouseLeave.dependencies).toContain(keyOf({onMouseOver: null})); + expect(EnterLeaveEventPlugin.eventTypes.mouseLeave.dependencies.length).toBe(2); + }); +}); \ No newline at end of file diff --git a/src/eventPlugins/__tests__/SelectEventPlugin-test.js b/src/eventPlugins/__tests__/SelectEventPlugin-test.js index 5d252e57caba..f440f8cf72ac 100644 --- a/src/eventPlugins/__tests__/SelectEventPlugin-test.js +++ b/src/eventPlugins/__tests__/SelectEventPlugin-test.js @@ -1 +1,36 @@ -// TODO: make sure correct dependencies are being used +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @emails react-core + */ + +"use strict"; + +var SelectEventPlugin = require('SelectEventPlugin'); +var keyOf = require('keyOf'); + +describe('SelectEventPlugin', function() { + it('should have dependencies', function() { + expect(SelectEventPlugin.eventTypes).toBeDefined(); + expect(SelectEventPlugin.eventTypes.select).toBeDefined(); + expect(SelectEventPlugin.eventTypes.select.dependencies).toContain(keyOf({onBlur: null})); + expect(SelectEventPlugin.eventTypes.select.dependencies).toContain(keyOf({onFocus: null})); + expect(SelectEventPlugin.eventTypes.select.dependencies).toContain(keyOf({onMouseUp: null})); + expect(SelectEventPlugin.eventTypes.select.dependencies).toContain(keyOf({onMouseDown: null})); + expect(SelectEventPlugin.eventTypes.select.dependencies).toContain(keyOf({onSelectionChange: null})); + expect(SelectEventPlugin.eventTypes.select.dependencies).toContain(keyOf({onKeyDown: null})); + expect(SelectEventPlugin.eventTypes.select.dependencies.length).toBe(6); + }); +}); \ No newline at end of file diff --git a/src/eventPlugins/__tests__/TapEventPlugin-test.js b/src/eventPlugins/__tests__/TapEventPlugin-test.js index 5d252e57caba..1cf5d791d5c0 100644 --- a/src/eventPlugins/__tests__/TapEventPlugin-test.js +++ b/src/eventPlugins/__tests__/TapEventPlugin-test.js @@ -1 +1,56 @@ -// TODO: make sure correct dependencies are being used +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @emails react-core + */ + +"use strict"; + +var keyOf; +var EventPluginUtils; +var TapEventPlugin; + +beforeEach(function() { + require('mock-modules').dumpCache(); + + keyOf = require('keyOf'); + EventPluginUtils = require('EventPluginUtils'); +}); + +describe('TapEventPlugin', function() { + it('should have default dependencies', function() { + TapEventPlugin = require('TapEventPlugin'); + expect(TapEventPlugin.eventTypes).toBeDefined(); + expect(TapEventPlugin.eventTypes.touchTap).toBeDefined(); + expect(TapEventPlugin.eventTypes.touchTap.dependencies).toContain(keyOf({onMouseDown: null})); + expect(TapEventPlugin.eventTypes.touchTap.dependencies).toContain(keyOf({onMouseMove: null})); + expect(TapEventPlugin.eventTypes.touchTap.dependencies).toContain(keyOf({onMouseUp: null})); + expect(TapEventPlugin.eventTypes.touchTap.dependencies.length).toBe(3); + }); + it('should also have touch dependencies when EventPluginUtils.supportTouch is true', function() { + EventPluginUtils.supportTouch = true; + TapEventPlugin = require('TapEventPlugin'); + expect(TapEventPlugin.eventTypes).toBeDefined(); + expect(TapEventPlugin.eventTypes.touchTap).toBeDefined(); + expect(TapEventPlugin.eventTypes.touchTap.dependencies).toContain(keyOf({onTouchStart: null})); + expect(TapEventPlugin.eventTypes.touchTap.dependencies).toContain(keyOf({onTouchMove: null})); + expect(TapEventPlugin.eventTypes.touchTap.dependencies).toContain(keyOf({onTouchEnd: null})); + expect(TapEventPlugin.eventTypes.touchTap.dependencies).toContain(keyOf({onTouchCancel: null})); + expect(TapEventPlugin.eventTypes.touchTap.dependencies).toContain(keyOf({onMouseDown: null})); + expect(TapEventPlugin.eventTypes.touchTap.dependencies).toContain(keyOf({onMouseMove: null})); + expect(TapEventPlugin.eventTypes.touchTap.dependencies).toContain(keyOf({onMouseUp: null})); + expect(TapEventPlugin.eventTypes.touchTap.dependencies.length).toBe(7); + }); +}); \ No newline at end of file From 3bec9536de57f4bb9fe1b862b3dd806a8ea07fbf Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 00:54:02 +0100 Subject: [PATCH 05/31] Merge branch 'master' of https://github.com/facebook/react into on-demand-events Conflicts: src/core/__tests__/ReactEventTopLevelCallback-test.js --- src/core/React.js | 4 +- src/core/ReactDOMComponent.js | 18 ++++- src/core/ReactEventEmitter.js | 65 +++++++------------ src/core/ReactMount.js | 4 +- src/core/__tests__/ReactComponent-test.js | 4 -- .../__tests__/ReactCompositeComponent-test.js | 2 + src/core/__tests__/ReactEventEmitter-test.js | 6 +- .../ReactEventTopLevelCallback-test.js | 9 ++- .../__tests__/ReactPropTransferer-test.js | 2 + .../__tests__/ReactDOMButton-test.js | 1 + .../__tests__/ReactDOMInput-test.js | 2 +- .../__tests__/ReactDOMSelect-test.js | 2 + .../__tests__/ReactDOMTextarea-test.js | 2 + .../__tests__/ReactServerRendering-test.js | 1 + src/event/EventPluginRegistry.js | 15 +++++ src/event/EventPluginUtils.js | 2 +- 16 files changed, 76 insertions(+), 63 deletions(-) diff --git a/src/core/React.js b/src/core/React.js index 42bcbaa6e5f3..cbf6d93a64c3 100644 --- a/src/core/React.js +++ b/src/core/React.js @@ -18,6 +18,7 @@ "use strict"; +var EventPluginUtils = require('EventPluginUtils'); var ReactComponent = require('ReactComponent'); var ReactCompositeComponent = require('ReactCompositeComponent'); var ReactContext = require('ReactContext'); @@ -32,7 +33,6 @@ var ReactPerf = require('ReactPerf'); var ReactPropTypes = require('ReactPropTypes'); var ReactServerRendering = require('ReactServerRendering'); var ReactTextComponent = require('ReactTextComponent'); -var EventPluginUtils = require('EventPluginUtils'); ReactDefaultInjection.inject(); @@ -40,7 +40,7 @@ var React = { DOM: ReactDOM, PropTypes: ReactPropTypes, initializeTouchEvents: function(shouldUseTouch) { - EventPluginUtils.supportTouch = shouldUseTouch; + EventPluginUtils.useTouchEvents = shouldUseTouch; }, createClass: ReactCompositeComponent.createClass, constructAndRenderComponent: ReactMount.constructAndRenderComponent, diff --git a/src/core/ReactDOMComponent.js b/src/core/ReactDOMComponent.js index e1803cc60fb5..ceb40cc309fc 100644 --- a/src/core/ReactDOMComponent.js +++ b/src/core/ReactDOMComponent.js @@ -44,6 +44,8 @@ var CONTENT_TYPES = {'string': true, 'number': true}; var STYLE = keyOf({style: null}); +var ELEMENT_NODE_TYPE = 1; + /** * @param {?object} props */ @@ -129,7 +131,13 @@ ReactDOMComponent.Mixin = { continue; } if (registrationNames[propKey]) { - listenTo(propKey, ReactMount.findReactContainerForID(this._rootNodeID)); + var container = ReactMount.findReactContainerForID(this._rootNodeID); + if (container) { + var doc = container.nodeType === ELEMENT_NODE_TYPE ? + container.ownerDocument : + container; + listenTo(propKey, doc); + } putListener(this._rootNodeID, propKey, propValue); } else { if (propKey === STYLE) { @@ -289,7 +297,13 @@ ReactDOMComponent.Mixin = { styleUpdates = nextProp; } } else if (registrationNames[propKey]) { - listenTo(propKey, ReactMount.findReactContainerForID(this._rootNodeID)); + var container = ReactMount.findReactContainerForID(this._rootNodeID); + if (container) { + var doc = container.nodeType === ELEMENT_NODE_TYPE ? + container.ownerDocument : + container; + listenTo(propKey, doc); + } putListener(this._rootNodeID, propKey, nextProp); } else if ( DOMProperty.isStandardName[propKey] || diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index 9d9298d8520f..ce9d2b700038 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -25,6 +25,7 @@ var EventPluginHub = require('EventPluginHub'); var ExecutionEnvironment = require('ExecutionEnvironment'); var ReactEventEmitterMixin = require('ReactEventEmitterMixin'); var ViewportMetrics = require('ViewportMetrics'); +var EventPluginRegistry = require('EventPluginRegistry'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); @@ -78,6 +79,7 @@ var keyOf = require('keyOf'); var alreadyListeningTo = {}; var isMonitoringScrollValue = false; +var reactTopListenersCounter = 0; /** * Traps top-level events by using event bubbling. @@ -180,36 +182,20 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { * @param {DOMDocument} contentDocument Document which owns the container */ listenTo: function(event, contentDocument) { - var mountAt; - var camelCasedEventName; - var camelCasedEventNameAlt; - var dependencies; - var dependency; - var i; - var l; - var topLevelType; - var topLevelTypes; - var registration; + var mountAt = contentDocument; + if (!mountAt._reactTopListenersID) { + mountAt._reactTopListenersID = '' + reactTopListenersCounter++; + alreadyListeningTo[mountAt._reactTopListenersID] = []; + } + var topListenersID = mountAt._reactTopListenersID; + if (!alreadyListeningTo[topListenersID][event]) { + var registrationName = ReactEventEmitter.registrationNames[event]; + var dependencies = registrationName.eventTypes[EventPluginRegistry.eventMapping[event]].dependencies; - if (!alreadyListeningTo[event]) { - mountAt = contentDocument; - camelCasedEventName = event.substr(2); - camelCasedEventNameAlt = - camelCasedEventName.charAt(0).toLowerCase() + camelCasedEventName.slice(1); - registration = ReactEventEmitter.registrationNames[event]; - if (registration && registration.eventTypes[camelCasedEventNameAlt]) { - dependencies = registration. - eventTypes[camelCasedEventNameAlt].dependencies; - } - if (dependencies) { - for (i = 0, l = dependencies.length; i < l; i++) { - dependency = dependencies[i]; - ReactEventEmitter.listenTo(dependency, contentDocument); - } - } - else { - topLevelTypes = EventConstants.topLevelTypes; - topLevelType = topLevelTypes['top' + camelCasedEventName]; + var topLevelTypes = EventConstants.topLevelTypes; + for (var i = 0, l = dependencies.length; i < l; i++) { + var dependency = dependencies[i]; + var topLevelType = topLevelTypes[dependency]; if (topLevelType === topLevelTypes.topWheel) { if (isEventSupported('wheel')) { @@ -221,25 +207,22 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { // @see http://www.quirksmode.org/dom/events/tests/scroll.html trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt); } - } - else if(topLevelType === topLevelTypes.topScroll) { + } else if (topLevelType === topLevelTypes.topScroll) { if (isEventSupported('scroll', true)) { trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt); } else { trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window); } - } - else if (topLevelType === topLevelTypes.topFocus || - topLevelType === topLevelTypes.topBlur) { + } else if (topLevelType === topLevelTypes.topFocus || + topLevelType === topLevelTypes.topBlur) { 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 + // @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt); trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt); } @@ -247,15 +230,11 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { // to make sure event listeners are only attached once alreadyListeningTo[keyOf({onBlur: null})] = true; alreadyListeningTo[keyOf({onFocus: null})] = true; - } - else { - if(camelCasedEventName === 'DoubleClick') { - camelCasedEventName = 'DblClick'; - } - trapBubbledEvent(topLevelType, camelCasedEventName.toLowerCase(), mountAt); + } else { + trapBubbledEvent(topLevelType, EventPluginRegistry.eventMapping[event], mountAt); } - alreadyListeningTo[event] = true; + alreadyListeningTo[topListenersID][event] = true; } } }, diff --git a/src/core/ReactMount.js b/src/core/ReactMount.js index 4f7e792262cc..dce8523b575c 100644 --- a/src/core/ReactMount.js +++ b/src/core/ReactMount.js @@ -240,8 +240,8 @@ var ReactMount = { invariant( container && ( container.nodeType === ELEMENT_NODE_TYPE || - container.nodeType === DOC_NODE_TYPE - ), + container.nodeType === DOC_NODE_TYPE + ), '_registerComponent(...): Target container is not a DOM element.' ); diff --git a/src/core/__tests__/ReactComponent-test.js b/src/core/__tests__/ReactComponent-test.js index 062014ec7d9b..4c1b54e64a21 100644 --- a/src/core/__tests__/ReactComponent-test.js +++ b/src/core/__tests__/ReactComponent-test.js @@ -209,8 +209,4 @@ describe('ReactComponent', function() { expect(root.refs.child.refs.span._mountDepth).toBe(6); }); - it("should listen to events on demand", function() { - // TODO + change component version - }); - }); diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index 0afef2dbdaa5..11dd41bd41c0 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -78,6 +78,8 @@ describe('ReactCompositeComponent', function() { ; } }); + + delete document['_reactTopListenersID']; }); it('should support rendering to different child types over time', function() { diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index 81dbba8252e9..04e8de19316e 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -100,6 +100,7 @@ describe('ReactEventEmitter', function() { EventPluginHub.injection.injectEventPluginsByName({ TapEventPlugin: TapEventPlugin }); + delete document['_reactTopListenersID']; }); it('should store a listener correctly', function() { @@ -317,10 +318,8 @@ describe('ReactEventEmitter', function() { 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); }); @@ -338,6 +337,7 @@ describe('ReactEventEmitter', function() { ReactEventEmitter.listenTo(ON_CHANGE_KEY, document); + var SCROLL_MONITORING_EVENTS_NO = 2; var setEventListeners = []; var listenCalls = EventListener.listen.argsForCall; var captureCalls = EventListener.capture.argsForCall; @@ -350,7 +350,7 @@ describe('ReactEventEmitter', function() { var dependencies = ReactEventEmitter.registrationNames[ON_CHANGE_KEY].eventTypes.change.dependencies; - expect(setEventListeners.length).toEqual(dependencies.length); + expect(setEventListeners.length).toEqual(dependencies.length + SCROLL_MONITORING_EVENTS_NO); for(i = 0, l = setEventListeners.length; i < l; i++) { expect(dependencies.indexOf(setEventListeners[i])).toBeTruthy(); diff --git a/src/core/__tests__/ReactEventTopLevelCallback-test.js b/src/core/__tests__/ReactEventTopLevelCallback-test.js index 0d8b7dab48ed..14b02ac1d099 100644 --- a/src/core/__tests__/ReactEventTopLevelCallback-test.js +++ b/src/core/__tests__/ReactEventTopLevelCallback-test.js @@ -22,7 +22,8 @@ require('mock-modules') .dontMock('ReactEventTopLevelCallback') .dontMock('ReactMount') .dontMock('ReactInstanceHandles') - .dontMock('ReactDOM'); + .dontMock('ReactDOM') + .mock('ReactEventEmitter'); var EVENT_TARGET_PARAM = 1; @@ -30,16 +31,14 @@ describe('ReactEventTopLevelCallback', function() { var React; var ReactEventTopLevelCallback; var ReactDOM; - var ReactEventEmitter; + var ReactEventEmitter; // mocked beforeEach(function() { require('mock-modules').dumpCache(); React = require('React'); ReactEventTopLevelCallback = require('ReactEventTopLevelCallback'); ReactDOM = require('ReactDOM'); - ReactEventEmitter = require('ReactEventEmitter'); - - ReactEventEmitter.handleTopLevel = mocks.getMockFunction(); + ReactEventEmitter = require('ReactEventEmitter'); // mocked }); describe('Propagation', function() { diff --git a/src/core/__tests__/ReactPropTransferer-test.js b/src/core/__tests__/ReactPropTransferer-test.js index 1854ef7e959c..76de37f9c6d5 100644 --- a/src/core/__tests__/ReactPropTransferer-test.js +++ b/src/core/__tests__/ReactPropTransferer-test.js @@ -44,6 +44,8 @@ describe('ReactPropTransferer', function() { ); } }); + + delete document['_reactTopListenersID']; }); it('should leave explicitly specified properties intact', function() { diff --git a/src/dom/components/__tests__/ReactDOMButton-test.js b/src/dom/components/__tests__/ReactDOMButton-test.js index 21a000018cb0..43b96122a64d 100644 --- a/src/dom/components/__tests__/ReactDOMButton-test.js +++ b/src/dom/components/__tests__/ReactDOMButton-test.js @@ -49,6 +49,7 @@ describe('ReactDOMButton', function() { beforeEach(function() { React = require('React'); ReactTestUtils = require('ReactTestUtils'); + delete document['_reactTopListenersID']; }); it('should forward clicks when it starts out not disabled', function() { diff --git a/src/dom/components/__tests__/ReactDOMInput-test.js b/src/dom/components/__tests__/ReactDOMInput-test.js index 0ff9ee05f1ad..71a271216f37 100644 --- a/src/dom/components/__tests__/ReactDOMInput-test.js +++ b/src/dom/components/__tests__/ReactDOMInput-test.js @@ -35,7 +35,7 @@ describe('ReactDOMInput', function() { React = require('React'); ReactLink = require('ReactLink'); ReactTestUtils = require('ReactTestUtils'); - + delete document['_reactTopListenersID']; renderTextInput = function(component) { var stub = ReactTestUtils.renderIntoDocument(component); var node = stub.getDOMNode(); diff --git a/src/dom/components/__tests__/ReactDOMSelect-test.js b/src/dom/components/__tests__/ReactDOMSelect-test.js index 6bbe58ca2aa7..db15dc76a1d6 100644 --- a/src/dom/components/__tests__/ReactDOMSelect-test.js +++ b/src/dom/components/__tests__/ReactDOMSelect-test.js @@ -40,6 +40,8 @@ describe('ReactDOMSelect', function() { var node = stub.getDOMNode(); return node; }; + + delete document['_reactTopListenersID']; }); it('should allow setting `defaultValue`', function() { diff --git a/src/dom/components/__tests__/ReactDOMTextarea-test.js b/src/dom/components/__tests__/ReactDOMTextarea-test.js index aaca8ea596b5..3a1760a9d5ab 100644 --- a/src/dom/components/__tests__/ReactDOMTextarea-test.js +++ b/src/dom/components/__tests__/ReactDOMTextarea-test.js @@ -42,6 +42,8 @@ describe('ReactDOMTextarea', function() { node.value = node.innerHTML; return node; }; + + delete document['_reactTopListenersID']; }); it('should allow setting `defaultValue`', function() { diff --git a/src/environment/__tests__/ReactServerRendering-test.js b/src/environment/__tests__/ReactServerRendering-test.js index c208eadc569c..20bce375e936 100644 --- a/src/environment/__tests__/ReactServerRendering-test.js +++ b/src/environment/__tests__/ReactServerRendering-test.js @@ -48,6 +48,7 @@ describe('ReactServerRendering', function() { ExecutionEnvironment.canUseDOM = false; ReactServerRendering = require('ReactServerRendering'); ReactMarkupChecksum = require('ReactMarkupChecksum'); + delete document['_reactTopListenersID']; }); it('should generate simple markup', function() { diff --git a/src/event/EventPluginRegistry.js b/src/event/EventPluginRegistry.js index 14a2fcb024af..6064da643c55 100644 --- a/src/event/EventPluginRegistry.js +++ b/src/event/EventPluginRegistry.js @@ -41,6 +41,7 @@ function recomputePluginOrdering() { // Wait until an `EventPluginOrder` is injected. return; } + for (var pluginName in namesToPlugins) { var PluginModule = namesToPlugins[pluginName]; var pluginIndex = EventPluginOrder.indexOf(pluginName); @@ -62,6 +63,15 @@ function recomputePluginOrdering() { EventPluginRegistry.plugins[pluginIndex] = PluginModule; var publishedEvents = PluginModule.eventTypes; for (var eventName in publishedEvents) { + var phasedRegistrationNames = publishedEvents[eventName].phasedRegistrationNames; + var domEventName; + if (phasedRegistrationNames) { + domEventName = phasedRegistrationNames.bubbled; + } else { + domEventName = publishedEvents[eventName].registrationName; + } + EventPluginRegistry.eventMapping[domEventName] = eventName; + invariant( publishEventForPlugin(publishedEvents[eventName], PluginModule), 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.', @@ -138,6 +148,11 @@ var EventPluginRegistry = { */ registrationNamesKeys: [], + /** + * Mapping from on{EventName} to + */ + eventMapping: {}, + /** * Injects an ordering of plugins (by plugin name). This allows the ordering * to be decoupled from injection of the actual plugins so that ordering is diff --git a/src/event/EventPluginUtils.js b/src/event/EventPluginUtils.js index ab5faaf6b36b..d7bee7510986 100644 --- a/src/event/EventPluginUtils.js +++ b/src/event/EventPluginUtils.js @@ -180,7 +180,7 @@ var EventPluginUtils = { executeDirectDispatch: executeDirectDispatch, hasDispatches: hasDispatches, executeDispatch: executeDispatch, - supportTouch: false + useTouchEvents: false }; module.exports = EventPluginUtils; From d3a880d1af9c09b7f7c1b163bc318a7e15220d9c Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 01:19:07 +0100 Subject: [PATCH 06/31] Merge branch 'master' of https://github.com/facebook/react into on-demand-events Conflicts: src/event/EventPluginRegistry.js --- README.md | 18 ++- docs/_data/nav_tips.yml | 2 - docs/_js/examples/timer.js | 2 +- docs/blog/index.html | 2 +- docs/docs/06-forms.md | 15 -- docs/downloads.md | 4 +- docs/index.md | 20 ++- docs/tips/13-false-in-jsx.md | 1 - .../tips/14-communicate-between-components.md | 40 ----- examples/basic-jsx/index.html | 5 +- .../transitions/ReactTransitionGroup.js | 1 - .../transitions/ReactTransitionKeySet.js | 3 - .../__tests__/ReactTransitionGroup-test.js | 21 +-- .../__tests__/ReactTransitionKeySet-test.js | 26 --- src/core/ReactCompositeComponent.js | 28 ++-- .../__tests__/ReactCompositeComponent-test.js | 20 --- .../__tests__/ReactRenderDocument-test.js | 35 +++- src/event/EventPluginRegistry.js | 7 + .../__tests__/EventPluginRegistry-test.js | 4 +- src/eventPlugins/ChangeEventPlugin.js | 11 +- src/eventPlugins/CompositionEventPlugin.js | 36 ++--- src/eventPlugins/EnterLeaveEventPlugin.js | 8 +- src/eventPlugins/SelectEventPlugin.js | 12 +- src/eventPlugins/SimpleEventPlugin.js | 150 ++++++++++++++---- src/eventPlugins/TapEventPlugin.js | 18 +-- src/test/getTestDocument.js | 22 +-- .../core/dom/getUnboundedScrollPosition.js | 14 -- 27 files changed, 254 insertions(+), 271 deletions(-) delete mode 100644 docs/tips/14-communicate-between-components.md diff --git a/README.md b/README.md index 157457406c3a..25d79f1e1d12 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,21 @@ React is a JavaScript library for building user interfaces. -* **Just the UI:** Lots of people use React as the V in MVC. Since React makes no assumptions about the rest of your technology stack, it's easy to try it out on a small feature in an existing project. -* **Virtual DOM:** React uses a *virtual DOM* diff implementation for ultra-high performance. It can also render on the server using Node.js — no heavy browser DOM required. -* **Data flow:** React implements one-way reactive data flow which reduces boilerplate and is easier to reason about than traditional data binding. +* **Declarative:** React uses a declarative paradigm that makes it easier to reason about your application. +* **Efficient:** React computes the minimal set of changes necessary to keep your DOM up-to-date. +* **Flexible:** React works with the libraries and frameworks that you already know. [Learn how to use React in your own project.](http://facebook.github.io/react/docs/getting-started.html) ## Examples -We have several examples [on the website](http://facebook.github.io/react/). Here is the first one to get you started: +We have several examples [on the website](http://facebook.github.io/react). Here is the first one to get you started: ```js /** @jsx React.DOM */ var HelloMessage = React.createClass({ render: function() { - return
Hello {this.props.name}
; + return
{'Hello ' + this.props.name}
; } }); @@ -51,7 +51,7 @@ bower install --save react ## Contribute -The main purpose of this repository is to continue to evolve React core, making it faster and easier to use. If you're interested in helping with that, then keep reading. If you're not interested in helping right now that's ok too. :) Any feedback you have about using React would be greatly appreciated. +The main purpose of this repository is to continue to evolve React core, making it faster and easier to use. If you're interested in helping with that, then keep reading. If you're not interested in helping right now that's ok too :) Any feedback you have about using React would be greatly appreciated. ### Building Your Copy of React @@ -81,10 +81,12 @@ At this point, you should now have a `build/` directory populated with everythin We use grunt to automate many tasks. Run `grunt -h` to see a mostly complete listing. The important ones to know: ```sh -# Build and run tests with PhantomJS +# Create test build & run tests with PhantomJS grunt test -# Lint the code with JSHint +# Lint the core library code with JSHint grunt lint +# Lint package code +grunt lint:package # Wipe out build directory grunt clean ``` diff --git a/docs/_data/nav_tips.yml b/docs/_data/nav_tips.yml index 95824f282e7b..c1eb18084266 100644 --- a/docs/_data/nav_tips.yml +++ b/docs/_data/nav_tips.yml @@ -26,5 +26,3 @@ title: Load Initial Data via AJAX - id: false-in-jsx title: False in JSX - - id: communicate-between-components - title: Communicate Between Components diff --git a/docs/_js/examples/timer.js b/docs/_js/examples/timer.js index 685e57e705e4..eb1fed09aa24 100644 --- a/docs/_js/examples/timer.js +++ b/docs/_js/examples/timer.js @@ -18,7 +18,7 @@ var Timer = React.createClass({\n\ },\n\ render: function() {\n\ return React.DOM.div({},\n\ - 'Seconds Elapsed: ', this.state.secondsElapsed\n\ + 'Seconds Elapsed: ' + this.state.secondsElapsed\n\ );\n\ }\n\ });\n\ diff --git a/docs/blog/index.html b/docs/blog/index.html index 3eb406c2e13c..ab3bbd179727 100644 --- a/docs/blog/index.html +++ b/docs/blog/index.html @@ -20,7 +20,7 @@

{{ page.title }}

diff --git a/docs/tips/13-false-in-jsx.md b/docs/tips/13-false-in-jsx.md index 3b46f12895ca..0c86bddf5577 100644 --- a/docs/tips/13-false-in-jsx.md +++ b/docs/tips/13-false-in-jsx.md @@ -4,7 +4,6 @@ title: False in JSX layout: tips permalink: false-in-jsx.html prev: initial-ajax.html -next: communicate-between-components.html --- Here's how `false` renders in different contexts: diff --git a/docs/tips/14-communicate-between-components.md b/docs/tips/14-communicate-between-components.md deleted file mode 100644 index 84c1a9feb902..000000000000 --- a/docs/tips/14-communicate-between-components.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -id: communicate-between-components -title: Communicate Between Components -layout: tips -permalink: communicate-between-components.html -prev: false-in-jsx.html ---- - -For parent-child communication, simply [pass props](/react/docs/multiple-components.html). - -For child-parent communication: -Say your `GroceryList` component has a list of items generated through an array. When a list item is clicked, you want to display its name: - -```js -/** @jsx React.DOM */ - -var GroceryList = React.createClass({ - handleClick: function(i) { - console.log('You clicked: ' + this.props.items[i]); - }, - - render: function() { - return ( -
- {this.props.items.map(function(item, i) { - return ( -
{item}
- ); - }, this)} -
- ); - } -}); - -React.renderComponent( - , mountNode -); -``` - -Notice the use of `bind(this, arg1, arg2, ...)`: we're simply passing more arguments to `handleClick`. This is not a new React concept; it's just JavaScript. diff --git a/examples/basic-jsx/index.html b/examples/basic-jsx/index.html index 6fe8133eb9c9..1b7a4836ab82 100644 --- a/examples/basic-jsx/index.html +++ b/examples/basic-jsx/index.html @@ -40,13 +40,16 @@

Example Details

* @jsx React.DOM */ var ExampleApplication = React.createClass({ + _onClick: function (e) { + console.log('click event'); + }, render: function() { var elapsed = Math.round(this.props.elapsed / 100); var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' ); var message = 'React has been successfully running for ' + seconds + ' seconds.'; - return

{message}

; + return

{message}

{message}

; } }); var start = new Date().getTime(); diff --git a/src/addons/transitions/ReactTransitionGroup.js b/src/addons/transitions/ReactTransitionGroup.js index f846f208c2a0..623838c49f4c 100644 --- a/src/addons/transitions/ReactTransitionGroup.js +++ b/src/addons/transitions/ReactTransitionGroup.js @@ -46,7 +46,6 @@ var ReactTransitionGroupMixin = { var children = {}; var childMapping = ReactTransitionKeySet.getChildMapping(sourceChildren); - var transitionConfig = this.getTransitionConfig(); var currentKeys = ReactTransitionKeySet.mergeKeySets( this._transitionGroupCurrentKeys, diff --git a/src/addons/transitions/ReactTransitionKeySet.js b/src/addons/transitions/ReactTransitionKeySet.js index ebb1cdeeb930..debb3073ca3f 100644 --- a/src/addons/transitions/ReactTransitionKeySet.js +++ b/src/addons/transitions/ReactTransitionKeySet.js @@ -68,9 +68,6 @@ var ReactTransitionKeySet = { * in `next` in a reasonable order. */ mergeKeySets: function(prev, next) { - prev = prev || {}; - next = next || {}; - var keySet = {}; var prevKeys = Object.keys(prev).concat([MERGE_KEY_SETS_TAIL_SENTINEL]); var nextKeys = Object.keys(next).concat([MERGE_KEY_SETS_TAIL_SENTINEL]); diff --git a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js index 1e5801f3bd86..bfddd7be5ae9 100644 --- a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js +++ b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js @@ -26,18 +26,18 @@ var mocks; // Most of the real functionality is covered in other unit tests, this just // makes sure we're wired up correctly. describe('ReactTransitionGroup', function() { - var container; - beforeEach(function() { React = require('React'); ReactTransitionGroup = require('ReactTransitionGroup'); mocks = require('mocks'); - - container = document.createElement('div'); }); it('should warn after time with no transitionend', function() { - var a = React.renderComponent( + var container; + var a; + + container = document.createElement('div'); + a = React.renderComponent( , @@ -65,6 +65,7 @@ describe('ReactTransitionGroup', function() { }); it('should keep both sets of DOM nodes around', function() { + var container = document.createElement('div'); var a = React.renderComponent( @@ -82,14 +83,4 @@ describe('ReactTransitionGroup', function() { expect(a.getDOMNode().childNodes[0].id).toBe('two'); expect(a.getDOMNode().childNodes[1].id).toBe('one'); }); - - describe('with an undefined child', function () { - it('should fail silently', function () { - React.renderComponent( - - , - container - ); - }); - }); }); diff --git a/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js b/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js index 993ebbc6e09d..72aba5ca6f21 100644 --- a/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js +++ b/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js @@ -127,30 +127,4 @@ describe('ReactTransitionKeySet', function() { five: true }); }); - - it('should support mergeKeySets with undefined input', function () { - var prev = { - one: true, - two: true - }; - - var next = undefined; - - expect(ReactTransitionKeySet.mergeKeySets(prev, next)).toEqual({ - one: true, - two: true - }); - - prev = undefined; - - next = { - three: true, - four: true - }; - - expect(ReactTransitionKeySet.mergeKeySets(prev, next)).toEqual({ - three: true, - four: true - }); - }); }); diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js index 3896f3029cc4..881d4d9d42e2 100644 --- a/src/core/ReactCompositeComponent.js +++ b/src/core/ReactCompositeComponent.js @@ -624,7 +624,7 @@ var ReactCompositeComponentMixin = { this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING; this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null; - this.props = this._processProps(this.props); + this._processProps(this.props); if (this.__reactAutoBindMap) { this._bindAutoBindMethods(); @@ -792,31 +792,22 @@ var ReactCompositeComponentMixin = { /** * Processes props by setting default values for unspecified props and - * asserting that the props are valid. Does not mutate its argument; returns - * a new props object with defaults merged in. + * asserting that the props are valid. * - * @param {object} newProps - * @return {object} + * @param {object} props * @private */ - _processProps: function(newProps) { - var props = this._defaultProps; - if (props) { - // To avoid mutating the props that are passed into the constructor, we - // copy onto the object returned by getDefaultProps instead. - for (var propName in newProps) { - if (typeof newProps[propName] !== 'undefined') { - props[propName] = newProps[propName]; - } + _processProps: function(props) { + var defaultProps = this._defaultProps; + for (var propName in defaultProps) { + if (typeof props[propName] === 'undefined') { + props[propName] = defaultProps[propName]; } - } else { - props = newProps; } var propTypes = this.constructor.propTypes; if (propTypes) { this._checkPropTypes(propTypes, props, ReactPropTypeLocations.prop); } - return props; }, /** @@ -868,7 +859,8 @@ var ReactCompositeComponentMixin = { var nextProps = this.props; if (this._pendingProps != null) { - nextProps = this._processProps(this._pendingProps); + nextProps = this._pendingProps; + this._processProps(nextProps); this._pendingProps = null; this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS; diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index a3bf431d398f..11dd41bd41c0 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -215,26 +215,6 @@ describe('ReactCompositeComponent', function() { reactComponentExpect(instance3).scalarPropsEqual({key: null}); }); - it('should not mutate passed-in props object', function() { - var Component = React.createClass({ - getDefaultProps: function() { - return {key: 'testKey'}; - }, - render: function() { - return ; - } - }); - - var inputProps = {}; - var instance1 = Component(inputProps); - ReactTestUtils.renderIntoDocument(instance1); - expect(instance1.props.key).toBe('testKey'); - - // We don't mutate the input, just in case the caller wants to do something - // with it after using it to instantiate a component - expect(inputProps.key).not.toBeDefined(); - }); - it('should normalize props with default values', function() { var Component = React.createClass({ propTypes: {key: ReactPropTypes.string.isRequired}, diff --git a/src/core/__tests__/ReactRenderDocument-test.js b/src/core/__tests__/ReactRenderDocument-test.js index 4ecff37d76b6..dd5250811ca0 100644 --- a/src/core/__tests__/ReactRenderDocument-test.js +++ b/src/core/__tests__/ReactRenderDocument-test.js @@ -40,7 +40,10 @@ describe('rendering React components at document', function() { }); it('should be able to get root component id for document node', function() { - expect(testDocument).not.toBeUndefined(); + if (!testDocument) { + // These tests are not applicable in jst, since jsdom is buggy. + return; + } var Root = React.createClass({ render: function() { @@ -66,7 +69,10 @@ describe('rendering React components at document', function() { }); it('should be able to unmount component from document node', function() { - expect(testDocument).not.toBeUndefined(); + if (!testDocument) { + // These tests are not applicable in jst, since jsdom is buggy. + return; + } var Root = React.createClass({ render: function() { @@ -94,7 +100,10 @@ describe('rendering React components at document', function() { }); it('should be able to switch root constructors via state', function() { - expect(testDocument).not.toBeUndefined(); + if (!testDocument) { + // These tests are not applicable in jst, since jsdom is buggy. + return; + } var Component = React.createClass({ render: function() { @@ -153,7 +162,10 @@ describe('rendering React components at document', function() { }); it('should be able to switch root constructors', function() { - expect(testDocument).not.toBeUndefined(); + if (!testDocument) { + // These tests are not applicable in jst, since jsdom is buggy. + return; + } var Component = React.createClass({ render: function() { @@ -198,7 +210,10 @@ describe('rendering React components at document', function() { }); it('should be able to mount into document', function() { - expect(testDocument).not.toBeUndefined(); + if (!testDocument) { + // These tests are not applicable in jst, since jsdom is buggy. + return; + } var Component = React.createClass({ render: function() { @@ -221,7 +236,10 @@ describe('rendering React components at document', function() { }); it('should throw on full document render', function() { - expect(testDocument).not.toBeUndefined(); + if (!testDocument) { + // These tests are not applicable in jst, since jsdom is buggy. + return; + } var container = testDocument; expect(function() { @@ -237,7 +255,10 @@ describe('rendering React components at document', function() { }); it('should throw on full document render of non-html', function() { - expect(testDocument).not.toBeUndefined(); + if (!testDocument) { + // These tests are not applicable in jst, since jsdom is buggy. + return; + } var container = testDocument; ReactMount.allowFullPageRender = true; diff --git a/src/event/EventPluginRegistry.js b/src/event/EventPluginRegistry.js index 50f2984e0f2b..6064da643c55 100644 --- a/src/event/EventPluginRegistry.js +++ b/src/event/EventPluginRegistry.js @@ -123,6 +123,7 @@ function publishRegistrationName(registrationName, PluginModule) { registrationName ); EventPluginRegistry.registrationNames[registrationName] = PluginModule; + EventPluginRegistry.registrationNamesKeys.push(registrationName); } /** @@ -142,6 +143,11 @@ var EventPluginRegistry = { */ registrationNames: {}, + /** + * The keys of `registrationNames`. + */ + registrationNamesKeys: [], + /** * Mapping from on{EventName} to */ @@ -245,6 +251,7 @@ var EventPluginRegistry = { delete registrationNames[registrationName]; } } + EventPluginRegistry.registrationNamesKeys.length = 0; } }; diff --git a/src/event/__tests__/EventPluginRegistry-test.js b/src/event/__tests__/EventPluginRegistry-test.js index 7474525e56f6..f890a58169fb 100644 --- a/src/event/__tests__/EventPluginRegistry-test.js +++ b/src/event/__tests__/EventPluginRegistry-test.js @@ -174,13 +174,13 @@ describe('EventPluginRegistry', function() { EventPluginRegistry.injectEventPluginsByName({one: OnePlugin}); EventPluginRegistry.injectEventPluginOrder(['one', 'two']); - expect(Object.keys(EventPluginRegistry.registrationNames).length).toBe(2); + expect(EventPluginRegistry.registrationNamesKeys.length).toBe(2); expect(EventPluginRegistry.registrationNames.onClick).toBe(OnePlugin); expect(EventPluginRegistry.registrationNames.onFocus).toBe(OnePlugin); EventPluginRegistry.injectEventPluginsByName({two: TwoPlugin}); - expect(Object.keys(EventPluginRegistry.registrationNames).length).toBe(4); + expect(EventPluginRegistry.registrationNamesKeys.length).toBe(4); expect(EventPluginRegistry.registrationNames.onMagicBubble).toBe(TwoPlugin); expect( EventPluginRegistry.registrationNames.onMagicCapture diff --git a/src/eventPlugins/ChangeEventPlugin.js b/src/eventPlugins/ChangeEventPlugin.js index bb7648485271..2eb1e93e0358 100644 --- a/src/eventPlugins/ChangeEventPlugin.js +++ b/src/eventPlugins/ChangeEventPlugin.js @@ -37,11 +37,12 @@ var eventTypes = { captured: keyOf({onChangeCapture: null}) }, dependencies: [ - keyOf({onInput: null}), - keyOf({onKeyUp: null}), - keyOf({onKeyDown: null}), - keyOf({onFocus: null}), - keyOf({onBlur: null}) + keyOf({topBlur: null}), + keyOf({topChange: null}), + keyOf({topFocus: null}), + keyOf({topInput: null}), + keyOf({topKeyDown: null}), + keyOf({topKeyUp: null}) ] } }; diff --git a/src/eventPlugins/CompositionEventPlugin.js b/src/eventPlugins/CompositionEventPlugin.js index d5da7a21087e..7b7796ca272e 100644 --- a/src/eventPlugins/CompositionEventPlugin.js +++ b/src/eventPlugins/CompositionEventPlugin.js @@ -44,12 +44,12 @@ var eventTypes = { captured: keyOf({onCompositionEndCapture: null}) }, dependencies: [ - keyOf({onCompositionEnd: null}), - keyOf({onKeyDown: null}), - keyOf({onKeyUp: null}), - keyOf({onKeyPress: null}), - keyOf({onMouseDown: null}), - keyOf({onBlur: null}) + keyOf({topBlur: null}), + keyOf({topCompositionEnd: null}), + keyOf({topKeyDown: null}), + keyOf({topKeyPress: null}), + keyOf({topKeyUp: null}), + keyOf({topMouseDown: null}) ] }, compositionStart: { @@ -58,12 +58,12 @@ var eventTypes = { captured: keyOf({onCompositionStartCapture: null}) }, dependencies: [ - keyOf({onCompositionStart: null}), - keyOf({onKeyDown: null}), - keyOf({onKeyUp: null}), - keyOf({onKeyPress: null}), - keyOf({onMouseDown: null}), - keyOf({onBlur: null}) + keyOf({topBlur: null}), + keyOf({topCompositionStart: null}), + keyOf({topKeyDown: null}), + keyOf({topKeyPress: null}), + keyOf({topKeyUp: null}), + keyOf({topMouseDown: null}) ] }, compositionUpdate: { @@ -72,12 +72,12 @@ var eventTypes = { captured: keyOf({onCompositionUpdateCapture: null}) }, dependencies: [ - keyOf({onCompositionUpdate: null}), - keyOf({onKeyDown: null}), - keyOf({onKeyUp: null}), - keyOf({onKeyPress: null}), - keyOf({onMouseDown: null}), - keyOf({onBlur: null}) + keyOf({topBlur: null}), + keyOf({topCompositionUpdate: null}), + keyOf({topKeyDown: null}), + keyOf({topKeyPress: null}), + keyOf({topKeyUp: null}), + keyOf({topMouseDown: null}) ] } }; diff --git a/src/eventPlugins/EnterLeaveEventPlugin.js b/src/eventPlugins/EnterLeaveEventPlugin.js index 000ee4286c7f..7abbb6f1ecea 100644 --- a/src/eventPlugins/EnterLeaveEventPlugin.js +++ b/src/eventPlugins/EnterLeaveEventPlugin.js @@ -33,15 +33,15 @@ var eventTypes = { mouseEnter: { registrationName: keyOf({onMouseEnter: null}), dependencies: [ - keyOf({onMouseOut: null}), - keyOf({onMouseOver: null}) + keyOf({topMouseOut: null}), + keyOf({topMouseOver: null}) ] }, mouseLeave: { registrationName: keyOf({onMouseLeave: null}), dependencies: [ - keyOf({onMouseOut: null}), - keyOf({onMouseOver: null}) + keyOf({topMouseOut: null}), + keyOf({topMouseOver: null}) ] } }; diff --git a/src/eventPlugins/SelectEventPlugin.js b/src/eventPlugins/SelectEventPlugin.js index de9a3a372137..aa3d01ae6690 100644 --- a/src/eventPlugins/SelectEventPlugin.js +++ b/src/eventPlugins/SelectEventPlugin.js @@ -39,12 +39,12 @@ var eventTypes = { captured: keyOf({onSelectCapture: null}) }, dependencies: [ - keyOf({onBlur: null}), - keyOf({onFocus: null}), - keyOf({onMouseUp: null}), - keyOf({onMouseDown: null}), - keyOf({onSelectionChange: null}), - keyOf({onKeyDown: null}) + keyOf({topBlur: null}), + keyOf({topFocus: null}), + keyOf({topKeyDown: null}), + keyOf({topMouseDown: null}), + keyOf({topMouseUp: null}), + keyOf({topSelectionChange: null}) ] } }; diff --git a/src/eventPlugins/SimpleEventPlugin.js b/src/eventPlugins/SimpleEventPlugin.js index 4c69441dbe6b..07850b2067a7 100644 --- a/src/eventPlugins/SimpleEventPlugin.js +++ b/src/eventPlugins/SimpleEventPlugin.js @@ -39,115 +39,172 @@ var eventTypes = { phasedRegistrationNames: { bubbled: keyOf({onBlur: true}), captured: keyOf({onBlurCapture: true}) - } + }, + dependencies: [ + keyOf({topBlur: null}) + ] }, click: { phasedRegistrationNames: { bubbled: keyOf({onClick: true}), captured: keyOf({onClickCapture: true}) - } + }, + dependencies: [ + keyOf({topClick: null}) + ] }, contextMenu: { phasedRegistrationNames: { bubbled: keyOf({onContextMenu: true}), captured: keyOf({onContextMenuCapture: true}) - } + }, + dependencies: [ + keyOf({topContextMenu: null}) + ] }, copy: { phasedRegistrationNames: { bubbled: keyOf({onCopy: true}), captured: keyOf({onCopyCapture: true}) - } + }, + dependencies: [ + keyOf({topCopy: null}) + ] }, cut: { phasedRegistrationNames: { bubbled: keyOf({onCut: true}), captured: keyOf({onCutCapture: true}) - } + }, + dependencies: [ + keyOf({topCut: null}) + ] }, doubleClick: { phasedRegistrationNames: { bubbled: keyOf({onDoubleClick: true}), captured: keyOf({onDoubleClickCapture: true}) - } + }, + dependencies: [ + keyOf({topDoubleClick: null}) + ] }, drag: { phasedRegistrationNames: { bubbled: keyOf({onDrag: true}), captured: keyOf({onDragCapture: true}) - } + }, + dependencies: [ + keyOf({topDrag: null}) + ] }, dragEnd: { phasedRegistrationNames: { bubbled: keyOf({onDragEnd: true}), captured: keyOf({onDragEndCapture: true}) - } + }, + dependencies: [ + keyOf({topDragEnd: null}) + ] }, dragEnter: { phasedRegistrationNames: { bubbled: keyOf({onDragEnter: true}), captured: keyOf({onDragEnterCapture: true}) - } + }, + dependencies: [ + keyOf({topDragEnter: null}) + ] }, dragExit: { phasedRegistrationNames: { bubbled: keyOf({onDragExit: true}), captured: keyOf({onDragExitCapture: true}) - } + }, + dependencies: [ + keyOf({topDragExit: null}) + ] }, dragLeave: { phasedRegistrationNames: { bubbled: keyOf({onDragLeave: true}), captured: keyOf({onDragLeaveCapture: true}) - } + }, + dependencies: [ + keyOf({topDragLeave: null}) + ] }, dragOver: { phasedRegistrationNames: { bubbled: keyOf({onDragOver: true}), captured: keyOf({onDragOverCapture: true}) - } + }, + dependencies: [ + keyOf({topDragOver: null}) + ] }, dragStart: { phasedRegistrationNames: { bubbled: keyOf({onDragStart: true}), captured: keyOf({onDragStartCapture: true}) - } + }, + dependencies: [ + keyOf({topDragStart: null}) + ] }, drop: { phasedRegistrationNames: { bubbled: keyOf({onDrop: true}), captured: keyOf({onDropCapture: true}) - } + }, + dependencies: [ + keyOf({topDrop: null}) + ] }, focus: { phasedRegistrationNames: { bubbled: keyOf({onFocus: true}), captured: keyOf({onFocusCapture: true}) - } + }, + dependencies: [ + keyOf({topFocus: null}) + ] }, input: { phasedRegistrationNames: { bubbled: keyOf({onInput: true}), captured: keyOf({onInputCapture: true}) - } + }, + dependencies: [ + keyOf({topInput: null}) + ] }, keyDown: { phasedRegistrationNames: { bubbled: keyOf({onKeyDown: true}), captured: keyOf({onKeyDownCapture: true}) - } + }, + dependencies: [ + keyOf({topKeyDown: null}) + ] }, keyPress: { phasedRegistrationNames: { bubbled: keyOf({onKeyPress: true}), captured: keyOf({onKeyPressCapture: true}) - } + }, + dependencies: [ + keyOf({topKeyPress: null}) + ] }, keyUp: { phasedRegistrationNames: { bubbled: keyOf({onKeyUp: true}), captured: keyOf({onKeyUpCapture: true}) - } + }, + dependencies: [ + keyOf({topKeyUp: null}) + ] }, // Note: We do not allow listening to mouseOver events. Instead, use the // onMouseEnter/onMouseLeave created by `EnterLeaveEventPlugin`. @@ -155,67 +212,100 @@ var eventTypes = { phasedRegistrationNames: { bubbled: keyOf({onMouseDown: true}), captured: keyOf({onMouseDownCapture: true}) - } + }, + dependencies: [ + keyOf({topMouseDown: null}) + ] }, mouseMove: { phasedRegistrationNames: { bubbled: keyOf({onMouseMove: true}), captured: keyOf({onMouseMoveCapture: true}) - } + }, + dependencies: [ + keyOf({topMouseMove: null}) + ] }, mouseUp: { phasedRegistrationNames: { bubbled: keyOf({onMouseUp: true}), captured: keyOf({onMouseUpCapture: true}) - } + }, + dependencies: [ + keyOf({topMouseUp: null}) + ] }, paste: { phasedRegistrationNames: { bubbled: keyOf({onPaste: true}), captured: keyOf({onPasteCapture: true}) - } + }, + dependencies: [ + keyOf({topPaste: null}) + ] }, scroll: { phasedRegistrationNames: { bubbled: keyOf({onScroll: true}), captured: keyOf({onScrollCapture: true}) - } + }, + dependencies: [ + keyOf({topScroll: null}) + ] }, submit: { phasedRegistrationNames: { bubbled: keyOf({onSubmit: true}), captured: keyOf({onSubmitCapture: true}) - } + }, + dependencies: [ + keyOf({topSubmit: null}) + ] }, touchCancel: { phasedRegistrationNames: { bubbled: keyOf({onTouchCancel: true}), captured: keyOf({onTouchCancelCapture: true}) - } + }, + dependencies: [ + keyOf({topTouchCancel: null}) + ] }, touchEnd: { phasedRegistrationNames: { bubbled: keyOf({onTouchEnd: true}), captured: keyOf({onTouchEndCapture: true}) - } + }, + dependencies: [ + keyOf({topTouchEnd: null}) + ] }, touchMove: { phasedRegistrationNames: { bubbled: keyOf({onTouchMove: true}), captured: keyOf({onTouchMoveCapture: true}) - } + }, + dependencies: [ + keyOf({topTouchMove: null}) + ] }, touchStart: { phasedRegistrationNames: { bubbled: keyOf({onTouchStart: true}), captured: keyOf({onTouchStartCapture: true}) - } + }, + dependencies: [ + keyOf({topTouchStart: null}) + ] }, wheel: { phasedRegistrationNames: { bubbled: keyOf({onWheel: true}), captured: keyOf({onWheelCapture: true}) - } + }, + dependencies: [ + keyOf({topWheel: null}) + ] } }; diff --git a/src/eventPlugins/TapEventPlugin.js b/src/eventPlugins/TapEventPlugin.js index 06b53a06c6d8..6cd30f206a60 100644 --- a/src/eventPlugins/TapEventPlugin.js +++ b/src/eventPlugins/TapEventPlugin.js @@ -62,18 +62,18 @@ function getDistance(coords, nativeEvent) { } var dependencies = [ - keyOf({onMouseDown: null}), - keyOf({onMouseMove: null}), - keyOf({onMouseUp: null}) + keyOf({topMouseDown: null}), + keyOf({topMouseMove: null}), + keyOf({topMouseUp: null}) ]; if (EventPluginUtils.supportTouch) { - dependencies = dependencies.concat([ - keyOf({onTouchStart: null}), - keyOf({onTouchMove: null}), - keyOf({onTouchEnd: null}), - keyOf({onTouchCancel: null}) - ]); + dependencies.push( + keyOf({topTouchStart: null}), + keyOf({topTouchMove: null}), + keyOf({topTouchEnd: null}), + keyOf({topTouchCancel: null}) + ); } var eventTypes = { diff --git a/src/test/getTestDocument.js b/src/test/getTestDocument.js index 82d7d64dfb96..9d30b6df57ac 100644 --- a/src/test/getTestDocument.js +++ b/src/test/getTestDocument.js @@ -16,18 +16,18 @@ * @providesModule getTestDocument */ +/** + * We need to work around the fact that we have two different + * test implementations: once that breaks if we clobber document + * (open-source) and one that doesn't support createHTMLDocument() + * (jst). + */ function getTestDocument() { - var iframe = document.createElement('iframe'); - iframe.style.display = 'none'; - document.body.appendChild(iframe); - - var testDocument = iframe.contentDocument || iframe.contentWindow.document; - testDocument.open(); - testDocument.write('test doc'); - testDocument.close(); - - iframe.parentNode.removeChild(iframe); - return testDocument; + if (document.implementation && + document.implementation.createHTMLDocument) { + return document.implementation.createHTMLDocument('test doc'); + } + return null; } module.exports = getTestDocument; diff --git a/src/vendor/core/dom/getUnboundedScrollPosition.js b/src/vendor/core/dom/getUnboundedScrollPosition.js index b9f8c9c6431a..872465bbc15e 100644 --- a/src/vendor/core/dom/getUnboundedScrollPosition.js +++ b/src/vendor/core/dom/getUnboundedScrollPosition.js @@ -1,18 +1,4 @@ /** - * Copyright 2013 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * * @providesModule getUnboundedScrollPosition * @typechecks */ From 2437586f6fa39567ce0701d3ed7c28239cb11616 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 02:23:33 +0100 Subject: [PATCH 07/31] Revert "Merge branch 'master' of https://github.com/facebook/react into on-demand-events" This reverts commit 3bec9536de57f4bb9fe1b862b3dd806a8ea07fbf. --- src/core/React.js | 4 +- src/core/ReactDOMComponent.js | 18 +---- src/core/ReactEventEmitter.js | 65 ++++++++++++------- src/core/ReactMount.js | 4 +- src/core/__tests__/ReactComponent-test.js | 4 ++ .../__tests__/ReactCompositeComponent-test.js | 2 - src/core/__tests__/ReactEventEmitter-test.js | 6 +- .../ReactEventTopLevelCallback-test.js | 9 +-- .../__tests__/ReactPropTransferer-test.js | 2 - .../__tests__/ReactDOMButton-test.js | 1 - .../__tests__/ReactDOMInput-test.js | 2 +- .../__tests__/ReactDOMSelect-test.js | 2 - .../__tests__/ReactDOMTextarea-test.js | 2 - .../__tests__/ReactServerRendering-test.js | 1 - src/event/EventPluginRegistry.js | 15 ----- src/event/EventPluginUtils.js | 2 +- 16 files changed, 63 insertions(+), 76 deletions(-) diff --git a/src/core/React.js b/src/core/React.js index cbf6d93a64c3..42bcbaa6e5f3 100644 --- a/src/core/React.js +++ b/src/core/React.js @@ -18,7 +18,6 @@ "use strict"; -var EventPluginUtils = require('EventPluginUtils'); var ReactComponent = require('ReactComponent'); var ReactCompositeComponent = require('ReactCompositeComponent'); var ReactContext = require('ReactContext'); @@ -33,6 +32,7 @@ var ReactPerf = require('ReactPerf'); var ReactPropTypes = require('ReactPropTypes'); var ReactServerRendering = require('ReactServerRendering'); var ReactTextComponent = require('ReactTextComponent'); +var EventPluginUtils = require('EventPluginUtils'); ReactDefaultInjection.inject(); @@ -40,7 +40,7 @@ var React = { DOM: ReactDOM, PropTypes: ReactPropTypes, initializeTouchEvents: function(shouldUseTouch) { - EventPluginUtils.useTouchEvents = shouldUseTouch; + EventPluginUtils.supportTouch = shouldUseTouch; }, createClass: ReactCompositeComponent.createClass, constructAndRenderComponent: ReactMount.constructAndRenderComponent, diff --git a/src/core/ReactDOMComponent.js b/src/core/ReactDOMComponent.js index ceb40cc309fc..e1803cc60fb5 100644 --- a/src/core/ReactDOMComponent.js +++ b/src/core/ReactDOMComponent.js @@ -44,8 +44,6 @@ var CONTENT_TYPES = {'string': true, 'number': true}; var STYLE = keyOf({style: null}); -var ELEMENT_NODE_TYPE = 1; - /** * @param {?object} props */ @@ -131,13 +129,7 @@ ReactDOMComponent.Mixin = { continue; } if (registrationNames[propKey]) { - var container = ReactMount.findReactContainerForID(this._rootNodeID); - if (container) { - var doc = container.nodeType === ELEMENT_NODE_TYPE ? - container.ownerDocument : - container; - listenTo(propKey, doc); - } + listenTo(propKey, ReactMount.findReactContainerForID(this._rootNodeID)); putListener(this._rootNodeID, propKey, propValue); } else { if (propKey === STYLE) { @@ -297,13 +289,7 @@ ReactDOMComponent.Mixin = { styleUpdates = nextProp; } } else if (registrationNames[propKey]) { - var container = ReactMount.findReactContainerForID(this._rootNodeID); - if (container) { - var doc = container.nodeType === ELEMENT_NODE_TYPE ? - container.ownerDocument : - container; - listenTo(propKey, doc); - } + listenTo(propKey, ReactMount.findReactContainerForID(this._rootNodeID)); putListener(this._rootNodeID, propKey, nextProp); } else if ( DOMProperty.isStandardName[propKey] || diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index ce9d2b700038..9d9298d8520f 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -25,7 +25,6 @@ var EventPluginHub = require('EventPluginHub'); var ExecutionEnvironment = require('ExecutionEnvironment'); var ReactEventEmitterMixin = require('ReactEventEmitterMixin'); var ViewportMetrics = require('ViewportMetrics'); -var EventPluginRegistry = require('EventPluginRegistry'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); @@ -79,7 +78,6 @@ var keyOf = require('keyOf'); var alreadyListeningTo = {}; var isMonitoringScrollValue = false; -var reactTopListenersCounter = 0; /** * Traps top-level events by using event bubbling. @@ -182,20 +180,36 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { * @param {DOMDocument} contentDocument Document which owns the container */ listenTo: function(event, contentDocument) { - var mountAt = contentDocument; - if (!mountAt._reactTopListenersID) { - mountAt._reactTopListenersID = '' + reactTopListenersCounter++; - alreadyListeningTo[mountAt._reactTopListenersID] = []; - } - var topListenersID = mountAt._reactTopListenersID; - if (!alreadyListeningTo[topListenersID][event]) { - var registrationName = ReactEventEmitter.registrationNames[event]; - var dependencies = registrationName.eventTypes[EventPluginRegistry.eventMapping[event]].dependencies; + var mountAt; + var camelCasedEventName; + var camelCasedEventNameAlt; + var dependencies; + var dependency; + var i; + var l; + var topLevelType; + var topLevelTypes; + var registration; - var topLevelTypes = EventConstants.topLevelTypes; - for (var i = 0, l = dependencies.length; i < l; i++) { - var dependency = dependencies[i]; - var topLevelType = topLevelTypes[dependency]; + if (!alreadyListeningTo[event]) { + mountAt = contentDocument; + camelCasedEventName = event.substr(2); + camelCasedEventNameAlt = + camelCasedEventName.charAt(0).toLowerCase() + camelCasedEventName.slice(1); + registration = ReactEventEmitter.registrationNames[event]; + if (registration && registration.eventTypes[camelCasedEventNameAlt]) { + dependencies = registration. + eventTypes[camelCasedEventNameAlt].dependencies; + } + if (dependencies) { + for (i = 0, l = dependencies.length; i < l; i++) { + dependency = dependencies[i]; + ReactEventEmitter.listenTo(dependency, contentDocument); + } + } + else { + topLevelTypes = EventConstants.topLevelTypes; + topLevelType = topLevelTypes['top' + camelCasedEventName]; if (topLevelType === topLevelTypes.topWheel) { if (isEventSupported('wheel')) { @@ -207,22 +221,25 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { // @see http://www.quirksmode.org/dom/events/tests/scroll.html trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt); } - } else if (topLevelType === topLevelTypes.topScroll) { + } + else if(topLevelType === topLevelTypes.topScroll) { if (isEventSupported('scroll', true)) { trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt); } else { trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window); } - } else if (topLevelType === topLevelTypes.topFocus || - topLevelType === topLevelTypes.topBlur) { + } + else if (topLevelType === topLevelTypes.topFocus || + topLevelType === topLevelTypes.topBlur) { 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 + // @see + // http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt); trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt); } @@ -230,11 +247,15 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { // to make sure event listeners are only attached once alreadyListeningTo[keyOf({onBlur: null})] = true; alreadyListeningTo[keyOf({onFocus: null})] = true; - } else { - trapBubbledEvent(topLevelType, EventPluginRegistry.eventMapping[event], mountAt); + } + else { + if(camelCasedEventName === 'DoubleClick') { + camelCasedEventName = 'DblClick'; + } + trapBubbledEvent(topLevelType, camelCasedEventName.toLowerCase(), mountAt); } - alreadyListeningTo[topListenersID][event] = true; + alreadyListeningTo[event] = true; } } }, diff --git a/src/core/ReactMount.js b/src/core/ReactMount.js index dce8523b575c..4f7e792262cc 100644 --- a/src/core/ReactMount.js +++ b/src/core/ReactMount.js @@ -240,8 +240,8 @@ var ReactMount = { invariant( container && ( container.nodeType === ELEMENT_NODE_TYPE || - container.nodeType === DOC_NODE_TYPE - ), + container.nodeType === DOC_NODE_TYPE + ), '_registerComponent(...): Target container is not a DOM element.' ); diff --git a/src/core/__tests__/ReactComponent-test.js b/src/core/__tests__/ReactComponent-test.js index 4c1b54e64a21..062014ec7d9b 100644 --- a/src/core/__tests__/ReactComponent-test.js +++ b/src/core/__tests__/ReactComponent-test.js @@ -209,4 +209,8 @@ describe('ReactComponent', function() { expect(root.refs.child.refs.span._mountDepth).toBe(6); }); + it("should listen to events on demand", function() { + // TODO + change component version + }); + }); diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index 11dd41bd41c0..0afef2dbdaa5 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -78,8 +78,6 @@ describe('ReactCompositeComponent', function() { ; } }); - - delete document['_reactTopListenersID']; }); it('should support rendering to different child types over time', function() { diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index 04e8de19316e..81dbba8252e9 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -100,7 +100,6 @@ describe('ReactEventEmitter', function() { EventPluginHub.injection.injectEventPluginsByName({ TapEventPlugin: TapEventPlugin }); - delete document['_reactTopListenersID']; }); it('should store a listener correctly', function() { @@ -318,8 +317,10 @@ describe('ReactEventEmitter', function() { 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); }); @@ -337,7 +338,6 @@ describe('ReactEventEmitter', function() { ReactEventEmitter.listenTo(ON_CHANGE_KEY, document); - var SCROLL_MONITORING_EVENTS_NO = 2; var setEventListeners = []; var listenCalls = EventListener.listen.argsForCall; var captureCalls = EventListener.capture.argsForCall; @@ -350,7 +350,7 @@ describe('ReactEventEmitter', function() { var dependencies = ReactEventEmitter.registrationNames[ON_CHANGE_KEY].eventTypes.change.dependencies; - expect(setEventListeners.length).toEqual(dependencies.length + SCROLL_MONITORING_EVENTS_NO); + expect(setEventListeners.length).toEqual(dependencies.length); for(i = 0, l = setEventListeners.length; i < l; i++) { expect(dependencies.indexOf(setEventListeners[i])).toBeTruthy(); diff --git a/src/core/__tests__/ReactEventTopLevelCallback-test.js b/src/core/__tests__/ReactEventTopLevelCallback-test.js index 14b02ac1d099..0d8b7dab48ed 100644 --- a/src/core/__tests__/ReactEventTopLevelCallback-test.js +++ b/src/core/__tests__/ReactEventTopLevelCallback-test.js @@ -22,8 +22,7 @@ require('mock-modules') .dontMock('ReactEventTopLevelCallback') .dontMock('ReactMount') .dontMock('ReactInstanceHandles') - .dontMock('ReactDOM') - .mock('ReactEventEmitter'); + .dontMock('ReactDOM'); var EVENT_TARGET_PARAM = 1; @@ -31,14 +30,16 @@ describe('ReactEventTopLevelCallback', function() { var React; var ReactEventTopLevelCallback; var ReactDOM; - var ReactEventEmitter; // mocked + var ReactEventEmitter; beforeEach(function() { require('mock-modules').dumpCache(); React = require('React'); ReactEventTopLevelCallback = require('ReactEventTopLevelCallback'); ReactDOM = require('ReactDOM'); - ReactEventEmitter = require('ReactEventEmitter'); // mocked + ReactEventEmitter = require('ReactEventEmitter'); + + ReactEventEmitter.handleTopLevel = mocks.getMockFunction(); }); describe('Propagation', function() { diff --git a/src/core/__tests__/ReactPropTransferer-test.js b/src/core/__tests__/ReactPropTransferer-test.js index 76de37f9c6d5..1854ef7e959c 100644 --- a/src/core/__tests__/ReactPropTransferer-test.js +++ b/src/core/__tests__/ReactPropTransferer-test.js @@ -44,8 +44,6 @@ describe('ReactPropTransferer', function() { ); } }); - - delete document['_reactTopListenersID']; }); it('should leave explicitly specified properties intact', function() { diff --git a/src/dom/components/__tests__/ReactDOMButton-test.js b/src/dom/components/__tests__/ReactDOMButton-test.js index 43b96122a64d..21a000018cb0 100644 --- a/src/dom/components/__tests__/ReactDOMButton-test.js +++ b/src/dom/components/__tests__/ReactDOMButton-test.js @@ -49,7 +49,6 @@ describe('ReactDOMButton', function() { beforeEach(function() { React = require('React'); ReactTestUtils = require('ReactTestUtils'); - delete document['_reactTopListenersID']; }); it('should forward clicks when it starts out not disabled', function() { diff --git a/src/dom/components/__tests__/ReactDOMInput-test.js b/src/dom/components/__tests__/ReactDOMInput-test.js index 71a271216f37..0ff9ee05f1ad 100644 --- a/src/dom/components/__tests__/ReactDOMInput-test.js +++ b/src/dom/components/__tests__/ReactDOMInput-test.js @@ -35,7 +35,7 @@ describe('ReactDOMInput', function() { React = require('React'); ReactLink = require('ReactLink'); ReactTestUtils = require('ReactTestUtils'); - delete document['_reactTopListenersID']; + renderTextInput = function(component) { var stub = ReactTestUtils.renderIntoDocument(component); var node = stub.getDOMNode(); diff --git a/src/dom/components/__tests__/ReactDOMSelect-test.js b/src/dom/components/__tests__/ReactDOMSelect-test.js index db15dc76a1d6..6bbe58ca2aa7 100644 --- a/src/dom/components/__tests__/ReactDOMSelect-test.js +++ b/src/dom/components/__tests__/ReactDOMSelect-test.js @@ -40,8 +40,6 @@ describe('ReactDOMSelect', function() { var node = stub.getDOMNode(); return node; }; - - delete document['_reactTopListenersID']; }); it('should allow setting `defaultValue`', function() { diff --git a/src/dom/components/__tests__/ReactDOMTextarea-test.js b/src/dom/components/__tests__/ReactDOMTextarea-test.js index 3a1760a9d5ab..aaca8ea596b5 100644 --- a/src/dom/components/__tests__/ReactDOMTextarea-test.js +++ b/src/dom/components/__tests__/ReactDOMTextarea-test.js @@ -42,8 +42,6 @@ describe('ReactDOMTextarea', function() { node.value = node.innerHTML; return node; }; - - delete document['_reactTopListenersID']; }); it('should allow setting `defaultValue`', function() { diff --git a/src/environment/__tests__/ReactServerRendering-test.js b/src/environment/__tests__/ReactServerRendering-test.js index 20bce375e936..c208eadc569c 100644 --- a/src/environment/__tests__/ReactServerRendering-test.js +++ b/src/environment/__tests__/ReactServerRendering-test.js @@ -48,7 +48,6 @@ describe('ReactServerRendering', function() { ExecutionEnvironment.canUseDOM = false; ReactServerRendering = require('ReactServerRendering'); ReactMarkupChecksum = require('ReactMarkupChecksum'); - delete document['_reactTopListenersID']; }); it('should generate simple markup', function() { diff --git a/src/event/EventPluginRegistry.js b/src/event/EventPluginRegistry.js index 6064da643c55..14a2fcb024af 100644 --- a/src/event/EventPluginRegistry.js +++ b/src/event/EventPluginRegistry.js @@ -41,7 +41,6 @@ function recomputePluginOrdering() { // Wait until an `EventPluginOrder` is injected. return; } - for (var pluginName in namesToPlugins) { var PluginModule = namesToPlugins[pluginName]; var pluginIndex = EventPluginOrder.indexOf(pluginName); @@ -63,15 +62,6 @@ function recomputePluginOrdering() { EventPluginRegistry.plugins[pluginIndex] = PluginModule; var publishedEvents = PluginModule.eventTypes; for (var eventName in publishedEvents) { - var phasedRegistrationNames = publishedEvents[eventName].phasedRegistrationNames; - var domEventName; - if (phasedRegistrationNames) { - domEventName = phasedRegistrationNames.bubbled; - } else { - domEventName = publishedEvents[eventName].registrationName; - } - EventPluginRegistry.eventMapping[domEventName] = eventName; - invariant( publishEventForPlugin(publishedEvents[eventName], PluginModule), 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.', @@ -148,11 +138,6 @@ var EventPluginRegistry = { */ registrationNamesKeys: [], - /** - * Mapping from on{EventName} to - */ - eventMapping: {}, - /** * Injects an ordering of plugins (by plugin name). This allows the ordering * to be decoupled from injection of the actual plugins so that ordering is diff --git a/src/event/EventPluginUtils.js b/src/event/EventPluginUtils.js index d7bee7510986..ab5faaf6b36b 100644 --- a/src/event/EventPluginUtils.js +++ b/src/event/EventPluginUtils.js @@ -180,7 +180,7 @@ var EventPluginUtils = { executeDirectDispatch: executeDirectDispatch, hasDispatches: hasDispatches, executeDispatch: executeDispatch, - useTouchEvents: false + supportTouch: false }; module.exports = EventPluginUtils; From 000f7ab8faac38623b751a98ca9067380da69972 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 02:49:39 +0100 Subject: [PATCH 08/31] Merge branch 'master' of https://github.com/facebook/react into HEAD Conflicts: src/event/EventPluginRegistry.js --- src/core/ReactDOMComponent.js | 18 ++++- src/core/ReactEventEmitter.js | 65 +++++++------------ .../__tests__/ReactCompositeComponent-test.js | 2 + src/core/__tests__/ReactEventEmitter-test.js | 7 +- .../ReactEventTopLevelCallback-test.js | 9 ++- .../__tests__/ReactPropTransferer-test.js | 2 + .../__tests__/ReactDOMButton-test.js | 1 + .../__tests__/ReactDOMInput-test.js | 2 +- .../__tests__/ReactDOMSelect-test.js | 2 + .../__tests__/ReactDOMTextarea-test.js | 2 + .../__tests__/ReactServerRendering-test.js | 1 + src/event/EventPluginRegistry.js | 14 ++++ 12 files changed, 71 insertions(+), 54 deletions(-) diff --git a/src/core/ReactDOMComponent.js b/src/core/ReactDOMComponent.js index e1803cc60fb5..ceb40cc309fc 100644 --- a/src/core/ReactDOMComponent.js +++ b/src/core/ReactDOMComponent.js @@ -44,6 +44,8 @@ var CONTENT_TYPES = {'string': true, 'number': true}; var STYLE = keyOf({style: null}); +var ELEMENT_NODE_TYPE = 1; + /** * @param {?object} props */ @@ -129,7 +131,13 @@ ReactDOMComponent.Mixin = { continue; } if (registrationNames[propKey]) { - listenTo(propKey, ReactMount.findReactContainerForID(this._rootNodeID)); + var container = ReactMount.findReactContainerForID(this._rootNodeID); + if (container) { + var doc = container.nodeType === ELEMENT_NODE_TYPE ? + container.ownerDocument : + container; + listenTo(propKey, doc); + } putListener(this._rootNodeID, propKey, propValue); } else { if (propKey === STYLE) { @@ -289,7 +297,13 @@ ReactDOMComponent.Mixin = { styleUpdates = nextProp; } } else if (registrationNames[propKey]) { - listenTo(propKey, ReactMount.findReactContainerForID(this._rootNodeID)); + var container = ReactMount.findReactContainerForID(this._rootNodeID); + if (container) { + var doc = container.nodeType === ELEMENT_NODE_TYPE ? + container.ownerDocument : + container; + listenTo(propKey, doc); + } putListener(this._rootNodeID, propKey, nextProp); } else if ( DOMProperty.isStandardName[propKey] || diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index 9d9298d8520f..ce9d2b700038 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -25,6 +25,7 @@ var EventPluginHub = require('EventPluginHub'); var ExecutionEnvironment = require('ExecutionEnvironment'); var ReactEventEmitterMixin = require('ReactEventEmitterMixin'); var ViewportMetrics = require('ViewportMetrics'); +var EventPluginRegistry = require('EventPluginRegistry'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); @@ -78,6 +79,7 @@ var keyOf = require('keyOf'); var alreadyListeningTo = {}; var isMonitoringScrollValue = false; +var reactTopListenersCounter = 0; /** * Traps top-level events by using event bubbling. @@ -180,36 +182,20 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { * @param {DOMDocument} contentDocument Document which owns the container */ listenTo: function(event, contentDocument) { - var mountAt; - var camelCasedEventName; - var camelCasedEventNameAlt; - var dependencies; - var dependency; - var i; - var l; - var topLevelType; - var topLevelTypes; - var registration; + var mountAt = contentDocument; + if (!mountAt._reactTopListenersID) { + mountAt._reactTopListenersID = '' + reactTopListenersCounter++; + alreadyListeningTo[mountAt._reactTopListenersID] = []; + } + var topListenersID = mountAt._reactTopListenersID; + if (!alreadyListeningTo[topListenersID][event]) { + var registrationName = ReactEventEmitter.registrationNames[event]; + var dependencies = registrationName.eventTypes[EventPluginRegistry.eventMapping[event]].dependencies; - if (!alreadyListeningTo[event]) { - mountAt = contentDocument; - camelCasedEventName = event.substr(2); - camelCasedEventNameAlt = - camelCasedEventName.charAt(0).toLowerCase() + camelCasedEventName.slice(1); - registration = ReactEventEmitter.registrationNames[event]; - if (registration && registration.eventTypes[camelCasedEventNameAlt]) { - dependencies = registration. - eventTypes[camelCasedEventNameAlt].dependencies; - } - if (dependencies) { - for (i = 0, l = dependencies.length; i < l; i++) { - dependency = dependencies[i]; - ReactEventEmitter.listenTo(dependency, contentDocument); - } - } - else { - topLevelTypes = EventConstants.topLevelTypes; - topLevelType = topLevelTypes['top' + camelCasedEventName]; + var topLevelTypes = EventConstants.topLevelTypes; + for (var i = 0, l = dependencies.length; i < l; i++) { + var dependency = dependencies[i]; + var topLevelType = topLevelTypes[dependency]; if (topLevelType === topLevelTypes.topWheel) { if (isEventSupported('wheel')) { @@ -221,25 +207,22 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { // @see http://www.quirksmode.org/dom/events/tests/scroll.html trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt); } - } - else if(topLevelType === topLevelTypes.topScroll) { + } else if (topLevelType === topLevelTypes.topScroll) { if (isEventSupported('scroll', true)) { trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt); } else { trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window); } - } - else if (topLevelType === topLevelTypes.topFocus || - topLevelType === topLevelTypes.topBlur) { + } else if (topLevelType === topLevelTypes.topFocus || + topLevelType === topLevelTypes.topBlur) { 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 + // @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt); trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt); } @@ -247,15 +230,11 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { // to make sure event listeners are only attached once alreadyListeningTo[keyOf({onBlur: null})] = true; alreadyListeningTo[keyOf({onFocus: null})] = true; - } - else { - if(camelCasedEventName === 'DoubleClick') { - camelCasedEventName = 'DblClick'; - } - trapBubbledEvent(topLevelType, camelCasedEventName.toLowerCase(), mountAt); + } else { + trapBubbledEvent(topLevelType, EventPluginRegistry.eventMapping[event], mountAt); } - alreadyListeningTo[event] = true; + alreadyListeningTo[topListenersID][event] = true; } } }, diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index 0afef2dbdaa5..11dd41bd41c0 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -78,6 +78,8 @@ describe('ReactCompositeComponent', function() { ; } }); + + delete document['_reactTopListenersID']; }); it('should support rendering to different child types over time', function() { diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index 81dbba8252e9..2d9fe4b7bed1 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -61,6 +61,7 @@ var ON_CLICK_KEY = keyOf({onClick: null}); var ON_TOUCH_TAP_KEY = keyOf({onTouchTap: null}); var ON_CHANGE_KEY = keyOf({onChange: null}); + /** * Since `ReactEventEmitter` is fairly well separated from the DOM, we can test * almost all of `ReactEventEmitter` without ever rendering anything in the DOM. @@ -100,6 +101,7 @@ describe('ReactEventEmitter', function() { EventPluginHub.injection.injectEventPluginsByName({ TapEventPlugin: TapEventPlugin }); + delete document['_reactTopListenersID']; }); it('should store a listener correctly', function() { @@ -317,10 +319,8 @@ describe('ReactEventEmitter', function() { 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); }); @@ -338,6 +338,7 @@ describe('ReactEventEmitter', function() { ReactEventEmitter.listenTo(ON_CHANGE_KEY, document); + var SCROLL_MONITORING_EVENTS_NO = 2; var setEventListeners = []; var listenCalls = EventListener.listen.argsForCall; var captureCalls = EventListener.capture.argsForCall; @@ -350,7 +351,7 @@ describe('ReactEventEmitter', function() { var dependencies = ReactEventEmitter.registrationNames[ON_CHANGE_KEY].eventTypes.change.dependencies; - expect(setEventListeners.length).toEqual(dependencies.length); + expect(setEventListeners.length).toEqual(dependencies.length + SCROLL_MONITORING_EVENTS_NO); for(i = 0, l = setEventListeners.length; i < l; i++) { expect(dependencies.indexOf(setEventListeners[i])).toBeTruthy(); diff --git a/src/core/__tests__/ReactEventTopLevelCallback-test.js b/src/core/__tests__/ReactEventTopLevelCallback-test.js index 0d8b7dab48ed..14b02ac1d099 100644 --- a/src/core/__tests__/ReactEventTopLevelCallback-test.js +++ b/src/core/__tests__/ReactEventTopLevelCallback-test.js @@ -22,7 +22,8 @@ require('mock-modules') .dontMock('ReactEventTopLevelCallback') .dontMock('ReactMount') .dontMock('ReactInstanceHandles') - .dontMock('ReactDOM'); + .dontMock('ReactDOM') + .mock('ReactEventEmitter'); var EVENT_TARGET_PARAM = 1; @@ -30,16 +31,14 @@ describe('ReactEventTopLevelCallback', function() { var React; var ReactEventTopLevelCallback; var ReactDOM; - var ReactEventEmitter; + var ReactEventEmitter; // mocked beforeEach(function() { require('mock-modules').dumpCache(); React = require('React'); ReactEventTopLevelCallback = require('ReactEventTopLevelCallback'); ReactDOM = require('ReactDOM'); - ReactEventEmitter = require('ReactEventEmitter'); - - ReactEventEmitter.handleTopLevel = mocks.getMockFunction(); + ReactEventEmitter = require('ReactEventEmitter'); // mocked }); describe('Propagation', function() { diff --git a/src/core/__tests__/ReactPropTransferer-test.js b/src/core/__tests__/ReactPropTransferer-test.js index 1854ef7e959c..76de37f9c6d5 100644 --- a/src/core/__tests__/ReactPropTransferer-test.js +++ b/src/core/__tests__/ReactPropTransferer-test.js @@ -44,6 +44,8 @@ describe('ReactPropTransferer', function() { ); } }); + + delete document['_reactTopListenersID']; }); it('should leave explicitly specified properties intact', function() { diff --git a/src/dom/components/__tests__/ReactDOMButton-test.js b/src/dom/components/__tests__/ReactDOMButton-test.js index 21a000018cb0..43b96122a64d 100644 --- a/src/dom/components/__tests__/ReactDOMButton-test.js +++ b/src/dom/components/__tests__/ReactDOMButton-test.js @@ -49,6 +49,7 @@ describe('ReactDOMButton', function() { beforeEach(function() { React = require('React'); ReactTestUtils = require('ReactTestUtils'); + delete document['_reactTopListenersID']; }); it('should forward clicks when it starts out not disabled', function() { diff --git a/src/dom/components/__tests__/ReactDOMInput-test.js b/src/dom/components/__tests__/ReactDOMInput-test.js index 0ff9ee05f1ad..71a271216f37 100644 --- a/src/dom/components/__tests__/ReactDOMInput-test.js +++ b/src/dom/components/__tests__/ReactDOMInput-test.js @@ -35,7 +35,7 @@ describe('ReactDOMInput', function() { React = require('React'); ReactLink = require('ReactLink'); ReactTestUtils = require('ReactTestUtils'); - + delete document['_reactTopListenersID']; renderTextInput = function(component) { var stub = ReactTestUtils.renderIntoDocument(component); var node = stub.getDOMNode(); diff --git a/src/dom/components/__tests__/ReactDOMSelect-test.js b/src/dom/components/__tests__/ReactDOMSelect-test.js index 6bbe58ca2aa7..db15dc76a1d6 100644 --- a/src/dom/components/__tests__/ReactDOMSelect-test.js +++ b/src/dom/components/__tests__/ReactDOMSelect-test.js @@ -40,6 +40,8 @@ describe('ReactDOMSelect', function() { var node = stub.getDOMNode(); return node; }; + + delete document['_reactTopListenersID']; }); it('should allow setting `defaultValue`', function() { diff --git a/src/dom/components/__tests__/ReactDOMTextarea-test.js b/src/dom/components/__tests__/ReactDOMTextarea-test.js index aaca8ea596b5..3a1760a9d5ab 100644 --- a/src/dom/components/__tests__/ReactDOMTextarea-test.js +++ b/src/dom/components/__tests__/ReactDOMTextarea-test.js @@ -42,6 +42,8 @@ describe('ReactDOMTextarea', function() { node.value = node.innerHTML; return node; }; + + delete document['_reactTopListenersID']; }); it('should allow setting `defaultValue`', function() { diff --git a/src/environment/__tests__/ReactServerRendering-test.js b/src/environment/__tests__/ReactServerRendering-test.js index c208eadc569c..20bce375e936 100644 --- a/src/environment/__tests__/ReactServerRendering-test.js +++ b/src/environment/__tests__/ReactServerRendering-test.js @@ -48,6 +48,7 @@ describe('ReactServerRendering', function() { ExecutionEnvironment.canUseDOM = false; ReactServerRendering = require('ReactServerRendering'); ReactMarkupChecksum = require('ReactMarkupChecksum'); + delete document['_reactTopListenersID']; }); it('should generate simple markup', function() { diff --git a/src/event/EventPluginRegistry.js b/src/event/EventPluginRegistry.js index 14a2fcb024af..0d0016a94f94 100644 --- a/src/event/EventPluginRegistry.js +++ b/src/event/EventPluginRegistry.js @@ -62,6 +62,15 @@ function recomputePluginOrdering() { EventPluginRegistry.plugins[pluginIndex] = PluginModule; var publishedEvents = PluginModule.eventTypes; for (var eventName in publishedEvents) { + var phasedRegistrationNames = publishedEvents[eventName].phasedRegistrationNames; + var domEventName; + if (phasedRegistrationNames) { + domEventName = phasedRegistrationNames.bubbled; + } else { + domEventName = publishedEvents[eventName].registrationName; + } + EventPluginRegistry.eventMapping[domEventName] = eventName; + invariant( publishEventForPlugin(publishedEvents[eventName], PluginModule), 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.', @@ -138,6 +147,11 @@ var EventPluginRegistry = { */ registrationNamesKeys: [], + /** + * Mapping from on{EventName} to + */ + eventMapping: {}, + /** * Injects an ordering of plugins (by plugin name). This allows the ordering * to be decoupled from injection of the actual plugins so that ordering is From 1336888fd44ab2f1e2248aa6888a02674f800cc4 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 03:00:17 +0100 Subject: [PATCH 09/31] Revert "Merge branch 'master' of https://github.com/facebook/react into on-demand-events" This reverts commit 3bec9536de57f4bb9fe1b862b3dd806a8ea07fbf. --- src/core/ReactDOMComponent.js | 18 +---- src/core/ReactEventEmitter.js | 65 ++++++++++++------- .../__tests__/ReactCompositeComponent-test.js | 2 - src/core/__tests__/ReactEventEmitter-test.js | 6 +- .../ReactEventTopLevelCallback-test.js | 9 +-- .../__tests__/ReactPropTransferer-test.js | 2 - .../__tests__/ReactDOMButton-test.js | 1 - .../__tests__/ReactDOMInput-test.js | 2 +- .../__tests__/ReactDOMSelect-test.js | 2 - .../__tests__/ReactDOMTextarea-test.js | 2 - .../__tests__/ReactServerRendering-test.js | 1 - src/event/EventPluginRegistry.js | 14 ---- 12 files changed, 54 insertions(+), 70 deletions(-) diff --git a/src/core/ReactDOMComponent.js b/src/core/ReactDOMComponent.js index ceb40cc309fc..e1803cc60fb5 100644 --- a/src/core/ReactDOMComponent.js +++ b/src/core/ReactDOMComponent.js @@ -44,8 +44,6 @@ var CONTENT_TYPES = {'string': true, 'number': true}; var STYLE = keyOf({style: null}); -var ELEMENT_NODE_TYPE = 1; - /** * @param {?object} props */ @@ -131,13 +129,7 @@ ReactDOMComponent.Mixin = { continue; } if (registrationNames[propKey]) { - var container = ReactMount.findReactContainerForID(this._rootNodeID); - if (container) { - var doc = container.nodeType === ELEMENT_NODE_TYPE ? - container.ownerDocument : - container; - listenTo(propKey, doc); - } + listenTo(propKey, ReactMount.findReactContainerForID(this._rootNodeID)); putListener(this._rootNodeID, propKey, propValue); } else { if (propKey === STYLE) { @@ -297,13 +289,7 @@ ReactDOMComponent.Mixin = { styleUpdates = nextProp; } } else if (registrationNames[propKey]) { - var container = ReactMount.findReactContainerForID(this._rootNodeID); - if (container) { - var doc = container.nodeType === ELEMENT_NODE_TYPE ? - container.ownerDocument : - container; - listenTo(propKey, doc); - } + listenTo(propKey, ReactMount.findReactContainerForID(this._rootNodeID)); putListener(this._rootNodeID, propKey, nextProp); } else if ( DOMProperty.isStandardName[propKey] || diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index ce9d2b700038..9d9298d8520f 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -25,7 +25,6 @@ var EventPluginHub = require('EventPluginHub'); var ExecutionEnvironment = require('ExecutionEnvironment'); var ReactEventEmitterMixin = require('ReactEventEmitterMixin'); var ViewportMetrics = require('ViewportMetrics'); -var EventPluginRegistry = require('EventPluginRegistry'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); @@ -79,7 +78,6 @@ var keyOf = require('keyOf'); var alreadyListeningTo = {}; var isMonitoringScrollValue = false; -var reactTopListenersCounter = 0; /** * Traps top-level events by using event bubbling. @@ -182,20 +180,36 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { * @param {DOMDocument} contentDocument Document which owns the container */ listenTo: function(event, contentDocument) { - var mountAt = contentDocument; - if (!mountAt._reactTopListenersID) { - mountAt._reactTopListenersID = '' + reactTopListenersCounter++; - alreadyListeningTo[mountAt._reactTopListenersID] = []; - } - var topListenersID = mountAt._reactTopListenersID; - if (!alreadyListeningTo[topListenersID][event]) { - var registrationName = ReactEventEmitter.registrationNames[event]; - var dependencies = registrationName.eventTypes[EventPluginRegistry.eventMapping[event]].dependencies; + var mountAt; + var camelCasedEventName; + var camelCasedEventNameAlt; + var dependencies; + var dependency; + var i; + var l; + var topLevelType; + var topLevelTypes; + var registration; - var topLevelTypes = EventConstants.topLevelTypes; - for (var i = 0, l = dependencies.length; i < l; i++) { - var dependency = dependencies[i]; - var topLevelType = topLevelTypes[dependency]; + if (!alreadyListeningTo[event]) { + mountAt = contentDocument; + camelCasedEventName = event.substr(2); + camelCasedEventNameAlt = + camelCasedEventName.charAt(0).toLowerCase() + camelCasedEventName.slice(1); + registration = ReactEventEmitter.registrationNames[event]; + if (registration && registration.eventTypes[camelCasedEventNameAlt]) { + dependencies = registration. + eventTypes[camelCasedEventNameAlt].dependencies; + } + if (dependencies) { + for (i = 0, l = dependencies.length; i < l; i++) { + dependency = dependencies[i]; + ReactEventEmitter.listenTo(dependency, contentDocument); + } + } + else { + topLevelTypes = EventConstants.topLevelTypes; + topLevelType = topLevelTypes['top' + camelCasedEventName]; if (topLevelType === topLevelTypes.topWheel) { if (isEventSupported('wheel')) { @@ -207,22 +221,25 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { // @see http://www.quirksmode.org/dom/events/tests/scroll.html trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt); } - } else if (topLevelType === topLevelTypes.topScroll) { + } + else if(topLevelType === topLevelTypes.topScroll) { if (isEventSupported('scroll', true)) { trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt); } else { trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window); } - } else if (topLevelType === topLevelTypes.topFocus || - topLevelType === topLevelTypes.topBlur) { + } + else if (topLevelType === topLevelTypes.topFocus || + topLevelType === topLevelTypes.topBlur) { 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 + // @see + // http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt); trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt); } @@ -230,11 +247,15 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { // to make sure event listeners are only attached once alreadyListeningTo[keyOf({onBlur: null})] = true; alreadyListeningTo[keyOf({onFocus: null})] = true; - } else { - trapBubbledEvent(topLevelType, EventPluginRegistry.eventMapping[event], mountAt); + } + else { + if(camelCasedEventName === 'DoubleClick') { + camelCasedEventName = 'DblClick'; + } + trapBubbledEvent(topLevelType, camelCasedEventName.toLowerCase(), mountAt); } - alreadyListeningTo[topListenersID][event] = true; + alreadyListeningTo[event] = true; } } }, diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index 11dd41bd41c0..0afef2dbdaa5 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -78,8 +78,6 @@ describe('ReactCompositeComponent', function() { ; } }); - - delete document['_reactTopListenersID']; }); it('should support rendering to different child types over time', function() { diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index 2d9fe4b7bed1..fd887356dcce 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -101,7 +101,6 @@ describe('ReactEventEmitter', function() { EventPluginHub.injection.injectEventPluginsByName({ TapEventPlugin: TapEventPlugin }); - delete document['_reactTopListenersID']; }); it('should store a listener correctly', function() { @@ -319,8 +318,10 @@ describe('ReactEventEmitter', function() { 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); }); @@ -338,7 +339,6 @@ describe('ReactEventEmitter', function() { ReactEventEmitter.listenTo(ON_CHANGE_KEY, document); - var SCROLL_MONITORING_EVENTS_NO = 2; var setEventListeners = []; var listenCalls = EventListener.listen.argsForCall; var captureCalls = EventListener.capture.argsForCall; @@ -351,7 +351,7 @@ describe('ReactEventEmitter', function() { var dependencies = ReactEventEmitter.registrationNames[ON_CHANGE_KEY].eventTypes.change.dependencies; - expect(setEventListeners.length).toEqual(dependencies.length + SCROLL_MONITORING_EVENTS_NO); + expect(setEventListeners.length).toEqual(dependencies.length); for(i = 0, l = setEventListeners.length; i < l; i++) { expect(dependencies.indexOf(setEventListeners[i])).toBeTruthy(); diff --git a/src/core/__tests__/ReactEventTopLevelCallback-test.js b/src/core/__tests__/ReactEventTopLevelCallback-test.js index 14b02ac1d099..0d8b7dab48ed 100644 --- a/src/core/__tests__/ReactEventTopLevelCallback-test.js +++ b/src/core/__tests__/ReactEventTopLevelCallback-test.js @@ -22,8 +22,7 @@ require('mock-modules') .dontMock('ReactEventTopLevelCallback') .dontMock('ReactMount') .dontMock('ReactInstanceHandles') - .dontMock('ReactDOM') - .mock('ReactEventEmitter'); + .dontMock('ReactDOM'); var EVENT_TARGET_PARAM = 1; @@ -31,14 +30,16 @@ describe('ReactEventTopLevelCallback', function() { var React; var ReactEventTopLevelCallback; var ReactDOM; - var ReactEventEmitter; // mocked + var ReactEventEmitter; beforeEach(function() { require('mock-modules').dumpCache(); React = require('React'); ReactEventTopLevelCallback = require('ReactEventTopLevelCallback'); ReactDOM = require('ReactDOM'); - ReactEventEmitter = require('ReactEventEmitter'); // mocked + ReactEventEmitter = require('ReactEventEmitter'); + + ReactEventEmitter.handleTopLevel = mocks.getMockFunction(); }); describe('Propagation', function() { diff --git a/src/core/__tests__/ReactPropTransferer-test.js b/src/core/__tests__/ReactPropTransferer-test.js index 76de37f9c6d5..1854ef7e959c 100644 --- a/src/core/__tests__/ReactPropTransferer-test.js +++ b/src/core/__tests__/ReactPropTransferer-test.js @@ -44,8 +44,6 @@ describe('ReactPropTransferer', function() { ); } }); - - delete document['_reactTopListenersID']; }); it('should leave explicitly specified properties intact', function() { diff --git a/src/dom/components/__tests__/ReactDOMButton-test.js b/src/dom/components/__tests__/ReactDOMButton-test.js index 43b96122a64d..21a000018cb0 100644 --- a/src/dom/components/__tests__/ReactDOMButton-test.js +++ b/src/dom/components/__tests__/ReactDOMButton-test.js @@ -49,7 +49,6 @@ describe('ReactDOMButton', function() { beforeEach(function() { React = require('React'); ReactTestUtils = require('ReactTestUtils'); - delete document['_reactTopListenersID']; }); it('should forward clicks when it starts out not disabled', function() { diff --git a/src/dom/components/__tests__/ReactDOMInput-test.js b/src/dom/components/__tests__/ReactDOMInput-test.js index 71a271216f37..0ff9ee05f1ad 100644 --- a/src/dom/components/__tests__/ReactDOMInput-test.js +++ b/src/dom/components/__tests__/ReactDOMInput-test.js @@ -35,7 +35,7 @@ describe('ReactDOMInput', function() { React = require('React'); ReactLink = require('ReactLink'); ReactTestUtils = require('ReactTestUtils'); - delete document['_reactTopListenersID']; + renderTextInput = function(component) { var stub = ReactTestUtils.renderIntoDocument(component); var node = stub.getDOMNode(); diff --git a/src/dom/components/__tests__/ReactDOMSelect-test.js b/src/dom/components/__tests__/ReactDOMSelect-test.js index db15dc76a1d6..6bbe58ca2aa7 100644 --- a/src/dom/components/__tests__/ReactDOMSelect-test.js +++ b/src/dom/components/__tests__/ReactDOMSelect-test.js @@ -40,8 +40,6 @@ describe('ReactDOMSelect', function() { var node = stub.getDOMNode(); return node; }; - - delete document['_reactTopListenersID']; }); it('should allow setting `defaultValue`', function() { diff --git a/src/dom/components/__tests__/ReactDOMTextarea-test.js b/src/dom/components/__tests__/ReactDOMTextarea-test.js index 3a1760a9d5ab..aaca8ea596b5 100644 --- a/src/dom/components/__tests__/ReactDOMTextarea-test.js +++ b/src/dom/components/__tests__/ReactDOMTextarea-test.js @@ -42,8 +42,6 @@ describe('ReactDOMTextarea', function() { node.value = node.innerHTML; return node; }; - - delete document['_reactTopListenersID']; }); it('should allow setting `defaultValue`', function() { diff --git a/src/environment/__tests__/ReactServerRendering-test.js b/src/environment/__tests__/ReactServerRendering-test.js index 20bce375e936..c208eadc569c 100644 --- a/src/environment/__tests__/ReactServerRendering-test.js +++ b/src/environment/__tests__/ReactServerRendering-test.js @@ -48,7 +48,6 @@ describe('ReactServerRendering', function() { ExecutionEnvironment.canUseDOM = false; ReactServerRendering = require('ReactServerRendering'); ReactMarkupChecksum = require('ReactMarkupChecksum'); - delete document['_reactTopListenersID']; }); it('should generate simple markup', function() { diff --git a/src/event/EventPluginRegistry.js b/src/event/EventPluginRegistry.js index 0d0016a94f94..14a2fcb024af 100644 --- a/src/event/EventPluginRegistry.js +++ b/src/event/EventPluginRegistry.js @@ -62,15 +62,6 @@ function recomputePluginOrdering() { EventPluginRegistry.plugins[pluginIndex] = PluginModule; var publishedEvents = PluginModule.eventTypes; for (var eventName in publishedEvents) { - var phasedRegistrationNames = publishedEvents[eventName].phasedRegistrationNames; - var domEventName; - if (phasedRegistrationNames) { - domEventName = phasedRegistrationNames.bubbled; - } else { - domEventName = publishedEvents[eventName].registrationName; - } - EventPluginRegistry.eventMapping[domEventName] = eventName; - invariant( publishEventForPlugin(publishedEvents[eventName], PluginModule), 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.', @@ -147,11 +138,6 @@ var EventPluginRegistry = { */ registrationNamesKeys: [], - /** - * Mapping from on{EventName} to - */ - eventMapping: {}, - /** * Injects an ordering of plugins (by plugin name). This allows the ordering * to be decoupled from injection of the actual plugins so that ordering is From 7d704c34678339eedef9910f5451d5517e30d0e5 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 03:18:26 +0100 Subject: [PATCH 10/31] Changes related to balpert's comments --- src/core/React.js | 3 +- src/core/ReactDOMComponent.js | 10 ++- src/core/ReactEventEmitter.js | 65 +++++++------------ src/core/__tests__/ReactComponent-test.js | 4 -- .../__tests__/ReactCompositeComponent-test.js | 2 + src/core/__tests__/ReactEventEmitter-test.js | 6 +- .../ReactEventTopLevelCallback-test.js | 9 ++- .../__tests__/ReactPropTransferer-test.js | 2 + .../__tests__/ReactDOMButton-test.js | 1 + .../__tests__/ReactDOMInput-test.js | 2 +- .../__tests__/ReactDOMSelect-test.js | 2 + .../__tests__/ReactDOMTextarea-test.js | 2 + .../__tests__/ReactServerRendering-test.js | 1 + src/event/EventPluginRegistry.js | 14 ++++ src/event/EventPluginUtils.js | 2 +- 15 files changed, 66 insertions(+), 59 deletions(-) diff --git a/src/core/React.js b/src/core/React.js index 42bcbaa6e5f3..48afaab1da9e 100644 --- a/src/core/React.js +++ b/src/core/React.js @@ -18,6 +18,7 @@ "use strict"; +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) { - EventPluginUtils.supportTouch = shouldUseTouch; + EventPluginUtils.useTouchEvents = shouldUseTouch; }, createClass: ReactCompositeComponent.createClass, constructAndRenderComponent: ReactMount.constructAndRenderComponent, diff --git a/src/core/ReactDOMComponent.js b/src/core/ReactDOMComponent.js index e1803cc60fb5..c74ad7fea690 100644 --- a/src/core/ReactDOMComponent.js +++ b/src/core/ReactDOMComponent.js @@ -44,6 +44,8 @@ var CONTENT_TYPES = {'string': true, 'number': true}; var STYLE = keyOf({style: null}); +var ELEMENT_NODE_TYPE = 1; + /** * @param {?object} props */ @@ -129,7 +131,13 @@ ReactDOMComponent.Mixin = { continue; } if (registrationNames[propKey]) { - listenTo(propKey, ReactMount.findReactContainerForID(this._rootNodeID)); + var container = ReactMount.findReactContainerForID(this._rootNodeID); + if (container) { + var doc = container.nodeType === ELEMENT_NODE_TYPE ? + container.ownerDocument : + container; + listenTo(propKey, doc); + } putListener(this._rootNodeID, propKey, propValue); } else { if (propKey === STYLE) { diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index 9d9298d8520f..ce9d2b700038 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -25,6 +25,7 @@ var EventPluginHub = require('EventPluginHub'); var ExecutionEnvironment = require('ExecutionEnvironment'); var ReactEventEmitterMixin = require('ReactEventEmitterMixin'); var ViewportMetrics = require('ViewportMetrics'); +var EventPluginRegistry = require('EventPluginRegistry'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); @@ -78,6 +79,7 @@ var keyOf = require('keyOf'); var alreadyListeningTo = {}; var isMonitoringScrollValue = false; +var reactTopListenersCounter = 0; /** * Traps top-level events by using event bubbling. @@ -180,36 +182,20 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { * @param {DOMDocument} contentDocument Document which owns the container */ listenTo: function(event, contentDocument) { - var mountAt; - var camelCasedEventName; - var camelCasedEventNameAlt; - var dependencies; - var dependency; - var i; - var l; - var topLevelType; - var topLevelTypes; - var registration; + var mountAt = contentDocument; + if (!mountAt._reactTopListenersID) { + mountAt._reactTopListenersID = '' + reactTopListenersCounter++; + alreadyListeningTo[mountAt._reactTopListenersID] = []; + } + var topListenersID = mountAt._reactTopListenersID; + if (!alreadyListeningTo[topListenersID][event]) { + var registrationName = ReactEventEmitter.registrationNames[event]; + var dependencies = registrationName.eventTypes[EventPluginRegistry.eventMapping[event]].dependencies; - if (!alreadyListeningTo[event]) { - mountAt = contentDocument; - camelCasedEventName = event.substr(2); - camelCasedEventNameAlt = - camelCasedEventName.charAt(0).toLowerCase() + camelCasedEventName.slice(1); - registration = ReactEventEmitter.registrationNames[event]; - if (registration && registration.eventTypes[camelCasedEventNameAlt]) { - dependencies = registration. - eventTypes[camelCasedEventNameAlt].dependencies; - } - if (dependencies) { - for (i = 0, l = dependencies.length; i < l; i++) { - dependency = dependencies[i]; - ReactEventEmitter.listenTo(dependency, contentDocument); - } - } - else { - topLevelTypes = EventConstants.topLevelTypes; - topLevelType = topLevelTypes['top' + camelCasedEventName]; + var topLevelTypes = EventConstants.topLevelTypes; + for (var i = 0, l = dependencies.length; i < l; i++) { + var dependency = dependencies[i]; + var topLevelType = topLevelTypes[dependency]; if (topLevelType === topLevelTypes.topWheel) { if (isEventSupported('wheel')) { @@ -221,25 +207,22 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { // @see http://www.quirksmode.org/dom/events/tests/scroll.html trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt); } - } - else if(topLevelType === topLevelTypes.topScroll) { + } else if (topLevelType === topLevelTypes.topScroll) { if (isEventSupported('scroll', true)) { trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt); } else { trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window); } - } - else if (topLevelType === topLevelTypes.topFocus || - topLevelType === topLevelTypes.topBlur) { + } else if (topLevelType === topLevelTypes.topFocus || + topLevelType === topLevelTypes.topBlur) { 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 + // @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt); trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt); } @@ -247,15 +230,11 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { // to make sure event listeners are only attached once alreadyListeningTo[keyOf({onBlur: null})] = true; alreadyListeningTo[keyOf({onFocus: null})] = true; - } - else { - if(camelCasedEventName === 'DoubleClick') { - camelCasedEventName = 'DblClick'; - } - trapBubbledEvent(topLevelType, camelCasedEventName.toLowerCase(), mountAt); + } else { + trapBubbledEvent(topLevelType, EventPluginRegistry.eventMapping[event], mountAt); } - alreadyListeningTo[event] = true; + alreadyListeningTo[topListenersID][event] = true; } } }, diff --git a/src/core/__tests__/ReactComponent-test.js b/src/core/__tests__/ReactComponent-test.js index 062014ec7d9b..4c1b54e64a21 100644 --- a/src/core/__tests__/ReactComponent-test.js +++ b/src/core/__tests__/ReactComponent-test.js @@ -209,8 +209,4 @@ describe('ReactComponent', function() { expect(root.refs.child.refs.span._mountDepth).toBe(6); }); - it("should listen to events on demand", function() { - // TODO + change component version - }); - }); diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index 0afef2dbdaa5..11dd41bd41c0 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -78,6 +78,8 @@ describe('ReactCompositeComponent', function() { ; } }); + + delete document['_reactTopListenersID']; }); it('should support rendering to different child types over time', function() { diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index fd887356dcce..2d9fe4b7bed1 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -101,6 +101,7 @@ describe('ReactEventEmitter', function() { EventPluginHub.injection.injectEventPluginsByName({ TapEventPlugin: TapEventPlugin }); + delete document['_reactTopListenersID']; }); it('should store a listener correctly', function() { @@ -318,10 +319,8 @@ describe('ReactEventEmitter', function() { 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); }); @@ -339,6 +338,7 @@ describe('ReactEventEmitter', function() { ReactEventEmitter.listenTo(ON_CHANGE_KEY, document); + var SCROLL_MONITORING_EVENTS_NO = 2; var setEventListeners = []; var listenCalls = EventListener.listen.argsForCall; var captureCalls = EventListener.capture.argsForCall; @@ -351,7 +351,7 @@ describe('ReactEventEmitter', function() { var dependencies = ReactEventEmitter.registrationNames[ON_CHANGE_KEY].eventTypes.change.dependencies; - expect(setEventListeners.length).toEqual(dependencies.length); + expect(setEventListeners.length).toEqual(dependencies.length + SCROLL_MONITORING_EVENTS_NO); for(i = 0, l = setEventListeners.length; i < l; i++) { expect(dependencies.indexOf(setEventListeners[i])).toBeTruthy(); diff --git a/src/core/__tests__/ReactEventTopLevelCallback-test.js b/src/core/__tests__/ReactEventTopLevelCallback-test.js index 0d8b7dab48ed..14b02ac1d099 100644 --- a/src/core/__tests__/ReactEventTopLevelCallback-test.js +++ b/src/core/__tests__/ReactEventTopLevelCallback-test.js @@ -22,7 +22,8 @@ require('mock-modules') .dontMock('ReactEventTopLevelCallback') .dontMock('ReactMount') .dontMock('ReactInstanceHandles') - .dontMock('ReactDOM'); + .dontMock('ReactDOM') + .mock('ReactEventEmitter'); var EVENT_TARGET_PARAM = 1; @@ -30,16 +31,14 @@ describe('ReactEventTopLevelCallback', function() { var React; var ReactEventTopLevelCallback; var ReactDOM; - var ReactEventEmitter; + var ReactEventEmitter; // mocked beforeEach(function() { require('mock-modules').dumpCache(); React = require('React'); ReactEventTopLevelCallback = require('ReactEventTopLevelCallback'); ReactDOM = require('ReactDOM'); - ReactEventEmitter = require('ReactEventEmitter'); - - ReactEventEmitter.handleTopLevel = mocks.getMockFunction(); + ReactEventEmitter = require('ReactEventEmitter'); // mocked }); describe('Propagation', function() { diff --git a/src/core/__tests__/ReactPropTransferer-test.js b/src/core/__tests__/ReactPropTransferer-test.js index 1854ef7e959c..76de37f9c6d5 100644 --- a/src/core/__tests__/ReactPropTransferer-test.js +++ b/src/core/__tests__/ReactPropTransferer-test.js @@ -44,6 +44,8 @@ describe('ReactPropTransferer', function() { ); } }); + + delete document['_reactTopListenersID']; }); it('should leave explicitly specified properties intact', function() { diff --git a/src/dom/components/__tests__/ReactDOMButton-test.js b/src/dom/components/__tests__/ReactDOMButton-test.js index 21a000018cb0..43b96122a64d 100644 --- a/src/dom/components/__tests__/ReactDOMButton-test.js +++ b/src/dom/components/__tests__/ReactDOMButton-test.js @@ -49,6 +49,7 @@ describe('ReactDOMButton', function() { beforeEach(function() { React = require('React'); ReactTestUtils = require('ReactTestUtils'); + delete document['_reactTopListenersID']; }); it('should forward clicks when it starts out not disabled', function() { diff --git a/src/dom/components/__tests__/ReactDOMInput-test.js b/src/dom/components/__tests__/ReactDOMInput-test.js index 0ff9ee05f1ad..71a271216f37 100644 --- a/src/dom/components/__tests__/ReactDOMInput-test.js +++ b/src/dom/components/__tests__/ReactDOMInput-test.js @@ -35,7 +35,7 @@ describe('ReactDOMInput', function() { React = require('React'); ReactLink = require('ReactLink'); ReactTestUtils = require('ReactTestUtils'); - + delete document['_reactTopListenersID']; renderTextInput = function(component) { var stub = ReactTestUtils.renderIntoDocument(component); var node = stub.getDOMNode(); diff --git a/src/dom/components/__tests__/ReactDOMSelect-test.js b/src/dom/components/__tests__/ReactDOMSelect-test.js index 6bbe58ca2aa7..db15dc76a1d6 100644 --- a/src/dom/components/__tests__/ReactDOMSelect-test.js +++ b/src/dom/components/__tests__/ReactDOMSelect-test.js @@ -40,6 +40,8 @@ describe('ReactDOMSelect', function() { var node = stub.getDOMNode(); return node; }; + + delete document['_reactTopListenersID']; }); it('should allow setting `defaultValue`', function() { diff --git a/src/dom/components/__tests__/ReactDOMTextarea-test.js b/src/dom/components/__tests__/ReactDOMTextarea-test.js index aaca8ea596b5..3a1760a9d5ab 100644 --- a/src/dom/components/__tests__/ReactDOMTextarea-test.js +++ b/src/dom/components/__tests__/ReactDOMTextarea-test.js @@ -42,6 +42,8 @@ describe('ReactDOMTextarea', function() { node.value = node.innerHTML; return node; }; + + delete document['_reactTopListenersID']; }); it('should allow setting `defaultValue`', function() { diff --git a/src/environment/__tests__/ReactServerRendering-test.js b/src/environment/__tests__/ReactServerRendering-test.js index c208eadc569c..20bce375e936 100644 --- a/src/environment/__tests__/ReactServerRendering-test.js +++ b/src/environment/__tests__/ReactServerRendering-test.js @@ -48,6 +48,7 @@ describe('ReactServerRendering', function() { ExecutionEnvironment.canUseDOM = false; ReactServerRendering = require('ReactServerRendering'); ReactMarkupChecksum = require('ReactMarkupChecksum'); + delete document['_reactTopListenersID']; }); it('should generate simple markup', function() { diff --git a/src/event/EventPluginRegistry.js b/src/event/EventPluginRegistry.js index 14a2fcb024af..0d0016a94f94 100644 --- a/src/event/EventPluginRegistry.js +++ b/src/event/EventPluginRegistry.js @@ -62,6 +62,15 @@ function recomputePluginOrdering() { EventPluginRegistry.plugins[pluginIndex] = PluginModule; var publishedEvents = PluginModule.eventTypes; for (var eventName in publishedEvents) { + var phasedRegistrationNames = publishedEvents[eventName].phasedRegistrationNames; + var domEventName; + if (phasedRegistrationNames) { + domEventName = phasedRegistrationNames.bubbled; + } else { + domEventName = publishedEvents[eventName].registrationName; + } + EventPluginRegistry.eventMapping[domEventName] = eventName; + invariant( publishEventForPlugin(publishedEvents[eventName], PluginModule), 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.', @@ -138,6 +147,11 @@ var EventPluginRegistry = { */ registrationNamesKeys: [], + /** + * Mapping from on{EventName} to + */ + eventMapping: {}, + /** * Injects an ordering of plugins (by plugin name). This allows the ordering * to be decoupled from injection of the actual plugins so that ordering is diff --git a/src/event/EventPluginUtils.js b/src/event/EventPluginUtils.js index ab5faaf6b36b..d7bee7510986 100644 --- a/src/event/EventPluginUtils.js +++ b/src/event/EventPluginUtils.js @@ -180,7 +180,7 @@ var EventPluginUtils = { executeDirectDispatch: executeDirectDispatch, hasDispatches: hasDispatches, executeDispatch: executeDispatch, - supportTouch: false + useTouchEvents: false }; module.exports = EventPluginUtils; From 4268841c37df7f1f0e8c5a9d811d4a786a78893e Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 08:18:37 +0100 Subject: [PATCH 11/31] Revert "Merge branch 'master' of https://github.com/facebook/react into on-demand-events" This reverts commit d3a880d1af9c09b7f7c1b163bc318a7e15220d9c. --- README.md | 18 +-- docs/_data/nav_tips.yml | 2 + docs/_js/examples/timer.js | 2 +- docs/blog/index.html | 2 +- docs/docs/06-forms.md | 15 ++ docs/downloads.md | 4 +- docs/index.md | 20 +-- docs/tips/13-false-in-jsx.md | 1 + .../tips/14-communicate-between-components.md | 40 +++++ examples/basic-jsx/index.html | 5 +- .../transitions/ReactTransitionGroup.js | 1 + .../transitions/ReactTransitionKeySet.js | 3 + .../__tests__/ReactTransitionGroup-test.js | 21 ++- .../__tests__/ReactTransitionKeySet-test.js | 26 +++ src/core/ReactCompositeComponent.js | 28 ++-- .../__tests__/ReactCompositeComponent-test.js | 20 +++ .../__tests__/ReactRenderDocument-test.js | 35 +--- src/event/EventPluginRegistry.js | 7 - .../__tests__/EventPluginRegistry-test.js | 4 +- src/eventPlugins/ChangeEventPlugin.js | 11 +- src/eventPlugins/CompositionEventPlugin.js | 36 ++--- src/eventPlugins/EnterLeaveEventPlugin.js | 8 +- src/eventPlugins/SelectEventPlugin.js | 12 +- src/eventPlugins/SimpleEventPlugin.js | 150 ++++-------------- src/eventPlugins/TapEventPlugin.js | 18 +-- src/test/getTestDocument.js | 22 +-- .../core/dom/getUnboundedScrollPosition.js | 14 ++ 27 files changed, 271 insertions(+), 254 deletions(-) create mode 100644 docs/tips/14-communicate-between-components.md diff --git a/README.md b/README.md index 25d79f1e1d12..157457406c3a 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,21 @@ React is a JavaScript library for building user interfaces. -* **Declarative:** React uses a declarative paradigm that makes it easier to reason about your application. -* **Efficient:** React computes the minimal set of changes necessary to keep your DOM up-to-date. -* **Flexible:** React works with the libraries and frameworks that you already know. +* **Just the UI:** Lots of people use React as the V in MVC. Since React makes no assumptions about the rest of your technology stack, it's easy to try it out on a small feature in an existing project. +* **Virtual DOM:** React uses a *virtual DOM* diff implementation for ultra-high performance. It can also render on the server using Node.js — no heavy browser DOM required. +* **Data flow:** React implements one-way reactive data flow which reduces boilerplate and is easier to reason about than traditional data binding. [Learn how to use React in your own project.](http://facebook.github.io/react/docs/getting-started.html) ## Examples -We have several examples [on the website](http://facebook.github.io/react). Here is the first one to get you started: +We have several examples [on the website](http://facebook.github.io/react/). Here is the first one to get you started: ```js /** @jsx React.DOM */ var HelloMessage = React.createClass({ render: function() { - return
{'Hello ' + this.props.name}
; + return
Hello {this.props.name}
; } }); @@ -51,7 +51,7 @@ bower install --save react ## Contribute -The main purpose of this repository is to continue to evolve React core, making it faster and easier to use. If you're interested in helping with that, then keep reading. If you're not interested in helping right now that's ok too :) Any feedback you have about using React would be greatly appreciated. +The main purpose of this repository is to continue to evolve React core, making it faster and easier to use. If you're interested in helping with that, then keep reading. If you're not interested in helping right now that's ok too. :) Any feedback you have about using React would be greatly appreciated. ### Building Your Copy of React @@ -81,12 +81,10 @@ At this point, you should now have a `build/` directory populated with everythin We use grunt to automate many tasks. Run `grunt -h` to see a mostly complete listing. The important ones to know: ```sh -# Create test build & run tests with PhantomJS +# Build and run tests with PhantomJS grunt test -# Lint the core library code with JSHint +# Lint the code with JSHint grunt lint -# Lint package code -grunt lint:package # Wipe out build directory grunt clean ``` diff --git a/docs/_data/nav_tips.yml b/docs/_data/nav_tips.yml index c1eb18084266..95824f282e7b 100644 --- a/docs/_data/nav_tips.yml +++ b/docs/_data/nav_tips.yml @@ -26,3 +26,5 @@ title: Load Initial Data via AJAX - id: false-in-jsx title: False in JSX + - id: communicate-between-components + title: Communicate Between Components diff --git a/docs/_js/examples/timer.js b/docs/_js/examples/timer.js index eb1fed09aa24..685e57e705e4 100644 --- a/docs/_js/examples/timer.js +++ b/docs/_js/examples/timer.js @@ -18,7 +18,7 @@ var Timer = React.createClass({\n\ },\n\ render: function() {\n\ return React.DOM.div({},\n\ - 'Seconds Elapsed: ' + this.state.secondsElapsed\n\ + 'Seconds Elapsed: ', this.state.secondsElapsed\n\ );\n\ }\n\ });\n\ diff --git a/docs/blog/index.html b/docs/blog/index.html index ab3bbd179727..3eb406c2e13c 100644 --- a/docs/blog/index.html +++ b/docs/blog/index.html @@ -20,7 +20,7 @@

{{ page.title }}

diff --git a/docs/tips/13-false-in-jsx.md b/docs/tips/13-false-in-jsx.md index 0c86bddf5577..3b46f12895ca 100644 --- a/docs/tips/13-false-in-jsx.md +++ b/docs/tips/13-false-in-jsx.md @@ -4,6 +4,7 @@ title: False in JSX layout: tips permalink: false-in-jsx.html prev: initial-ajax.html +next: communicate-between-components.html --- Here's how `false` renders in different contexts: diff --git a/docs/tips/14-communicate-between-components.md b/docs/tips/14-communicate-between-components.md new file mode 100644 index 000000000000..84c1a9feb902 --- /dev/null +++ b/docs/tips/14-communicate-between-components.md @@ -0,0 +1,40 @@ +--- +id: communicate-between-components +title: Communicate Between Components +layout: tips +permalink: communicate-between-components.html +prev: false-in-jsx.html +--- + +For parent-child communication, simply [pass props](/react/docs/multiple-components.html). + +For child-parent communication: +Say your `GroceryList` component has a list of items generated through an array. When a list item is clicked, you want to display its name: + +```js +/** @jsx React.DOM */ + +var GroceryList = React.createClass({ + handleClick: function(i) { + console.log('You clicked: ' + this.props.items[i]); + }, + + render: function() { + return ( +
+ {this.props.items.map(function(item, i) { + return ( +
{item}
+ ); + }, this)} +
+ ); + } +}); + +React.renderComponent( + , mountNode +); +``` + +Notice the use of `bind(this, arg1, arg2, ...)`: we're simply passing more arguments to `handleClick`. This is not a new React concept; it's just JavaScript. diff --git a/examples/basic-jsx/index.html b/examples/basic-jsx/index.html index 1b7a4836ab82..6fe8133eb9c9 100644 --- a/examples/basic-jsx/index.html +++ b/examples/basic-jsx/index.html @@ -40,16 +40,13 @@

Example Details

* @jsx React.DOM */ var ExampleApplication = React.createClass({ - _onClick: function (e) { - console.log('click event'); - }, render: function() { var elapsed = Math.round(this.props.elapsed / 100); var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' ); var message = 'React has been successfully running for ' + seconds + ' seconds.'; - return

{message}

{message}

; + return

{message}

; } }); var start = new Date().getTime(); diff --git a/src/addons/transitions/ReactTransitionGroup.js b/src/addons/transitions/ReactTransitionGroup.js index 623838c49f4c..f846f208c2a0 100644 --- a/src/addons/transitions/ReactTransitionGroup.js +++ b/src/addons/transitions/ReactTransitionGroup.js @@ -46,6 +46,7 @@ var ReactTransitionGroupMixin = { var children = {}; var childMapping = ReactTransitionKeySet.getChildMapping(sourceChildren); + var transitionConfig = this.getTransitionConfig(); var currentKeys = ReactTransitionKeySet.mergeKeySets( this._transitionGroupCurrentKeys, diff --git a/src/addons/transitions/ReactTransitionKeySet.js b/src/addons/transitions/ReactTransitionKeySet.js index debb3073ca3f..ebb1cdeeb930 100644 --- a/src/addons/transitions/ReactTransitionKeySet.js +++ b/src/addons/transitions/ReactTransitionKeySet.js @@ -68,6 +68,9 @@ var ReactTransitionKeySet = { * in `next` in a reasonable order. */ mergeKeySets: function(prev, next) { + prev = prev || {}; + next = next || {}; + var keySet = {}; var prevKeys = Object.keys(prev).concat([MERGE_KEY_SETS_TAIL_SENTINEL]); var nextKeys = Object.keys(next).concat([MERGE_KEY_SETS_TAIL_SENTINEL]); diff --git a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js index bfddd7be5ae9..1e5801f3bd86 100644 --- a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js +++ b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js @@ -26,18 +26,18 @@ var mocks; // Most of the real functionality is covered in other unit tests, this just // makes sure we're wired up correctly. describe('ReactTransitionGroup', function() { + var container; + beforeEach(function() { React = require('React'); ReactTransitionGroup = require('ReactTransitionGroup'); mocks = require('mocks'); + + container = document.createElement('div'); }); it('should warn after time with no transitionend', function() { - var container; - var a; - - container = document.createElement('div'); - a = React.renderComponent( + var a = React.renderComponent( , @@ -65,7 +65,6 @@ describe('ReactTransitionGroup', function() { }); it('should keep both sets of DOM nodes around', function() { - var container = document.createElement('div'); var a = React.renderComponent( @@ -83,4 +82,14 @@ describe('ReactTransitionGroup', function() { expect(a.getDOMNode().childNodes[0].id).toBe('two'); expect(a.getDOMNode().childNodes[1].id).toBe('one'); }); + + describe('with an undefined child', function () { + it('should fail silently', function () { + React.renderComponent( + + , + container + ); + }); + }); }); diff --git a/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js b/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js index 72aba5ca6f21..993ebbc6e09d 100644 --- a/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js +++ b/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js @@ -127,4 +127,30 @@ describe('ReactTransitionKeySet', function() { five: true }); }); + + it('should support mergeKeySets with undefined input', function () { + var prev = { + one: true, + two: true + }; + + var next = undefined; + + expect(ReactTransitionKeySet.mergeKeySets(prev, next)).toEqual({ + one: true, + two: true + }); + + prev = undefined; + + next = { + three: true, + four: true + }; + + expect(ReactTransitionKeySet.mergeKeySets(prev, next)).toEqual({ + three: true, + four: true + }); + }); }); diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js index 881d4d9d42e2..3896f3029cc4 100644 --- a/src/core/ReactCompositeComponent.js +++ b/src/core/ReactCompositeComponent.js @@ -624,7 +624,7 @@ var ReactCompositeComponentMixin = { this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING; this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null; - this._processProps(this.props); + this.props = this._processProps(this.props); if (this.__reactAutoBindMap) { this._bindAutoBindMethods(); @@ -792,22 +792,31 @@ var ReactCompositeComponentMixin = { /** * Processes props by setting default values for unspecified props and - * asserting that the props are valid. + * asserting that the props are valid. Does not mutate its argument; returns + * a new props object with defaults merged in. * - * @param {object} props + * @param {object} newProps + * @return {object} * @private */ - _processProps: function(props) { - var defaultProps = this._defaultProps; - for (var propName in defaultProps) { - if (typeof props[propName] === 'undefined') { - props[propName] = defaultProps[propName]; + _processProps: function(newProps) { + var props = this._defaultProps; + if (props) { + // To avoid mutating the props that are passed into the constructor, we + // copy onto the object returned by getDefaultProps instead. + for (var propName in newProps) { + if (typeof newProps[propName] !== 'undefined') { + props[propName] = newProps[propName]; + } } + } else { + props = newProps; } var propTypes = this.constructor.propTypes; if (propTypes) { this._checkPropTypes(propTypes, props, ReactPropTypeLocations.prop); } + return props; }, /** @@ -859,8 +868,7 @@ var ReactCompositeComponentMixin = { var nextProps = this.props; if (this._pendingProps != null) { - nextProps = this._pendingProps; - this._processProps(nextProps); + nextProps = this._processProps(this._pendingProps); this._pendingProps = null; this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS; diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index 11dd41bd41c0..a3bf431d398f 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -215,6 +215,26 @@ describe('ReactCompositeComponent', function() { reactComponentExpect(instance3).scalarPropsEqual({key: null}); }); + it('should not mutate passed-in props object', function() { + var Component = React.createClass({ + getDefaultProps: function() { + return {key: 'testKey'}; + }, + render: function() { + return ; + } + }); + + var inputProps = {}; + var instance1 = Component(inputProps); + ReactTestUtils.renderIntoDocument(instance1); + expect(instance1.props.key).toBe('testKey'); + + // We don't mutate the input, just in case the caller wants to do something + // with it after using it to instantiate a component + expect(inputProps.key).not.toBeDefined(); + }); + it('should normalize props with default values', function() { var Component = React.createClass({ propTypes: {key: ReactPropTypes.string.isRequired}, diff --git a/src/core/__tests__/ReactRenderDocument-test.js b/src/core/__tests__/ReactRenderDocument-test.js index dd5250811ca0..4ecff37d76b6 100644 --- a/src/core/__tests__/ReactRenderDocument-test.js +++ b/src/core/__tests__/ReactRenderDocument-test.js @@ -40,10 +40,7 @@ describe('rendering React components at document', function() { }); it('should be able to get root component id for document node', function() { - if (!testDocument) { - // These tests are not applicable in jst, since jsdom is buggy. - return; - } + expect(testDocument).not.toBeUndefined(); var Root = React.createClass({ render: function() { @@ -69,10 +66,7 @@ describe('rendering React components at document', function() { }); it('should be able to unmount component from document node', function() { - if (!testDocument) { - // These tests are not applicable in jst, since jsdom is buggy. - return; - } + expect(testDocument).not.toBeUndefined(); var Root = React.createClass({ render: function() { @@ -100,10 +94,7 @@ describe('rendering React components at document', function() { }); it('should be able to switch root constructors via state', function() { - if (!testDocument) { - // These tests are not applicable in jst, since jsdom is buggy. - return; - } + expect(testDocument).not.toBeUndefined(); var Component = React.createClass({ render: function() { @@ -162,10 +153,7 @@ describe('rendering React components at document', function() { }); it('should be able to switch root constructors', function() { - if (!testDocument) { - // These tests are not applicable in jst, since jsdom is buggy. - return; - } + expect(testDocument).not.toBeUndefined(); var Component = React.createClass({ render: function() { @@ -210,10 +198,7 @@ describe('rendering React components at document', function() { }); it('should be able to mount into document', function() { - if (!testDocument) { - // These tests are not applicable in jst, since jsdom is buggy. - return; - } + expect(testDocument).not.toBeUndefined(); var Component = React.createClass({ render: function() { @@ -236,10 +221,7 @@ describe('rendering React components at document', function() { }); it('should throw on full document render', function() { - if (!testDocument) { - // These tests are not applicable in jst, since jsdom is buggy. - return; - } + expect(testDocument).not.toBeUndefined(); var container = testDocument; expect(function() { @@ -255,10 +237,7 @@ describe('rendering React components at document', function() { }); it('should throw on full document render of non-html', function() { - if (!testDocument) { - // These tests are not applicable in jst, since jsdom is buggy. - return; - } + expect(testDocument).not.toBeUndefined(); var container = testDocument; ReactMount.allowFullPageRender = true; diff --git a/src/event/EventPluginRegistry.js b/src/event/EventPluginRegistry.js index 0d0016a94f94..148ce4670c1d 100644 --- a/src/event/EventPluginRegistry.js +++ b/src/event/EventPluginRegistry.js @@ -122,7 +122,6 @@ function publishRegistrationName(registrationName, PluginModule) { registrationName ); EventPluginRegistry.registrationNames[registrationName] = PluginModule; - EventPluginRegistry.registrationNamesKeys.push(registrationName); } /** @@ -142,11 +141,6 @@ var EventPluginRegistry = { */ registrationNames: {}, - /** - * The keys of `registrationNames`. - */ - registrationNamesKeys: [], - /** * Mapping from on{EventName} to */ @@ -250,7 +244,6 @@ var EventPluginRegistry = { delete registrationNames[registrationName]; } } - EventPluginRegistry.registrationNamesKeys.length = 0; } }; diff --git a/src/event/__tests__/EventPluginRegistry-test.js b/src/event/__tests__/EventPluginRegistry-test.js index f890a58169fb..7474525e56f6 100644 --- a/src/event/__tests__/EventPluginRegistry-test.js +++ b/src/event/__tests__/EventPluginRegistry-test.js @@ -174,13 +174,13 @@ describe('EventPluginRegistry', function() { EventPluginRegistry.injectEventPluginsByName({one: OnePlugin}); EventPluginRegistry.injectEventPluginOrder(['one', 'two']); - expect(EventPluginRegistry.registrationNamesKeys.length).toBe(2); + expect(Object.keys(EventPluginRegistry.registrationNames).length).toBe(2); expect(EventPluginRegistry.registrationNames.onClick).toBe(OnePlugin); expect(EventPluginRegistry.registrationNames.onFocus).toBe(OnePlugin); EventPluginRegistry.injectEventPluginsByName({two: TwoPlugin}); - expect(EventPluginRegistry.registrationNamesKeys.length).toBe(4); + expect(Object.keys(EventPluginRegistry.registrationNames).length).toBe(4); expect(EventPluginRegistry.registrationNames.onMagicBubble).toBe(TwoPlugin); expect( EventPluginRegistry.registrationNames.onMagicCapture diff --git a/src/eventPlugins/ChangeEventPlugin.js b/src/eventPlugins/ChangeEventPlugin.js index 2eb1e93e0358..bb7648485271 100644 --- a/src/eventPlugins/ChangeEventPlugin.js +++ b/src/eventPlugins/ChangeEventPlugin.js @@ -37,12 +37,11 @@ var eventTypes = { captured: keyOf({onChangeCapture: null}) }, dependencies: [ - keyOf({topBlur: null}), - keyOf({topChange: null}), - keyOf({topFocus: null}), - keyOf({topInput: null}), - keyOf({topKeyDown: null}), - keyOf({topKeyUp: null}) + keyOf({onInput: null}), + keyOf({onKeyUp: null}), + keyOf({onKeyDown: null}), + keyOf({onFocus: null}), + keyOf({onBlur: null}) ] } }; diff --git a/src/eventPlugins/CompositionEventPlugin.js b/src/eventPlugins/CompositionEventPlugin.js index 7b7796ca272e..d5da7a21087e 100644 --- a/src/eventPlugins/CompositionEventPlugin.js +++ b/src/eventPlugins/CompositionEventPlugin.js @@ -44,12 +44,12 @@ var eventTypes = { captured: keyOf({onCompositionEndCapture: null}) }, dependencies: [ - keyOf({topBlur: null}), - keyOf({topCompositionEnd: null}), - keyOf({topKeyDown: null}), - keyOf({topKeyPress: null}), - keyOf({topKeyUp: null}), - keyOf({topMouseDown: null}) + keyOf({onCompositionEnd: null}), + keyOf({onKeyDown: null}), + keyOf({onKeyUp: null}), + keyOf({onKeyPress: null}), + keyOf({onMouseDown: null}), + keyOf({onBlur: null}) ] }, compositionStart: { @@ -58,12 +58,12 @@ var eventTypes = { captured: keyOf({onCompositionStartCapture: null}) }, dependencies: [ - keyOf({topBlur: null}), - keyOf({topCompositionStart: null}), - keyOf({topKeyDown: null}), - keyOf({topKeyPress: null}), - keyOf({topKeyUp: null}), - keyOf({topMouseDown: null}) + keyOf({onCompositionStart: null}), + keyOf({onKeyDown: null}), + keyOf({onKeyUp: null}), + keyOf({onKeyPress: null}), + keyOf({onMouseDown: null}), + keyOf({onBlur: null}) ] }, compositionUpdate: { @@ -72,12 +72,12 @@ var eventTypes = { captured: keyOf({onCompositionUpdateCapture: null}) }, dependencies: [ - keyOf({topBlur: null}), - keyOf({topCompositionUpdate: null}), - keyOf({topKeyDown: null}), - keyOf({topKeyPress: null}), - keyOf({topKeyUp: null}), - keyOf({topMouseDown: null}) + keyOf({onCompositionUpdate: null}), + keyOf({onKeyDown: null}), + keyOf({onKeyUp: null}), + keyOf({onKeyPress: null}), + keyOf({onMouseDown: null}), + keyOf({onBlur: null}) ] } }; diff --git a/src/eventPlugins/EnterLeaveEventPlugin.js b/src/eventPlugins/EnterLeaveEventPlugin.js index 7abbb6f1ecea..000ee4286c7f 100644 --- a/src/eventPlugins/EnterLeaveEventPlugin.js +++ b/src/eventPlugins/EnterLeaveEventPlugin.js @@ -33,15 +33,15 @@ var eventTypes = { mouseEnter: { registrationName: keyOf({onMouseEnter: null}), dependencies: [ - keyOf({topMouseOut: null}), - keyOf({topMouseOver: null}) + keyOf({onMouseOut: null}), + keyOf({onMouseOver: null}) ] }, mouseLeave: { registrationName: keyOf({onMouseLeave: null}), dependencies: [ - keyOf({topMouseOut: null}), - keyOf({topMouseOver: null}) + keyOf({onMouseOut: null}), + keyOf({onMouseOver: null}) ] } }; diff --git a/src/eventPlugins/SelectEventPlugin.js b/src/eventPlugins/SelectEventPlugin.js index aa3d01ae6690..de9a3a372137 100644 --- a/src/eventPlugins/SelectEventPlugin.js +++ b/src/eventPlugins/SelectEventPlugin.js @@ -39,12 +39,12 @@ var eventTypes = { captured: keyOf({onSelectCapture: null}) }, dependencies: [ - keyOf({topBlur: null}), - keyOf({topFocus: null}), - keyOf({topKeyDown: null}), - keyOf({topMouseDown: null}), - keyOf({topMouseUp: null}), - keyOf({topSelectionChange: null}) + keyOf({onBlur: null}), + keyOf({onFocus: null}), + keyOf({onMouseUp: null}), + keyOf({onMouseDown: null}), + keyOf({onSelectionChange: null}), + keyOf({onKeyDown: null}) ] } }; diff --git a/src/eventPlugins/SimpleEventPlugin.js b/src/eventPlugins/SimpleEventPlugin.js index 07850b2067a7..4c69441dbe6b 100644 --- a/src/eventPlugins/SimpleEventPlugin.js +++ b/src/eventPlugins/SimpleEventPlugin.js @@ -39,172 +39,115 @@ var eventTypes = { phasedRegistrationNames: { bubbled: keyOf({onBlur: true}), captured: keyOf({onBlurCapture: true}) - }, - dependencies: [ - keyOf({topBlur: null}) - ] + } }, click: { phasedRegistrationNames: { bubbled: keyOf({onClick: true}), captured: keyOf({onClickCapture: true}) - }, - dependencies: [ - keyOf({topClick: null}) - ] + } }, contextMenu: { phasedRegistrationNames: { bubbled: keyOf({onContextMenu: true}), captured: keyOf({onContextMenuCapture: true}) - }, - dependencies: [ - keyOf({topContextMenu: null}) - ] + } }, copy: { phasedRegistrationNames: { bubbled: keyOf({onCopy: true}), captured: keyOf({onCopyCapture: true}) - }, - dependencies: [ - keyOf({topCopy: null}) - ] + } }, cut: { phasedRegistrationNames: { bubbled: keyOf({onCut: true}), captured: keyOf({onCutCapture: true}) - }, - dependencies: [ - keyOf({topCut: null}) - ] + } }, doubleClick: { phasedRegistrationNames: { bubbled: keyOf({onDoubleClick: true}), captured: keyOf({onDoubleClickCapture: true}) - }, - dependencies: [ - keyOf({topDoubleClick: null}) - ] + } }, drag: { phasedRegistrationNames: { bubbled: keyOf({onDrag: true}), captured: keyOf({onDragCapture: true}) - }, - dependencies: [ - keyOf({topDrag: null}) - ] + } }, dragEnd: { phasedRegistrationNames: { bubbled: keyOf({onDragEnd: true}), captured: keyOf({onDragEndCapture: true}) - }, - dependencies: [ - keyOf({topDragEnd: null}) - ] + } }, dragEnter: { phasedRegistrationNames: { bubbled: keyOf({onDragEnter: true}), captured: keyOf({onDragEnterCapture: true}) - }, - dependencies: [ - keyOf({topDragEnter: null}) - ] + } }, dragExit: { phasedRegistrationNames: { bubbled: keyOf({onDragExit: true}), captured: keyOf({onDragExitCapture: true}) - }, - dependencies: [ - keyOf({topDragExit: null}) - ] + } }, dragLeave: { phasedRegistrationNames: { bubbled: keyOf({onDragLeave: true}), captured: keyOf({onDragLeaveCapture: true}) - }, - dependencies: [ - keyOf({topDragLeave: null}) - ] + } }, dragOver: { phasedRegistrationNames: { bubbled: keyOf({onDragOver: true}), captured: keyOf({onDragOverCapture: true}) - }, - dependencies: [ - keyOf({topDragOver: null}) - ] + } }, dragStart: { phasedRegistrationNames: { bubbled: keyOf({onDragStart: true}), captured: keyOf({onDragStartCapture: true}) - }, - dependencies: [ - keyOf({topDragStart: null}) - ] + } }, drop: { phasedRegistrationNames: { bubbled: keyOf({onDrop: true}), captured: keyOf({onDropCapture: true}) - }, - dependencies: [ - keyOf({topDrop: null}) - ] + } }, focus: { phasedRegistrationNames: { bubbled: keyOf({onFocus: true}), captured: keyOf({onFocusCapture: true}) - }, - dependencies: [ - keyOf({topFocus: null}) - ] + } }, input: { phasedRegistrationNames: { bubbled: keyOf({onInput: true}), captured: keyOf({onInputCapture: true}) - }, - dependencies: [ - keyOf({topInput: null}) - ] + } }, keyDown: { phasedRegistrationNames: { bubbled: keyOf({onKeyDown: true}), captured: keyOf({onKeyDownCapture: true}) - }, - dependencies: [ - keyOf({topKeyDown: null}) - ] + } }, keyPress: { phasedRegistrationNames: { bubbled: keyOf({onKeyPress: true}), captured: keyOf({onKeyPressCapture: true}) - }, - dependencies: [ - keyOf({topKeyPress: null}) - ] + } }, keyUp: { phasedRegistrationNames: { bubbled: keyOf({onKeyUp: true}), captured: keyOf({onKeyUpCapture: true}) - }, - dependencies: [ - keyOf({topKeyUp: null}) - ] + } }, // Note: We do not allow listening to mouseOver events. Instead, use the // onMouseEnter/onMouseLeave created by `EnterLeaveEventPlugin`. @@ -212,100 +155,67 @@ var eventTypes = { phasedRegistrationNames: { bubbled: keyOf({onMouseDown: true}), captured: keyOf({onMouseDownCapture: true}) - }, - dependencies: [ - keyOf({topMouseDown: null}) - ] + } }, mouseMove: { phasedRegistrationNames: { bubbled: keyOf({onMouseMove: true}), captured: keyOf({onMouseMoveCapture: true}) - }, - dependencies: [ - keyOf({topMouseMove: null}) - ] + } }, mouseUp: { phasedRegistrationNames: { bubbled: keyOf({onMouseUp: true}), captured: keyOf({onMouseUpCapture: true}) - }, - dependencies: [ - keyOf({topMouseUp: null}) - ] + } }, paste: { phasedRegistrationNames: { bubbled: keyOf({onPaste: true}), captured: keyOf({onPasteCapture: true}) - }, - dependencies: [ - keyOf({topPaste: null}) - ] + } }, scroll: { phasedRegistrationNames: { bubbled: keyOf({onScroll: true}), captured: keyOf({onScrollCapture: true}) - }, - dependencies: [ - keyOf({topScroll: null}) - ] + } }, submit: { phasedRegistrationNames: { bubbled: keyOf({onSubmit: true}), captured: keyOf({onSubmitCapture: true}) - }, - dependencies: [ - keyOf({topSubmit: null}) - ] + } }, touchCancel: { phasedRegistrationNames: { bubbled: keyOf({onTouchCancel: true}), captured: keyOf({onTouchCancelCapture: true}) - }, - dependencies: [ - keyOf({topTouchCancel: null}) - ] + } }, touchEnd: { phasedRegistrationNames: { bubbled: keyOf({onTouchEnd: true}), captured: keyOf({onTouchEndCapture: true}) - }, - dependencies: [ - keyOf({topTouchEnd: null}) - ] + } }, touchMove: { phasedRegistrationNames: { bubbled: keyOf({onTouchMove: true}), captured: keyOf({onTouchMoveCapture: true}) - }, - dependencies: [ - keyOf({topTouchMove: null}) - ] + } }, touchStart: { phasedRegistrationNames: { bubbled: keyOf({onTouchStart: true}), captured: keyOf({onTouchStartCapture: true}) - }, - dependencies: [ - keyOf({topTouchStart: null}) - ] + } }, wheel: { phasedRegistrationNames: { bubbled: keyOf({onWheel: true}), captured: keyOf({onWheelCapture: true}) - }, - dependencies: [ - keyOf({topWheel: null}) - ] + } } }; diff --git a/src/eventPlugins/TapEventPlugin.js b/src/eventPlugins/TapEventPlugin.js index 6cd30f206a60..06b53a06c6d8 100644 --- a/src/eventPlugins/TapEventPlugin.js +++ b/src/eventPlugins/TapEventPlugin.js @@ -62,18 +62,18 @@ function getDistance(coords, nativeEvent) { } var dependencies = [ - keyOf({topMouseDown: null}), - keyOf({topMouseMove: null}), - keyOf({topMouseUp: null}) + keyOf({onMouseDown: null}), + keyOf({onMouseMove: null}), + keyOf({onMouseUp: null}) ]; if (EventPluginUtils.supportTouch) { - dependencies.push( - keyOf({topTouchStart: null}), - keyOf({topTouchMove: null}), - keyOf({topTouchEnd: null}), - keyOf({topTouchCancel: null}) - ); + dependencies = dependencies.concat([ + keyOf({onTouchStart: null}), + keyOf({onTouchMove: null}), + keyOf({onTouchEnd: null}), + keyOf({onTouchCancel: null}) + ]); } var eventTypes = { diff --git a/src/test/getTestDocument.js b/src/test/getTestDocument.js index 9d30b6df57ac..82d7d64dfb96 100644 --- a/src/test/getTestDocument.js +++ b/src/test/getTestDocument.js @@ -16,18 +16,18 @@ * @providesModule getTestDocument */ -/** - * We need to work around the fact that we have two different - * test implementations: once that breaks if we clobber document - * (open-source) and one that doesn't support createHTMLDocument() - * (jst). - */ function getTestDocument() { - if (document.implementation && - document.implementation.createHTMLDocument) { - return document.implementation.createHTMLDocument('test doc'); - } - return null; + var iframe = document.createElement('iframe'); + iframe.style.display = 'none'; + document.body.appendChild(iframe); + + var testDocument = iframe.contentDocument || iframe.contentWindow.document; + testDocument.open(); + testDocument.write('test doc'); + testDocument.close(); + + iframe.parentNode.removeChild(iframe); + return testDocument; } module.exports = getTestDocument; diff --git a/src/vendor/core/dom/getUnboundedScrollPosition.js b/src/vendor/core/dom/getUnboundedScrollPosition.js index 872465bbc15e..b9f8c9c6431a 100644 --- a/src/vendor/core/dom/getUnboundedScrollPosition.js +++ b/src/vendor/core/dom/getUnboundedScrollPosition.js @@ -1,4 +1,18 @@ /** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * * @providesModule getUnboundedScrollPosition * @typechecks */ From 22df2b677afb3e3185c20211216ea74df170da87 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 08:43:01 +0100 Subject: [PATCH 12/31] Sanitizing --- src/core/React.js | 1 - src/core/ReactMount.js | 5 +- src/eventPlugins/ChangeEventPlugin.js | 11 +- src/eventPlugins/EnterLeaveEventPlugin.js | 8 +- src/eventPlugins/SelectEventPlugin.js | 12 +- src/eventPlugins/SimpleEventPlugin.js | 150 +++++++++++++++++----- src/eventPlugins/TapEventPlugin.js | 18 +-- 7 files changed, 147 insertions(+), 58 deletions(-) diff --git a/src/core/React.js b/src/core/React.js index 48afaab1da9e..cbf6d93a64c3 100644 --- a/src/core/React.js +++ b/src/core/React.js @@ -33,7 +33,6 @@ var ReactPerf = require('ReactPerf'); var ReactPropTypes = require('ReactPropTypes'); var ReactServerRendering = require('ReactServerRendering'); var ReactTextComponent = require('ReactTextComponent'); -var EventPluginUtils = require('EventPluginUtils'); ReactDefaultInjection.inject(); diff --git a/src/core/ReactMount.js b/src/core/ReactMount.js index 4f7e792262cc..4a949469f3d8 100644 --- a/src/core/ReactMount.js +++ b/src/core/ReactMount.js @@ -240,12 +240,11 @@ var ReactMount = { invariant( container && ( container.nodeType === ELEMENT_NODE_TYPE || - container.nodeType === DOC_NODE_TYPE - ), + container.nodeType === DOC_NODE_TYPE + ), '_registerComponent(...): Target container is not a DOM element.' ); - // TODO: add unit tests ReactEventEmitter.ensureScrollValueMonitoring(); var reactRootID = ReactMount.registerContainer(container); diff --git a/src/eventPlugins/ChangeEventPlugin.js b/src/eventPlugins/ChangeEventPlugin.js index bb7648485271..2eb1e93e0358 100644 --- a/src/eventPlugins/ChangeEventPlugin.js +++ b/src/eventPlugins/ChangeEventPlugin.js @@ -37,11 +37,12 @@ var eventTypes = { captured: keyOf({onChangeCapture: null}) }, dependencies: [ - keyOf({onInput: null}), - keyOf({onKeyUp: null}), - keyOf({onKeyDown: null}), - keyOf({onFocus: null}), - keyOf({onBlur: null}) + keyOf({topBlur: null}), + keyOf({topChange: null}), + keyOf({topFocus: null}), + keyOf({topInput: null}), + keyOf({topKeyDown: null}), + keyOf({topKeyUp: null}) ] } }; diff --git a/src/eventPlugins/EnterLeaveEventPlugin.js b/src/eventPlugins/EnterLeaveEventPlugin.js index 000ee4286c7f..7abbb6f1ecea 100644 --- a/src/eventPlugins/EnterLeaveEventPlugin.js +++ b/src/eventPlugins/EnterLeaveEventPlugin.js @@ -33,15 +33,15 @@ var eventTypes = { mouseEnter: { registrationName: keyOf({onMouseEnter: null}), dependencies: [ - keyOf({onMouseOut: null}), - keyOf({onMouseOver: null}) + keyOf({topMouseOut: null}), + keyOf({topMouseOver: null}) ] }, mouseLeave: { registrationName: keyOf({onMouseLeave: null}), dependencies: [ - keyOf({onMouseOut: null}), - keyOf({onMouseOver: null}) + keyOf({topMouseOut: null}), + keyOf({topMouseOver: null}) ] } }; diff --git a/src/eventPlugins/SelectEventPlugin.js b/src/eventPlugins/SelectEventPlugin.js index de9a3a372137..aa3d01ae6690 100644 --- a/src/eventPlugins/SelectEventPlugin.js +++ b/src/eventPlugins/SelectEventPlugin.js @@ -39,12 +39,12 @@ var eventTypes = { captured: keyOf({onSelectCapture: null}) }, dependencies: [ - keyOf({onBlur: null}), - keyOf({onFocus: null}), - keyOf({onMouseUp: null}), - keyOf({onMouseDown: null}), - keyOf({onSelectionChange: null}), - keyOf({onKeyDown: null}) + keyOf({topBlur: null}), + keyOf({topFocus: null}), + keyOf({topKeyDown: null}), + keyOf({topMouseDown: null}), + keyOf({topMouseUp: null}), + keyOf({topSelectionChange: null}) ] } }; diff --git a/src/eventPlugins/SimpleEventPlugin.js b/src/eventPlugins/SimpleEventPlugin.js index 4c69441dbe6b..07850b2067a7 100644 --- a/src/eventPlugins/SimpleEventPlugin.js +++ b/src/eventPlugins/SimpleEventPlugin.js @@ -39,115 +39,172 @@ var eventTypes = { phasedRegistrationNames: { bubbled: keyOf({onBlur: true}), captured: keyOf({onBlurCapture: true}) - } + }, + dependencies: [ + keyOf({topBlur: null}) + ] }, click: { phasedRegistrationNames: { bubbled: keyOf({onClick: true}), captured: keyOf({onClickCapture: true}) - } + }, + dependencies: [ + keyOf({topClick: null}) + ] }, contextMenu: { phasedRegistrationNames: { bubbled: keyOf({onContextMenu: true}), captured: keyOf({onContextMenuCapture: true}) - } + }, + dependencies: [ + keyOf({topContextMenu: null}) + ] }, copy: { phasedRegistrationNames: { bubbled: keyOf({onCopy: true}), captured: keyOf({onCopyCapture: true}) - } + }, + dependencies: [ + keyOf({topCopy: null}) + ] }, cut: { phasedRegistrationNames: { bubbled: keyOf({onCut: true}), captured: keyOf({onCutCapture: true}) - } + }, + dependencies: [ + keyOf({topCut: null}) + ] }, doubleClick: { phasedRegistrationNames: { bubbled: keyOf({onDoubleClick: true}), captured: keyOf({onDoubleClickCapture: true}) - } + }, + dependencies: [ + keyOf({topDoubleClick: null}) + ] }, drag: { phasedRegistrationNames: { bubbled: keyOf({onDrag: true}), captured: keyOf({onDragCapture: true}) - } + }, + dependencies: [ + keyOf({topDrag: null}) + ] }, dragEnd: { phasedRegistrationNames: { bubbled: keyOf({onDragEnd: true}), captured: keyOf({onDragEndCapture: true}) - } + }, + dependencies: [ + keyOf({topDragEnd: null}) + ] }, dragEnter: { phasedRegistrationNames: { bubbled: keyOf({onDragEnter: true}), captured: keyOf({onDragEnterCapture: true}) - } + }, + dependencies: [ + keyOf({topDragEnter: null}) + ] }, dragExit: { phasedRegistrationNames: { bubbled: keyOf({onDragExit: true}), captured: keyOf({onDragExitCapture: true}) - } + }, + dependencies: [ + keyOf({topDragExit: null}) + ] }, dragLeave: { phasedRegistrationNames: { bubbled: keyOf({onDragLeave: true}), captured: keyOf({onDragLeaveCapture: true}) - } + }, + dependencies: [ + keyOf({topDragLeave: null}) + ] }, dragOver: { phasedRegistrationNames: { bubbled: keyOf({onDragOver: true}), captured: keyOf({onDragOverCapture: true}) - } + }, + dependencies: [ + keyOf({topDragOver: null}) + ] }, dragStart: { phasedRegistrationNames: { bubbled: keyOf({onDragStart: true}), captured: keyOf({onDragStartCapture: true}) - } + }, + dependencies: [ + keyOf({topDragStart: null}) + ] }, drop: { phasedRegistrationNames: { bubbled: keyOf({onDrop: true}), captured: keyOf({onDropCapture: true}) - } + }, + dependencies: [ + keyOf({topDrop: null}) + ] }, focus: { phasedRegistrationNames: { bubbled: keyOf({onFocus: true}), captured: keyOf({onFocusCapture: true}) - } + }, + dependencies: [ + keyOf({topFocus: null}) + ] }, input: { phasedRegistrationNames: { bubbled: keyOf({onInput: true}), captured: keyOf({onInputCapture: true}) - } + }, + dependencies: [ + keyOf({topInput: null}) + ] }, keyDown: { phasedRegistrationNames: { bubbled: keyOf({onKeyDown: true}), captured: keyOf({onKeyDownCapture: true}) - } + }, + dependencies: [ + keyOf({topKeyDown: null}) + ] }, keyPress: { phasedRegistrationNames: { bubbled: keyOf({onKeyPress: true}), captured: keyOf({onKeyPressCapture: true}) - } + }, + dependencies: [ + keyOf({topKeyPress: null}) + ] }, keyUp: { phasedRegistrationNames: { bubbled: keyOf({onKeyUp: true}), captured: keyOf({onKeyUpCapture: true}) - } + }, + dependencies: [ + keyOf({topKeyUp: null}) + ] }, // Note: We do not allow listening to mouseOver events. Instead, use the // onMouseEnter/onMouseLeave created by `EnterLeaveEventPlugin`. @@ -155,67 +212,100 @@ var eventTypes = { phasedRegistrationNames: { bubbled: keyOf({onMouseDown: true}), captured: keyOf({onMouseDownCapture: true}) - } + }, + dependencies: [ + keyOf({topMouseDown: null}) + ] }, mouseMove: { phasedRegistrationNames: { bubbled: keyOf({onMouseMove: true}), captured: keyOf({onMouseMoveCapture: true}) - } + }, + dependencies: [ + keyOf({topMouseMove: null}) + ] }, mouseUp: { phasedRegistrationNames: { bubbled: keyOf({onMouseUp: true}), captured: keyOf({onMouseUpCapture: true}) - } + }, + dependencies: [ + keyOf({topMouseUp: null}) + ] }, paste: { phasedRegistrationNames: { bubbled: keyOf({onPaste: true}), captured: keyOf({onPasteCapture: true}) - } + }, + dependencies: [ + keyOf({topPaste: null}) + ] }, scroll: { phasedRegistrationNames: { bubbled: keyOf({onScroll: true}), captured: keyOf({onScrollCapture: true}) - } + }, + dependencies: [ + keyOf({topScroll: null}) + ] }, submit: { phasedRegistrationNames: { bubbled: keyOf({onSubmit: true}), captured: keyOf({onSubmitCapture: true}) - } + }, + dependencies: [ + keyOf({topSubmit: null}) + ] }, touchCancel: { phasedRegistrationNames: { bubbled: keyOf({onTouchCancel: true}), captured: keyOf({onTouchCancelCapture: true}) - } + }, + dependencies: [ + keyOf({topTouchCancel: null}) + ] }, touchEnd: { phasedRegistrationNames: { bubbled: keyOf({onTouchEnd: true}), captured: keyOf({onTouchEndCapture: true}) - } + }, + dependencies: [ + keyOf({topTouchEnd: null}) + ] }, touchMove: { phasedRegistrationNames: { bubbled: keyOf({onTouchMove: true}), captured: keyOf({onTouchMoveCapture: true}) - } + }, + dependencies: [ + keyOf({topTouchMove: null}) + ] }, touchStart: { phasedRegistrationNames: { bubbled: keyOf({onTouchStart: true}), captured: keyOf({onTouchStartCapture: true}) - } + }, + dependencies: [ + keyOf({topTouchStart: null}) + ] }, wheel: { phasedRegistrationNames: { bubbled: keyOf({onWheel: true}), captured: keyOf({onWheelCapture: true}) - } + }, + dependencies: [ + keyOf({topWheel: null}) + ] } }; diff --git a/src/eventPlugins/TapEventPlugin.js b/src/eventPlugins/TapEventPlugin.js index 06b53a06c6d8..6cd30f206a60 100644 --- a/src/eventPlugins/TapEventPlugin.js +++ b/src/eventPlugins/TapEventPlugin.js @@ -62,18 +62,18 @@ function getDistance(coords, nativeEvent) { } var dependencies = [ - keyOf({onMouseDown: null}), - keyOf({onMouseMove: null}), - keyOf({onMouseUp: null}) + keyOf({topMouseDown: null}), + keyOf({topMouseMove: null}), + keyOf({topMouseUp: null}) ]; if (EventPluginUtils.supportTouch) { - dependencies = dependencies.concat([ - keyOf({onTouchStart: null}), - keyOf({onTouchMove: null}), - keyOf({onTouchEnd: null}), - keyOf({onTouchCancel: null}) - ]); + dependencies.push( + keyOf({topTouchStart: null}), + keyOf({topTouchMove: null}), + keyOf({topTouchEnd: null}), + keyOf({topTouchCancel: null}) + ); } var eventTypes = { From f3caf234ba152e8898dac8ca1fabbd403d3e0aa1 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 08:43:22 +0100 Subject: [PATCH 13/31] Sanitizing --- .idea/webResources.xml | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 .idea/webResources.xml diff --git a/.idea/webResources.xml b/.idea/webResources.xml deleted file mode 100644 index 946b63560412..000000000000 --- a/.idea/webResources.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - From 8d6fb7ebb38e2c85db66a6937449fba7f3d90f90 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 09:03:23 +0100 Subject: [PATCH 14/31] Sanitizing --- .idea/dictionaries/sanderspies.xml | 3 +++ src/core/ReactDOMComponent.js | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .idea/dictionaries/sanderspies.xml diff --git a/.idea/dictionaries/sanderspies.xml b/.idea/dictionaries/sanderspies.xml new file mode 100644 index 000000000000..c6e53b1660a7 --- /dev/null +++ b/.idea/dictionaries/sanderspies.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/core/ReactDOMComponent.js b/src/core/ReactDOMComponent.js index c74ad7fea690..ceb40cc309fc 100644 --- a/src/core/ReactDOMComponent.js +++ b/src/core/ReactDOMComponent.js @@ -297,7 +297,13 @@ ReactDOMComponent.Mixin = { styleUpdates = nextProp; } } else if (registrationNames[propKey]) { - listenTo(propKey, ReactMount.findReactContainerForID(this._rootNodeID)); + var container = ReactMount.findReactContainerForID(this._rootNodeID); + if (container) { + var doc = container.nodeType === ELEMENT_NODE_TYPE ? + container.ownerDocument : + container; + listenTo(propKey, doc); + } putListener(this._rootNodeID, propKey, nextProp); } else if ( DOMProperty.isStandardName[propKey] || From db2116f50abd91affd0872bfcdfe52d50ac139d4 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 09:38:11 +0100 Subject: [PATCH 15/31] sanitizing --- .idea/inspectionProfiles/Project_Default.xml | 7 +++++++ .idea/inspectionProfiles/profiles_settings.xml | 7 +++++++ .idea/scopes/scope_settings.xml | 5 +++++ 3 files changed, 19 insertions(+) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/scopes/scope_settings.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000000..9cc3673b7758 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 000000000000..3b312839bf2e --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 000000000000..922003b8433b --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file From 686e817636e94cce7bf8af5f796eb91a91951d98 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 09:47:52 +0100 Subject: [PATCH 16/31] Sanitizing --- .idea/dictionaries/sanderspies.xml | 3 --- .idea/inspectionProfiles/Project_Default.xml | 7 ------- .idea/inspectionProfiles/profiles_settings.xml | 7 ------- 3 files changed, 17 deletions(-) delete mode 100644 .idea/dictionaries/sanderspies.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml diff --git a/.idea/dictionaries/sanderspies.xml b/.idea/dictionaries/sanderspies.xml deleted file mode 100644 index c6e53b1660a7..000000000000 --- a/.idea/dictionaries/sanderspies.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 9cc3673b7758..000000000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 3b312839bf2e..000000000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file From ca7b131b9d04f24bcfac9fb8121466c7fc9642fc Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 09:55:21 +0100 Subject: [PATCH 17/31] Added git ignore for .idea --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 39a72e9b2da0..d4678809fc66 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ examples/shared/*.js test/the-files-to-test.generated.js *.log* chrome-user-data +.idea \ No newline at end of file From 210778a6f2afb694ebaef62320f6b08abe2b21da Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 10:26:59 +0100 Subject: [PATCH 18/31] sanitizing --- .idea/scopes/scope_settings.xml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .idea/scopes/scope_settings.xml diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml deleted file mode 100644 index 922003b8433b..000000000000 --- a/.idea/scopes/scope_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file From 0389251f21a7a4d768f5734bb1d7439bde7cc8b7 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 10:38:01 +0100 Subject: [PATCH 19/31] Accessing non-existent property --- src/eventPlugins/TapEventPlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eventPlugins/TapEventPlugin.js b/src/eventPlugins/TapEventPlugin.js index 6cd30f206a60..bd45f7119c29 100644 --- a/src/eventPlugins/TapEventPlugin.js +++ b/src/eventPlugins/TapEventPlugin.js @@ -67,7 +67,7 @@ var dependencies = [ keyOf({topMouseUp: null}) ]; -if (EventPluginUtils.supportTouch) { +if (EventPluginUtils.useTouchEvents) { dependencies.push( keyOf({topTouchStart: null}), keyOf({topTouchMove: null}), From 98bb7fccdeceae4ffd27c9894abb318e5b0b3382 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Tue, 3 Dec 2013 10:49:37 +0100 Subject: [PATCH 20/31] Renaming variables, completing doc. --- src/core/ReactEventEmitter.js | 4 +-- src/event/EventPluginRegistry.js | 6 ++-- src/eventPlugins/CompositionEventPlugin.js | 36 +++++++++++----------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index ce9d2b700038..759dffc49d6c 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -190,7 +190,7 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { var topListenersID = mountAt._reactTopListenersID; if (!alreadyListeningTo[topListenersID][event]) { var registrationName = ReactEventEmitter.registrationNames[event]; - var dependencies = registrationName.eventTypes[EventPluginRegistry.eventMapping[event]].dependencies; + var dependencies = registrationName.eventTypes[EventPluginRegistry.domEventMapping[event]].dependencies; var topLevelTypes = EventConstants.topLevelTypes; for (var i = 0, l = dependencies.length; i < l; i++) { @@ -231,7 +231,7 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { alreadyListeningTo[keyOf({onBlur: null})] = true; alreadyListeningTo[keyOf({onFocus: null})] = true; } else { - trapBubbledEvent(topLevelType, EventPluginRegistry.eventMapping[event], mountAt); + trapBubbledEvent(topLevelType, EventPluginRegistry.domEventMapping[event], mountAt); } alreadyListeningTo[topListenersID][event] = true; diff --git a/src/event/EventPluginRegistry.js b/src/event/EventPluginRegistry.js index 148ce4670c1d..43a5d38ea9d9 100644 --- a/src/event/EventPluginRegistry.js +++ b/src/event/EventPluginRegistry.js @@ -69,7 +69,7 @@ function recomputePluginOrdering() { } else { domEventName = publishedEvents[eventName].registrationName; } - EventPluginRegistry.eventMapping[domEventName] = eventName; + EventPluginRegistry.domEventMapping[domEventName] = eventName; invariant( publishEventForPlugin(publishedEvents[eventName], PluginModule), @@ -142,9 +142,9 @@ var EventPluginRegistry = { registrationNames: {}, /** - * Mapping from on{EventName} to + * Mapping from DOM event name to registrationName */ - eventMapping: {}, + domEventMapping: {}, /** * Injects an ordering of plugins (by plugin name). This allows the ordering diff --git a/src/eventPlugins/CompositionEventPlugin.js b/src/eventPlugins/CompositionEventPlugin.js index d5da7a21087e..7b7796ca272e 100644 --- a/src/eventPlugins/CompositionEventPlugin.js +++ b/src/eventPlugins/CompositionEventPlugin.js @@ -44,12 +44,12 @@ var eventTypes = { captured: keyOf({onCompositionEndCapture: null}) }, dependencies: [ - keyOf({onCompositionEnd: null}), - keyOf({onKeyDown: null}), - keyOf({onKeyUp: null}), - keyOf({onKeyPress: null}), - keyOf({onMouseDown: null}), - keyOf({onBlur: null}) + keyOf({topBlur: null}), + keyOf({topCompositionEnd: null}), + keyOf({topKeyDown: null}), + keyOf({topKeyPress: null}), + keyOf({topKeyUp: null}), + keyOf({topMouseDown: null}) ] }, compositionStart: { @@ -58,12 +58,12 @@ var eventTypes = { captured: keyOf({onCompositionStartCapture: null}) }, dependencies: [ - keyOf({onCompositionStart: null}), - keyOf({onKeyDown: null}), - keyOf({onKeyUp: null}), - keyOf({onKeyPress: null}), - keyOf({onMouseDown: null}), - keyOf({onBlur: null}) + keyOf({topBlur: null}), + keyOf({topCompositionStart: null}), + keyOf({topKeyDown: null}), + keyOf({topKeyPress: null}), + keyOf({topKeyUp: null}), + keyOf({topMouseDown: null}) ] }, compositionUpdate: { @@ -72,12 +72,12 @@ var eventTypes = { captured: keyOf({onCompositionUpdateCapture: null}) }, dependencies: [ - keyOf({onCompositionUpdate: null}), - keyOf({onKeyDown: null}), - keyOf({onKeyUp: null}), - keyOf({onKeyPress: null}), - keyOf({onMouseDown: null}), - keyOf({onBlur: null}) + keyOf({topBlur: null}), + keyOf({topCompositionUpdate: null}), + keyOf({topKeyDown: null}), + keyOf({topKeyPress: null}), + keyOf({topKeyUp: null}), + keyOf({topMouseDown: null}) ] } }; From 6be9de597c1bc59c56a0bca02a0b64c5fa77a52b Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Wed, 4 Dec 2013 20:33:17 +0100 Subject: [PATCH 21/31] Now it actually works. --- src/core/ReactEventEmitter.js | 62 ++++++++++++++++---- src/core/__tests__/ReactEventEmitter-test.js | 3 +- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index 759dffc49d6c..07bdda2eee60 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -22,10 +22,10 @@ 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'); -var EventPluginRegistry = require('EventPluginRegistry'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); @@ -80,7 +80,45 @@ var keyOf = require('keyOf'); var alreadyListeningTo = {}; var isMonitoringScrollValue = false; var reactTopListenersCounter = 0; - +var topEventMapping = { + topBlur: keyOf({onBlur: null}), + topChange: keyOf({onChange: null}), + topClick: keyOf({onClick: null}), + topCompositionEnd: keyOf({onCompositionEnd: null}), + topCompositionStart: keyOf({onCompositionStart: null}), + topCompositionUpdate: keyOf({onCompositionUpdate: null}), + topContextMenu: keyOf({onContextMenu: null}), + topCopy: keyOf({onCopy: null}), + topCut: keyOf({onCut: null}), + topDoubleClick: keyOf({onDoubleClick: null}), + topDrag: keyOf({onDrag: null}), + topDragEnd: keyOf({onDragEnd: null}), + topDragEnter: keyOf({onDragEnter: null}), + topDragExit: keyOf({onDragExit: null}), + topDragLeave: keyOf({onDragLeave: null}), + topDragOver: keyOf({onDragOver: null}), + topDragStart: keyOf({onDragStart: null}), + topDrop: keyOf({onDrop: null}), + topFocus: keyOf({onFocus: null}), + topInput: keyOf({onInput: null}), + topKeyDown: keyOf({onKeyDown: null}), + topKeyPress: keyOf({onKeyPress: null}), + topKeyUp: keyOf({onKeyUp: null}), + topMouseDown: keyOf({onMouseDown: null}), + topMouseMove: keyOf({onMouseMove: null}), + topMouseOut: keyOf({onMouseOut: null}), + topMouseOver: keyOf({onMouseOver: null}), + topMouseUp: keyOf({onMouseUp: null}), + topPaste: keyOf({onPaste: null}), + topScroll: keyOf({onScroll: null}), + topSelectionChange: keyOf({onSelectionChange: null}), + topSubmit: keyOf({onSubmit: null}), + topTouchCancel: keyOf({onTouchCancel: null}), + topTouchEnd: keyOf({onTouchEnd: null}), + topTouchMove: keyOf({onTouchMove: null}), + topTouchStart: keyOf({onTouchStart: null}), + topWheel: keyOf({onWheel: null}) +}; /** * Traps top-level events by using event bubbling. * @@ -188,13 +226,13 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { alreadyListeningTo[mountAt._reactTopListenersID] = []; } var topListenersID = mountAt._reactTopListenersID; - if (!alreadyListeningTo[topListenersID][event]) { - var registrationName = ReactEventEmitter.registrationNames[event]; - var dependencies = registrationName.eventTypes[EventPluginRegistry.domEventMapping[event]].dependencies; + var registrationName = ReactEventEmitter.registrationNames[event]; + var dependencies = registrationName.eventTypes[EventPluginRegistry.domEventMapping[event]].dependencies; - var topLevelTypes = EventConstants.topLevelTypes; - for (var i = 0, l = dependencies.length; i < l; i++) { - var dependency = dependencies[i]; + var topLevelTypes = EventConstants.topLevelTypes; + for (var i = 0, l = dependencies.length; i < l; i++) { + var dependency = dependencies[i]; + if (!alreadyListeningTo[topListenersID][dependency]) { var topLevelType = topLevelTypes[dependency]; if (topLevelType === topLevelTypes.topWheel) { @@ -228,13 +266,13 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { } // to make sure event listeners are only attached once - alreadyListeningTo[keyOf({onBlur: null})] = true; - alreadyListeningTo[keyOf({onFocus: null})] = true; + alreadyListeningTo[topListenersID][keyOf({topBlur: null})] = true; + alreadyListeningTo[topListenersID][keyOf({topFocus: null})] = true; } else { - trapBubbledEvent(topLevelType, EventPluginRegistry.domEventMapping[event], mountAt); + trapBubbledEvent(topLevelType, EventPluginRegistry.domEventMapping[topEventMapping[dependency]], mountAt); } - alreadyListeningTo[topListenersID][event] = true; + alreadyListeningTo[topListenersID][dependency] = true; } } }, diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index 2d9fe4b7bed1..a401f03b80ef 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -338,7 +338,6 @@ describe('ReactEventEmitter', function() { ReactEventEmitter.listenTo(ON_CHANGE_KEY, document); - var SCROLL_MONITORING_EVENTS_NO = 2; var setEventListeners = []; var listenCalls = EventListener.listen.argsForCall; var captureCalls = EventListener.capture.argsForCall; @@ -351,7 +350,7 @@ describe('ReactEventEmitter', function() { var dependencies = ReactEventEmitter.registrationNames[ON_CHANGE_KEY].eventTypes.change.dependencies; - expect(setEventListeners.length).toEqual(dependencies.length + SCROLL_MONITORING_EVENTS_NO); + expect(setEventListeners.length).toEqual(dependencies.length); for(i = 0, l = setEventListeners.length; i < l; i++) { expect(dependencies.indexOf(setEventListeners[i])).toBeTruthy(); From 9b2062bda22f6b0c2b766a4d608200f92277deb6 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Wed, 4 Dec 2013 20:48:06 +0100 Subject: [PATCH 22/31] Ordering --- src/core/ReactDOMComponent.js | 2 +- src/core/ReactEventEmitter.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/ReactDOMComponent.js b/src/core/ReactDOMComponent.js index ceb40cc309fc..90f3167c0f40 100644 --- a/src/core/ReactDOMComponent.js +++ b/src/core/ReactDOMComponent.js @@ -36,8 +36,8 @@ var mixInto = require('mixInto'); var putListener = ReactEventEmitter.putListener; var deleteListener = ReactEventEmitter.deleteListener; -var registrationNames = ReactEventEmitter.registrationNames; var listenTo = ReactEventEmitter.listenTo; +var registrationNames = ReactEventEmitter.registrationNames; // For quickly matching children type, to test if can be treated as content. var CONTENT_TYPES = {'string': true, 'number': true}; diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index 07bdda2eee60..4ce379f83995 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -29,8 +29,8 @@ var ViewportMetrics = require('ViewportMetrics'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); -var merge = require('merge'); var keyOf = require('keyOf'); +var merge = require('merge'); /** * Summary of `ReactEventEmitter` event handling: From 8f294c807cecc7641005dd184c91d4a83d2c44b5 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Sun, 8 Dec 2013 18:39:17 -0800 Subject: [PATCH 23/31] - removed all the delete document['_reactTopListenersID']; - fixed some bugs - renaming of variables - changed event registration mapping --- .gitignore | 1 - src/core/ReactDOMComponent.js | 8 +- src/core/ReactEventEmitter.js | 97 ++++++++++--------- .../__tests__/ReactCompositeComponent-test.js | 2 - src/core/__tests__/ReactEventEmitter-test.js | 3 +- .../__tests__/ReactPropTransferer-test.js | 2 - .../__tests__/ReactDOMButton-test.js | 1 - .../__tests__/ReactDOMInput-test.js | 1 - .../__tests__/ReactDOMSelect-test.js | 2 - .../__tests__/ReactDOMTextarea-test.js | 2 - .../__tests__/ReactServerRendering-test.js | 1 - src/event/EventPluginHub.js | 2 +- src/event/EventPluginRegistry.js | 42 ++++---- .../__tests__/EventPluginRegistry-test.js | 12 +-- 14 files changed, 80 insertions(+), 96 deletions(-) diff --git a/.gitignore b/.gitignore index d4678809fc66..39a72e9b2da0 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,3 @@ examples/shared/*.js test/the-files-to-test.generated.js *.log* chrome-user-data -.idea \ No newline at end of file diff --git a/src/core/ReactDOMComponent.js b/src/core/ReactDOMComponent.js index 90f3167c0f40..6a31580e64f9 100644 --- a/src/core/ReactDOMComponent.js +++ b/src/core/ReactDOMComponent.js @@ -37,7 +37,7 @@ var mixInto = require('mixInto'); var putListener = ReactEventEmitter.putListener; var deleteListener = ReactEventEmitter.deleteListener; var listenTo = ReactEventEmitter.listenTo; -var registrationNames = ReactEventEmitter.registrationNames; +var registrationNameModules = ReactEventEmitter.registrationNameModules; // For quickly matching children type, to test if can be treated as content. var CONTENT_TYPES = {'string': true, 'number': true}; @@ -130,7 +130,7 @@ ReactDOMComponent.Mixin = { if (propValue == null) { continue; } - if (registrationNames[propKey]) { + if (registrationNameModules[propKey]) { var container = ReactMount.findReactContainerForID(this._rootNodeID); if (container) { var doc = container.nodeType === ELEMENT_NODE_TYPE ? @@ -254,7 +254,7 @@ ReactDOMComponent.Mixin = { styleUpdates[styleName] = ''; } } - } else if (registrationNames[propKey]) { + } else if (registrationNameModules[propKey]) { deleteListener(this._rootNodeID, propKey); } else if ( DOMProperty.isStandardName[propKey] || @@ -296,7 +296,7 @@ ReactDOMComponent.Mixin = { // Relies on `updateStylesByID` not mutating `styleUpdates`. styleUpdates = nextProp; } - } else if (registrationNames[propKey]) { + } else if (registrationNameModules[propKey]) { var container = ReactMount.findReactContainerForID(this._rootNodeID); if (container) { var doc = container.nodeType === ELEMENT_NODE_TYPE ? diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index 4ce379f83995..6df8eab4867f 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -81,44 +81,50 @@ var alreadyListeningTo = {}; var isMonitoringScrollValue = false; var reactTopListenersCounter = 0; var topEventMapping = { - topBlur: keyOf({onBlur: null}), - topChange: keyOf({onChange: null}), - topClick: keyOf({onClick: null}), - topCompositionEnd: keyOf({onCompositionEnd: null}), - topCompositionStart: keyOf({onCompositionStart: null}), - topCompositionUpdate: keyOf({onCompositionUpdate: null}), - topContextMenu: keyOf({onContextMenu: null}), - topCopy: keyOf({onCopy: null}), - topCut: keyOf({onCut: null}), - topDoubleClick: keyOf({onDoubleClick: null}), - topDrag: keyOf({onDrag: null}), - topDragEnd: keyOf({onDragEnd: null}), - topDragEnter: keyOf({onDragEnter: null}), - topDragExit: keyOf({onDragExit: null}), - topDragLeave: keyOf({onDragLeave: null}), - topDragOver: keyOf({onDragOver: null}), - topDragStart: keyOf({onDragStart: null}), - topDrop: keyOf({onDrop: null}), - topFocus: keyOf({onFocus: null}), - topInput: keyOf({onInput: null}), - topKeyDown: keyOf({onKeyDown: null}), - topKeyPress: keyOf({onKeyPress: null}), - topKeyUp: keyOf({onKeyUp: null}), - topMouseDown: keyOf({onMouseDown: null}), - topMouseMove: keyOf({onMouseMove: null}), - topMouseOut: keyOf({onMouseOut: null}), - topMouseOver: keyOf({onMouseOver: null}), - topMouseUp: keyOf({onMouseUp: null}), - topPaste: keyOf({onPaste: null}), - topScroll: keyOf({onScroll: null}), - topSelectionChange: keyOf({onSelectionChange: null}), - topSubmit: keyOf({onSubmit: null}), - topTouchCancel: keyOf({onTouchCancel: null}), - topTouchEnd: keyOf({onTouchEnd: null}), - topTouchMove: keyOf({onTouchMove: null}), - topTouchStart: keyOf({onTouchStart: null}), - topWheel: keyOf({onWheel: null}) + 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 = "_react" + String(Math.random()).slice(2); + /** * Traps top-level events by using event bubbling. * @@ -221,13 +227,13 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { */ listenTo: function(event, contentDocument) { var mountAt = contentDocument; - if (!mountAt._reactTopListenersID) { - mountAt._reactTopListenersID = '' + reactTopListenersCounter++; - alreadyListeningTo[mountAt._reactTopListenersID] = []; + if (mountAt[topListenersIDKey] == null) { + mountAt[topListenersIDKey] = reactTopListenersCounter++; + alreadyListeningTo[mountAt[topListenersIDKey]] = {}; } - var topListenersID = mountAt._reactTopListenersID; - var registrationName = ReactEventEmitter.registrationNames[event]; - var dependencies = registrationName.eventTypes[EventPluginRegistry.domEventMapping[event]].dependencies; + var topListenersID = mountAt[topListenersIDKey]; + var registrationName = ReactEventEmitter.registrationNameModules[event]; + var dependencies = registrationName.eventTypes[EventPluginRegistry.registrationNameEventNameMapping[event]].dependencies; var topLevelTypes = EventConstants.topLevelTypes; for (var i = 0, l = dependencies.length; i < l; i++) { @@ -269,7 +275,7 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { alreadyListeningTo[topListenersID][keyOf({topBlur: null})] = true; alreadyListeningTo[topListenersID][keyOf({topFocus: null})] = true; } else { - trapBubbledEvent(topLevelType, EventPluginRegistry.domEventMapping[topEventMapping[dependency]], mountAt); + trapBubbledEvent(topLevelType, topEventMapping[dependency], mountAt); } alreadyListeningTo[topListenersID][dependency] = true; @@ -294,7 +300,7 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { } }, - registrationNames: EventPluginHub.registrationNames, + registrationNameModules: EventPluginHub.registrationNameModules, putListener: EventPluginHub.putListener, @@ -310,5 +316,4 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { }); - module.exports = ReactEventEmitter; diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index 474ad2422e75..fa1b866e002e 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -78,8 +78,6 @@ describe('ReactCompositeComponent', function() { ; } }); - - delete document['_reactTopListenersID']; }); it('should support rendering to different child types over time', function() { diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index a401f03b80ef..25eeb4b0d3f5 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -101,7 +101,6 @@ describe('ReactEventEmitter', function() { EventPluginHub.injection.injectEventPluginsByName({ TapEventPlugin: TapEventPlugin }); - delete document['_reactTopListenersID']; }); it('should store a listener correctly', function() { @@ -349,7 +348,7 @@ describe('ReactEventEmitter', function() { } var dependencies = - ReactEventEmitter.registrationNames[ON_CHANGE_KEY].eventTypes.change.dependencies; + ReactEventEmitter.registrationNameModules[ON_CHANGE_KEY].eventTypes.change.dependencies; expect(setEventListeners.length).toEqual(dependencies.length); for(i = 0, l = setEventListeners.length; i < l; i++) { diff --git a/src/core/__tests__/ReactPropTransferer-test.js b/src/core/__tests__/ReactPropTransferer-test.js index 76de37f9c6d5..1854ef7e959c 100644 --- a/src/core/__tests__/ReactPropTransferer-test.js +++ b/src/core/__tests__/ReactPropTransferer-test.js @@ -44,8 +44,6 @@ describe('ReactPropTransferer', function() { ); } }); - - delete document['_reactTopListenersID']; }); it('should leave explicitly specified properties intact', function() { diff --git a/src/dom/components/__tests__/ReactDOMButton-test.js b/src/dom/components/__tests__/ReactDOMButton-test.js index 43b96122a64d..21a000018cb0 100644 --- a/src/dom/components/__tests__/ReactDOMButton-test.js +++ b/src/dom/components/__tests__/ReactDOMButton-test.js @@ -49,7 +49,6 @@ describe('ReactDOMButton', function() { beforeEach(function() { React = require('React'); ReactTestUtils = require('ReactTestUtils'); - delete document['_reactTopListenersID']; }); it('should forward clicks when it starts out not disabled', function() { diff --git a/src/dom/components/__tests__/ReactDOMInput-test.js b/src/dom/components/__tests__/ReactDOMInput-test.js index 71a271216f37..5cc2a24c5f6a 100644 --- a/src/dom/components/__tests__/ReactDOMInput-test.js +++ b/src/dom/components/__tests__/ReactDOMInput-test.js @@ -35,7 +35,6 @@ describe('ReactDOMInput', function() { React = require('React'); ReactLink = require('ReactLink'); ReactTestUtils = require('ReactTestUtils'); - delete document['_reactTopListenersID']; renderTextInput = function(component) { var stub = ReactTestUtils.renderIntoDocument(component); var node = stub.getDOMNode(); diff --git a/src/dom/components/__tests__/ReactDOMSelect-test.js b/src/dom/components/__tests__/ReactDOMSelect-test.js index db15dc76a1d6..6bbe58ca2aa7 100644 --- a/src/dom/components/__tests__/ReactDOMSelect-test.js +++ b/src/dom/components/__tests__/ReactDOMSelect-test.js @@ -40,8 +40,6 @@ describe('ReactDOMSelect', function() { var node = stub.getDOMNode(); return node; }; - - delete document['_reactTopListenersID']; }); it('should allow setting `defaultValue`', function() { diff --git a/src/dom/components/__tests__/ReactDOMTextarea-test.js b/src/dom/components/__tests__/ReactDOMTextarea-test.js index 3a1760a9d5ab..aaca8ea596b5 100644 --- a/src/dom/components/__tests__/ReactDOMTextarea-test.js +++ b/src/dom/components/__tests__/ReactDOMTextarea-test.js @@ -42,8 +42,6 @@ describe('ReactDOMTextarea', function() { node.value = node.innerHTML; return node; }; - - delete document['_reactTopListenersID']; }); it('should allow setting `defaultValue`', function() { diff --git a/src/environment/__tests__/ReactServerRendering-test.js b/src/environment/__tests__/ReactServerRendering-test.js index 20bce375e936..c208eadc569c 100644 --- a/src/environment/__tests__/ReactServerRendering-test.js +++ b/src/environment/__tests__/ReactServerRendering-test.js @@ -48,7 +48,6 @@ describe('ReactServerRendering', function() { ExecutionEnvironment.canUseDOM = false; ReactServerRendering = require('ReactServerRendering'); ReactMarkupChecksum = require('ReactMarkupChecksum'); - delete document['_reactTopListenersID']; }); it('should generate simple markup', function() { diff --git a/src/event/EventPluginHub.js b/src/event/EventPluginHub.js index ef75bb770432..3fda08ac2c4f 100644 --- a/src/event/EventPluginHub.js +++ b/src/event/EventPluginHub.js @@ -104,7 +104,7 @@ var EventPluginHub = { }, - registrationNames: EventPluginRegistry.registrationNames, + registrationNameModules: EventPluginRegistry.registrationNameModules, putListener: CallbackRegistry.putListener, diff --git a/src/event/EventPluginRegistry.js b/src/event/EventPluginRegistry.js index 43a5d38ea9d9..a4563d99fc06 100644 --- a/src/event/EventPluginRegistry.js +++ b/src/event/EventPluginRegistry.js @@ -62,17 +62,8 @@ function recomputePluginOrdering() { EventPluginRegistry.plugins[pluginIndex] = PluginModule; var publishedEvents = PluginModule.eventTypes; for (var eventName in publishedEvents) { - var phasedRegistrationNames = publishedEvents[eventName].phasedRegistrationNames; - var domEventName; - if (phasedRegistrationNames) { - domEventName = phasedRegistrationNames.bubbled; - } else { - domEventName = publishedEvents[eventName].registrationName; - } - EventPluginRegistry.domEventMapping[domEventName] = eventName; - invariant( - publishEventForPlugin(publishedEvents[eventName], PluginModule), + publishEventForPlugin(publishedEvents[eventName], PluginModule, eventName), 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.', eventName, pluginName @@ -89,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; @@ -114,14 +105,15 @@ 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.registrationNameEventNameMapping[registrationName] = eventName; } /** @@ -139,12 +131,12 @@ var EventPluginRegistry = { /** * Mapping from registration names to plugin modules. */ - registrationNames: {}, + registrationNameModules: {}, /** - * Mapping from DOM event name to registrationName + * Mapping from registration name to event name */ - domEventMapping: {}, + registrationNameEventNameMapping: {}, /** * Injects an ordering of plugins (by plugin name). This allows the ordering @@ -208,7 +200,7 @@ var EventPluginRegistry = { getPluginModuleForEvent: function(event) { var dispatchConfig = event.dispatchConfig; if (dispatchConfig.registrationName) { - return EventPluginRegistry.registrationNames[ + return EventPluginRegistry.registrationNameModules[ dispatchConfig.registrationName ] || null; } @@ -216,7 +208,7 @@ var EventPluginRegistry = { if (!dispatchConfig.phasedRegistrationNames.hasOwnProperty(phase)) { continue; } - var PluginModule = EventPluginRegistry.registrationNames[ + var PluginModule = EventPluginRegistry.registrationNameModules[ dispatchConfig.phasedRegistrationNames[phase] ]; if (PluginModule) { @@ -238,10 +230,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]; } } } diff --git a/src/event/__tests__/EventPluginRegistry-test.js b/src/event/__tests__/EventPluginRegistry-test.js index 7474525e56f6..2c18076447a0 100644 --- a/src/event/__tests__/EventPluginRegistry-test.js +++ b/src/event/__tests__/EventPluginRegistry-test.js @@ -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); }); From e9b7e5723b5b4394dcda06178df0d9967ea9fdc8 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Sun, 8 Dec 2013 19:00:10 -0800 Subject: [PATCH 24/31] Staying closer to the 80 character limit --- src/core/ReactEventEmitter.js | 6 ++++-- src/core/__tests__/ReactComponent-test.js | 1 - src/core/__tests__/ReactEventEmitter-test.js | 7 ++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index 6df8eab4867f..45f543ea99d5 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -233,7 +233,8 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { } var topListenersID = mountAt[topListenersIDKey]; var registrationName = ReactEventEmitter.registrationNameModules[event]; - var dependencies = registrationName.eventTypes[EventPluginRegistry.registrationNameEventNameMapping[event]].dependencies; + var dependencies = registrationName.eventTypes[EventPluginRegistry. + registrationNameEventNameMapping[event]].dependencies; var topLevelTypes = EventConstants.topLevelTypes; for (var i = 0, l = dependencies.length; i < l; i++) { @@ -249,7 +250,8 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { } 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); + trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', + mountAt); } } else if (topLevelType === topLevelTypes.topScroll) { diff --git a/src/core/__tests__/ReactComponent-test.js b/src/core/__tests__/ReactComponent-test.js index 4c1b54e64a21..7b045c1dcc39 100644 --- a/src/core/__tests__/ReactComponent-test.js +++ b/src/core/__tests__/ReactComponent-test.js @@ -208,5 +208,4 @@ describe('ReactComponent', function() { expect(root.refs.switcher.refs.box.refs.boxDiv._mountDepth).toBe(3); expect(root.refs.child.refs.span._mountDepth).toBe(6); }); - }); diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index 25eeb4b0d3f5..9d7e2054fa5f 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -79,7 +79,8 @@ setID(GRANDPARENT, '.reactRoot.[0]'); function registerSimpleTestHandler() { ReactEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, LISTENER); - var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY); + var listener = ReactEventEmitter.getListener(getID(CHILD), + ON_CLICK_KEY); expect(listener).toEqual(LISTENER); return ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY); } @@ -347,8 +348,8 @@ describe('ReactEventEmitter', function() { setEventListeners.push(captureCalls[i][1]); } - var dependencies = - ReactEventEmitter.registrationNameModules[ON_CHANGE_KEY].eventTypes.change.dependencies; + var dependencies = ReactEventEmitter.registrationNameModules[ON_CHANGE_KEY]. + eventTypes.change.dependencies; expect(setEventListeners.length).toEqual(dependencies.length); for(i = 0, l = setEventListeners.length; i < l; i++) { From 4c73f3d713db7f2df4967ad0fb6fb71864554b02 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Mon, 9 Dec 2013 01:45:33 -0800 Subject: [PATCH 25/31] Naming + code formatting --- src/core/ReactEventEmitter.js | 8 +++++--- src/core/__tests__/ReactEventEmitter-test.js | 3 +-- src/event/EventPluginRegistry.js | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index 45f543ea99d5..a6cbd3dfdebc 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -123,7 +123,7 @@ var topEventMapping = { /** * To ensure no conflicts with other potential React instances on the page */ -var topListenersIDKey = "_react" + String(Math.random()).slice(2); +var topListenersIDKey = "_reactListenersID" + String(Math.random()).slice(2); /** * Traps top-level events by using event bubbling. @@ -234,7 +234,7 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { var topListenersID = mountAt[topListenersIDKey]; var registrationName = ReactEventEmitter.registrationNameModules[event]; var dependencies = registrationName.eventTypes[EventPluginRegistry. - registrationNameEventNameMapping[event]].dependencies; + registrationNameEventNames[event]].dependencies; var topLevelTypes = EventConstants.topLevelTypes; for (var i = 0, l = dependencies.length; i < l; i++) { @@ -250,7 +250,9 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { } else { // Firefox needs to capture a different mouse scroll event. // @see http://www.quirksmode.org/dom/events/tests/scroll.html - trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', + trapBubbledEvent( + topLevelTypes.topWheel, + 'DOMMouseScroll', mountAt); } } else if (topLevelType === topLevelTypes.topScroll) { diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index 9d7e2054fa5f..5f0ea8cd7d4d 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -79,8 +79,7 @@ setID(GRANDPARENT, '.reactRoot.[0]'); function registerSimpleTestHandler() { ReactEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, LISTENER); - var listener = ReactEventEmitter.getListener(getID(CHILD), - ON_CLICK_KEY); + var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY); expect(listener).toEqual(LISTENER); return ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY); } diff --git a/src/event/EventPluginRegistry.js b/src/event/EventPluginRegistry.js index a4563d99fc06..da84add32e06 100644 --- a/src/event/EventPluginRegistry.js +++ b/src/event/EventPluginRegistry.js @@ -113,7 +113,7 @@ function publishRegistrationName(registrationName, PluginModule, eventName) { registrationName ); EventPluginRegistry.registrationNameModules[registrationName] = PluginModule; - EventPluginRegistry.registrationNameEventNameMapping[registrationName] = eventName; + EventPluginRegistry.registrationNameEventNames[registrationName] = eventName; } /** @@ -136,7 +136,7 @@ var EventPluginRegistry = { /** * Mapping from registration name to event name */ - registrationNameEventNameMapping: {}, + registrationNameEventNames: {}, /** * Injects an ordering of plugins (by plugin name). This allows the ordering From a892ec3c2f9789083533d590cd7dec9797977d40 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Mon, 9 Dec 2013 07:19:38 -0800 Subject: [PATCH 26/31] Making comment clearer --- src/core/ReactEventEmitter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index a6cbd3dfdebc..e7455732606e 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -275,7 +275,7 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt); } - // to make sure event listeners are only attached once + // to make sure blur and focus event listeners are only attached once alreadyListeningTo[topListenersID][keyOf({topBlur: null})] = true; alreadyListeningTo[topListenersID][keyOf({topFocus: null})] = true; } else { From 020e50a10cc3b8c14a4e15d6f38bd4d5d5af63b1 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Mon, 9 Dec 2013 16:23:48 -0800 Subject: [PATCH 27/31] Simplifying SimpleEventPlugin --- src/eventPlugins/SimpleEventPlugin.js | 154 ++++++-------------------- 1 file changed, 34 insertions(+), 120 deletions(-) diff --git a/src/eventPlugins/SimpleEventPlugin.js b/src/eventPlugins/SimpleEventPlugin.js index 07850b2067a7..6cfb4f407f70 100644 --- a/src/eventPlugins/SimpleEventPlugin.js +++ b/src/eventPlugins/SimpleEventPlugin.js @@ -39,172 +39,115 @@ var eventTypes = { phasedRegistrationNames: { bubbled: keyOf({onBlur: true}), captured: keyOf({onBlurCapture: true}) - }, - dependencies: [ - keyOf({topBlur: null}) - ] + } }, click: { phasedRegistrationNames: { bubbled: keyOf({onClick: true}), captured: keyOf({onClickCapture: true}) - }, - dependencies: [ - keyOf({topClick: null}) - ] + } }, contextMenu: { phasedRegistrationNames: { bubbled: keyOf({onContextMenu: true}), captured: keyOf({onContextMenuCapture: true}) - }, - dependencies: [ - keyOf({topContextMenu: null}) - ] + } }, copy: { phasedRegistrationNames: { bubbled: keyOf({onCopy: true}), captured: keyOf({onCopyCapture: true}) - }, - dependencies: [ - keyOf({topCopy: null}) - ] + } }, cut: { phasedRegistrationNames: { bubbled: keyOf({onCut: true}), captured: keyOf({onCutCapture: true}) - }, - dependencies: [ - keyOf({topCut: null}) - ] + } }, doubleClick: { phasedRegistrationNames: { bubbled: keyOf({onDoubleClick: true}), captured: keyOf({onDoubleClickCapture: true}) - }, - dependencies: [ - keyOf({topDoubleClick: null}) - ] + } }, drag: { phasedRegistrationNames: { bubbled: keyOf({onDrag: true}), captured: keyOf({onDragCapture: true}) - }, - dependencies: [ - keyOf({topDrag: null}) - ] + } }, dragEnd: { phasedRegistrationNames: { bubbled: keyOf({onDragEnd: true}), captured: keyOf({onDragEndCapture: true}) - }, - dependencies: [ - keyOf({topDragEnd: null}) - ] + } }, dragEnter: { phasedRegistrationNames: { bubbled: keyOf({onDragEnter: true}), captured: keyOf({onDragEnterCapture: true}) - }, - dependencies: [ - keyOf({topDragEnter: null}) - ] + } }, dragExit: { phasedRegistrationNames: { bubbled: keyOf({onDragExit: true}), captured: keyOf({onDragExitCapture: true}) - }, - dependencies: [ - keyOf({topDragExit: null}) - ] + } }, dragLeave: { phasedRegistrationNames: { bubbled: keyOf({onDragLeave: true}), captured: keyOf({onDragLeaveCapture: true}) - }, - dependencies: [ - keyOf({topDragLeave: null}) - ] + } }, dragOver: { phasedRegistrationNames: { bubbled: keyOf({onDragOver: true}), captured: keyOf({onDragOverCapture: true}) - }, - dependencies: [ - keyOf({topDragOver: null}) - ] + } }, dragStart: { phasedRegistrationNames: { bubbled: keyOf({onDragStart: true}), captured: keyOf({onDragStartCapture: true}) - }, - dependencies: [ - keyOf({topDragStart: null}) - ] + } }, drop: { phasedRegistrationNames: { bubbled: keyOf({onDrop: true}), captured: keyOf({onDropCapture: true}) - }, - dependencies: [ - keyOf({topDrop: null}) - ] + } }, focus: { phasedRegistrationNames: { bubbled: keyOf({onFocus: true}), captured: keyOf({onFocusCapture: true}) - }, - dependencies: [ - keyOf({topFocus: null}) - ] + } }, input: { phasedRegistrationNames: { bubbled: keyOf({onInput: true}), captured: keyOf({onInputCapture: true}) - }, - dependencies: [ - keyOf({topInput: null}) - ] + } }, keyDown: { phasedRegistrationNames: { bubbled: keyOf({onKeyDown: true}), captured: keyOf({onKeyDownCapture: true}) - }, - dependencies: [ - keyOf({topKeyDown: null}) - ] + } }, keyPress: { phasedRegistrationNames: { bubbled: keyOf({onKeyPress: true}), captured: keyOf({onKeyPressCapture: true}) - }, - dependencies: [ - keyOf({topKeyPress: null}) - ] + } }, keyUp: { phasedRegistrationNames: { bubbled: keyOf({onKeyUp: true}), captured: keyOf({onKeyUpCapture: true}) - }, - dependencies: [ - keyOf({topKeyUp: null}) - ] + } }, // Note: We do not allow listening to mouseOver events. Instead, use the // onMouseEnter/onMouseLeave created by `EnterLeaveEventPlugin`. @@ -212,100 +155,67 @@ var eventTypes = { phasedRegistrationNames: { bubbled: keyOf({onMouseDown: true}), captured: keyOf({onMouseDownCapture: true}) - }, - dependencies: [ - keyOf({topMouseDown: null}) - ] + } }, mouseMove: { phasedRegistrationNames: { bubbled: keyOf({onMouseMove: true}), captured: keyOf({onMouseMoveCapture: true}) - }, - dependencies: [ - keyOf({topMouseMove: null}) - ] + } }, mouseUp: { phasedRegistrationNames: { bubbled: keyOf({onMouseUp: true}), captured: keyOf({onMouseUpCapture: true}) - }, - dependencies: [ - keyOf({topMouseUp: null}) - ] + } }, paste: { phasedRegistrationNames: { bubbled: keyOf({onPaste: true}), captured: keyOf({onPasteCapture: true}) - }, - dependencies: [ - keyOf({topPaste: null}) - ] + } }, scroll: { phasedRegistrationNames: { bubbled: keyOf({onScroll: true}), captured: keyOf({onScrollCapture: true}) - }, - dependencies: [ - keyOf({topScroll: null}) - ] + } }, submit: { phasedRegistrationNames: { bubbled: keyOf({onSubmit: true}), captured: keyOf({onSubmitCapture: true}) - }, - dependencies: [ - keyOf({topSubmit: null}) - ] + } }, touchCancel: { phasedRegistrationNames: { bubbled: keyOf({onTouchCancel: true}), captured: keyOf({onTouchCancelCapture: true}) - }, - dependencies: [ - keyOf({topTouchCancel: null}) - ] + } }, touchEnd: { phasedRegistrationNames: { bubbled: keyOf({onTouchEnd: true}), captured: keyOf({onTouchEndCapture: true}) - }, - dependencies: [ - keyOf({topTouchEnd: null}) - ] + } }, touchMove: { phasedRegistrationNames: { bubbled: keyOf({onTouchMove: true}), captured: keyOf({onTouchMoveCapture: true}) - }, - dependencies: [ - keyOf({topTouchMove: null}) - ] + } }, touchStart: { phasedRegistrationNames: { bubbled: keyOf({onTouchStart: true}), captured: keyOf({onTouchStartCapture: true}) - }, - dependencies: [ - keyOf({topTouchStart: null}) - ] + } }, wheel: { phasedRegistrationNames: { bubbled: keyOf({onWheel: true}), captured: keyOf({onWheelCapture: true}) - }, - dependencies: [ - keyOf({topWheel: null}) - ] + } } }; @@ -342,6 +252,10 @@ var topLevelEventsToDispatchConfig = { topWheel: eventTypes.wheel }; +for (var topLevelType in topLevelEventsToDispatchConfig) { + topLevelEventsToDispatchConfig[topLevelType].dependencies = [topLevelType]; +} + var SimpleEventPlugin = { eventTypes: eventTypes, From d32cf27d2b98255728c986d2c5d54f1bd2e142b0 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Fri, 3 Jan 2014 15:54:54 +0100 Subject: [PATCH 28/31] Merge branch 'master' of https://github.com/facebook/react into on-demand-events Conflicts: src/core/React.js --- examples/basic-jsx/index.html | 5 ++++- src/core/ReactDOMComponent.js | 1 + src/core/ReactEventEmitter.js | 10 +++++----- src/core/ReactMount.js | 3 ++- src/core/__tests__/ReactEventEmitter-test.js | 4 ++-- src/dom/components/__tests__/ReactDOMInput-test.js | 1 + 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/examples/basic-jsx/index.html b/examples/basic-jsx/index.html index 6fe8133eb9c9..c42b620708c1 100644 --- a/examples/basic-jsx/index.html +++ b/examples/basic-jsx/index.html @@ -40,13 +40,16 @@

Example Details

* @jsx React.DOM */ var ExampleApplication = React.createClass({ + _onClick: function() { + console.log('clickity click'); + }, render: function() { var elapsed = Math.round(this.props.elapsed / 100); var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' ); var message = 'React has been successfully running for ' + seconds + ' seconds.'; - return

{message}

; + return

{message}

; } }); var start = new Date().getTime(); diff --git a/src/core/ReactDOMComponent.js b/src/core/ReactDOMComponent.js index afce32c32354..b8da87b63703 100644 --- a/src/core/ReactDOMComponent.js +++ b/src/core/ReactDOMComponent.js @@ -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'); diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index e7455732606e..875c4d53ae32 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -222,19 +222,19 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but * they bubble to document. * - * @param {string} event + * @param {string} registrationName Name of listener (e.g. `onClick`). * @param {DOMDocument} contentDocument Document which owns the container */ - listenTo: function(event, contentDocument) { + listenTo: function(registrationName, contentDocument) { var mountAt = contentDocument; if (mountAt[topListenersIDKey] == null) { mountAt[topListenersIDKey] = reactTopListenersCounter++; alreadyListeningTo[mountAt[topListenersIDKey]] = {}; } var topListenersID = mountAt[topListenersIDKey]; - var registrationName = ReactEventEmitter.registrationNameModules[event]; - var dependencies = registrationName.eventTypes[EventPluginRegistry. - registrationNameEventNames[event]].dependencies; + var eventPlugin = ReactEventEmitter.registrationNameModules[registrationName]; + var dependencies = eventPlugin.eventTypes[EventPluginRegistry. + registrationNameEventNames[registrationName]].dependencies; var topLevelTypes = EventConstants.topLevelTypes; for (var i = 0, l = dependencies.length; i < l; i++) { diff --git a/src/core/ReactMount.js b/src/core/ReactMount.js index f28cf369dd01..7947c91bdeaf 100644 --- a/src/core/ReactMount.js +++ b/src/core/ReactMount.js @@ -235,7 +235,8 @@ var ReactMount = { }, /** - * Register a component into the instance map and starts scroll value monitoring + * 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 diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index 5b2ae4206a1c..3f6ec76be016 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -348,8 +348,8 @@ describe('ReactEventEmitter', function() { setEventListeners.push(captureCalls[i][1]); } - var dependencies = ReactEventEmitter.registrationNameModules[ON_CHANGE_KEY]. - eventTypes.change.dependencies; + 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++) { diff --git a/src/dom/components/__tests__/ReactDOMInput-test.js b/src/dom/components/__tests__/ReactDOMInput-test.js index 5cc2a24c5f6a..0ff9ee05f1ad 100644 --- a/src/dom/components/__tests__/ReactDOMInput-test.js +++ b/src/dom/components/__tests__/ReactDOMInput-test.js @@ -35,6 +35,7 @@ describe('ReactDOMInput', function() { React = require('React'); ReactLink = require('ReactLink'); ReactTestUtils = require('ReactTestUtils'); + renderTextInput = function(component) { var stub = ReactTestUtils.renderIntoDocument(component); var node = stub.getDOMNode(); From 38f21b857a2ce18d152bbe5442f473c6d3d7e5bb Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Fri, 3 Jan 2014 16:41:48 +0100 Subject: [PATCH 29/31] Accidentally pushed testing code --- examples/basic-jsx/index.html | 5 +---- src/core/__tests__/ReactEventEmitter-test.js | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/examples/basic-jsx/index.html b/examples/basic-jsx/index.html index c42b620708c1..6fe8133eb9c9 100644 --- a/examples/basic-jsx/index.html +++ b/examples/basic-jsx/index.html @@ -40,16 +40,13 @@

Example Details

* @jsx React.DOM */ var ExampleApplication = React.createClass({ - _onClick: function() { - console.log('clickity click'); - }, render: function() { var elapsed = Math.round(this.props.elapsed / 100); var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' ); var message = 'React has been successfully running for ' + seconds + ' seconds.'; - return

{message}

; + return

{message}

; } }); var start = new Date().getTime(); diff --git a/src/core/__tests__/ReactEventEmitter-test.js b/src/core/__tests__/ReactEventEmitter-test.js index 3f6ec76be016..55bbf8af5251 100644 --- a/src/core/__tests__/ReactEventEmitter-test.js +++ b/src/core/__tests__/ReactEventEmitter-test.js @@ -341,10 +341,10 @@ describe('ReactEventEmitter', function() { var setEventListeners = []; var listenCalls = EventListener.listen.argsForCall; var captureCalls = EventListener.capture.argsForCall; - for(var i = 0, l = listenCalls.length; i < l; i++) { + for (var i = 0, l = listenCalls.length; i < l; i++) { setEventListeners.push(listenCalls[i][1]); } - for(i = 0, l = captureCalls.length; i < l; i++) { + for (i = 0, l = captureCalls.length; i < l; i++) { setEventListeners.push(captureCalls[i][1]); } @@ -352,7 +352,7 @@ describe('ReactEventEmitter', function() { var dependencies = module.eventTypes.change.dependencies; expect(setEventListeners.length).toEqual(dependencies.length); - for(i = 0, l = setEventListeners.length; i < l; i++) { + for (i = 0, l = setEventListeners.length; i < l; i++) { expect(dependencies.indexOf(setEventListeners[i])).toBeTruthy(); } }); From 98e6f96af28820eec709896a858c1eeb26c0854c Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Fri, 3 Jan 2014 19:50:59 +0100 Subject: [PATCH 30/31] Implemented several requested changes --- src/core/ReactDOMComponent.js | 28 ++++++++--------- src/eventPlugins/ChangeEventPlugin.js | 12 ++++---- src/eventPlugins/CompositionEventPlugin.js | 36 +++++++++++----------- src/eventPlugins/EnterLeaveEventPlugin.js | 8 ++--- src/eventPlugins/SelectEventPlugin.js | 12 ++++---- src/eventPlugins/TapEventPlugin.js | 16 +++++----- 6 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/core/ReactDOMComponent.js b/src/core/ReactDOMComponent.js index b8da87b63703..44e983510f0b 100644 --- a/src/core/ReactDOMComponent.js +++ b/src/core/ReactDOMComponent.js @@ -34,7 +34,6 @@ var keyOf = require('keyOf'); var merge = require('merge'); var mixInto = require('mixInto'); -var putListener = ReactEventEmitter.putListener; var deleteListener = ReactEventEmitter.deleteListener; var listenTo = ReactEventEmitter.listenTo; var registrationNameModules = ReactEventEmitter.registrationNameModules; @@ -65,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 @@ -131,13 +143,6 @@ ReactDOMComponent.Mixin = { continue; } if (registrationNameModules[propKey]) { - var container = ReactMount.findReactContainerForID(this._rootNodeID); - if (container) { - var doc = container.nodeType === ELEMENT_NODE_TYPE ? - container.ownerDocument : - container; - listenTo(propKey, doc); - } putListener(this._rootNodeID, propKey, propValue); } else { if (propKey === STYLE) { @@ -297,13 +302,6 @@ ReactDOMComponent.Mixin = { styleUpdates = nextProp; } } else if (registrationNameModules[propKey]) { - var container = ReactMount.findReactContainerForID(this._rootNodeID); - if (container) { - var doc = container.nodeType === ELEMENT_NODE_TYPE ? - container.ownerDocument : - container; - listenTo(propKey, doc); - } putListener(this._rootNodeID, propKey, nextProp); } else if ( DOMProperty.isStandardName[propKey] || diff --git a/src/eventPlugins/ChangeEventPlugin.js b/src/eventPlugins/ChangeEventPlugin.js index 2eb1e93e0358..601208ddf7e9 100644 --- a/src/eventPlugins/ChangeEventPlugin.js +++ b/src/eventPlugins/ChangeEventPlugin.js @@ -37,12 +37,12 @@ var eventTypes = { captured: keyOf({onChangeCapture: null}) }, dependencies: [ - keyOf({topBlur: null}), - keyOf({topChange: null}), - keyOf({topFocus: null}), - keyOf({topInput: null}), - keyOf({topKeyDown: null}), - keyOf({topKeyUp: null}) + topLevelTypes.topBlur, + topLevelTypes.topChange, + topLevelTypes.topFocus, + topLevelTypes.topInput, + topLevelTypes.topKeyDown, + topLevelTypes.topKeyUp ] } }; diff --git a/src/eventPlugins/CompositionEventPlugin.js b/src/eventPlugins/CompositionEventPlugin.js index 7b7796ca272e..dbe44d14cb17 100644 --- a/src/eventPlugins/CompositionEventPlugin.js +++ b/src/eventPlugins/CompositionEventPlugin.js @@ -44,12 +44,12 @@ var eventTypes = { captured: keyOf({onCompositionEndCapture: null}) }, dependencies: [ - keyOf({topBlur: null}), - keyOf({topCompositionEnd: null}), - keyOf({topKeyDown: null}), - keyOf({topKeyPress: null}), - keyOf({topKeyUp: null}), - keyOf({topMouseDown: null}) + topLevelTypes.topBlur, + topLevelTypes.topCompositionEnd, + topLevelTypes.topKeyDown, + topLevelTypes.topKeyPress, + topLevelTypes.topKeyUp, + topLevelTypes.topMouseDown ] }, compositionStart: { @@ -58,12 +58,12 @@ var eventTypes = { captured: keyOf({onCompositionStartCapture: null}) }, dependencies: [ - keyOf({topBlur: null}), - keyOf({topCompositionStart: null}), - keyOf({topKeyDown: null}), - keyOf({topKeyPress: null}), - keyOf({topKeyUp: null}), - keyOf({topMouseDown: null}) + topLevelTypes.topBlur, + topLevelTypes.topCompositionStart, + topLevelTypes.topKeyDown, + topLevelTypes.topKeyPress, + topLevelTypes.topKeyUp, + topLevelTypes.topMouseDown ] }, compositionUpdate: { @@ -72,12 +72,12 @@ var eventTypes = { captured: keyOf({onCompositionUpdateCapture: null}) }, dependencies: [ - keyOf({topBlur: null}), - keyOf({topCompositionUpdate: null}), - keyOf({topKeyDown: null}), - keyOf({topKeyPress: null}), - keyOf({topKeyUp: null}), - keyOf({topMouseDown: null}) + topLevelTypes.topBlur, + topLevelTypes.topCompositionUpdate, + topLevelTypes.topKeyDown, + topLevelTypes.topKeyPress, + topLevelTypes.topKeyUp, + topLevelTypes.topMouseDown ] } }; diff --git a/src/eventPlugins/EnterLeaveEventPlugin.js b/src/eventPlugins/EnterLeaveEventPlugin.js index 7abbb6f1ecea..3ec57cb2c9bf 100644 --- a/src/eventPlugins/EnterLeaveEventPlugin.js +++ b/src/eventPlugins/EnterLeaveEventPlugin.js @@ -33,15 +33,15 @@ var eventTypes = { mouseEnter: { registrationName: keyOf({onMouseEnter: null}), dependencies: [ - keyOf({topMouseOut: null}), - keyOf({topMouseOver: null}) + topLevelTypes.topMouseOut, + topLevelTypes.topMouseOver ] }, mouseLeave: { registrationName: keyOf({onMouseLeave: null}), dependencies: [ - keyOf({topMouseOut: null}), - keyOf({topMouseOver: null}) + topLevelTypes.topMouseOut, + topLevelTypes.topMouseOver ] } }; diff --git a/src/eventPlugins/SelectEventPlugin.js b/src/eventPlugins/SelectEventPlugin.js index a5cc3f0e9101..3b19274f849d 100644 --- a/src/eventPlugins/SelectEventPlugin.js +++ b/src/eventPlugins/SelectEventPlugin.js @@ -39,12 +39,12 @@ var eventTypes = { captured: keyOf({onSelectCapture: null}) }, dependencies: [ - keyOf({topBlur: null}), - keyOf({topFocus: null}), - keyOf({topKeyDown: null}), - keyOf({topMouseDown: null}), - keyOf({topMouseUp: null}), - keyOf({topSelectionChange: null}) + topLevelTypes.topBlur, + topLevelTypes.topFocus, + topLevelTypes.topKeyDown, + topLevelTypes.topMouseDown, + topLevelTypes.topMouseUp, + topLevelTypes.topSelectionChange ] } }; diff --git a/src/eventPlugins/TapEventPlugin.js b/src/eventPlugins/TapEventPlugin.js index bd45f7119c29..779ecadf09eb 100644 --- a/src/eventPlugins/TapEventPlugin.js +++ b/src/eventPlugins/TapEventPlugin.js @@ -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; @@ -62,17 +64,17 @@ function getDistance(coords, nativeEvent) { } var dependencies = [ - keyOf({topMouseDown: null}), - keyOf({topMouseMove: null}), - keyOf({topMouseUp: null}) + topLevelTypes.topMouseDown, + topLevelTypes.topMouseMove, + topLevelTypes.topMouseUp ]; if (EventPluginUtils.useTouchEvents) { dependencies.push( - keyOf({topTouchStart: null}), - keyOf({topTouchMove: null}), - keyOf({topTouchEnd: null}), - keyOf({topTouchCancel: null}) + topLevelTypes.topTouchCancel, + topLevelTypes.topTouchEnd, + topLevelTypes.topTouchStart, + topLevelTypes.topTouchMove ); } From 4e47221bcff1da8108c192d98650fae78d8083a6 Mon Sep 17 00:00:00 2001 From: SanderSpies Date: Fri, 3 Jan 2014 21:54:49 +0100 Subject: [PATCH 31/31] implemented getListeningForDocument registrationNameEventNames -> registrationNameDependencies --- src/core/ReactEventEmitter.js | 28 +++++++++++++++------------- src/event/EventPluginRegistry.js | 5 +++-- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index 875c4d53ae32..f98fc0a86bc5 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -29,7 +29,6 @@ var ViewportMetrics = require('ViewportMetrics'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); -var keyOf = require('keyOf'); var merge = require('merge'); /** @@ -125,6 +124,14 @@ var topEventMapping = { */ 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. * @@ -227,19 +234,14 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { */ listenTo: function(registrationName, contentDocument) { var mountAt = contentDocument; - if (mountAt[topListenersIDKey] == null) { - mountAt[topListenersIDKey] = reactTopListenersCounter++; - alreadyListeningTo[mountAt[topListenersIDKey]] = {}; - } - var topListenersID = mountAt[topListenersIDKey]; - var eventPlugin = ReactEventEmitter.registrationNameModules[registrationName]; - var dependencies = eventPlugin.eventTypes[EventPluginRegistry. - registrationNameEventNames[registrationName]].dependencies; + var listeningDocument = getListeningDocument(mountAt); + var dependencies = EventPluginRegistry. + registrationNameDependencies[registrationName]; var topLevelTypes = EventConstants.topLevelTypes; for (var i = 0, l = dependencies.length; i < l; i++) { var dependency = dependencies[i]; - if (!alreadyListeningTo[topListenersID][dependency]) { + if (!listeningDocument[dependency]) { var topLevelType = topLevelTypes[dependency]; if (topLevelType === topLevelTypes.topWheel) { @@ -276,13 +278,13 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { } // to make sure blur and focus event listeners are only attached once - alreadyListeningTo[topListenersID][keyOf({topBlur: null})] = true; - alreadyListeningTo[topListenersID][keyOf({topFocus: null})] = true; + listeningDocument[topLevelTypes.topBlur] = true; + listeningDocument[topLevelTypes.topFocus] = true; } else { trapBubbledEvent(topLevelType, topEventMapping[dependency], mountAt); } - alreadyListeningTo[topListenersID][dependency] = true; + listeningDocument[dependency] = true; } } }, diff --git a/src/event/EventPluginRegistry.js b/src/event/EventPluginRegistry.js index da84add32e06..a2ab2305d111 100644 --- a/src/event/EventPluginRegistry.js +++ b/src/event/EventPluginRegistry.js @@ -113,7 +113,8 @@ function publishRegistrationName(registrationName, PluginModule, eventName) { registrationName ); EventPluginRegistry.registrationNameModules[registrationName] = PluginModule; - EventPluginRegistry.registrationNameEventNames[registrationName] = eventName; + EventPluginRegistry.registrationNameDependencies[registrationName] = + PluginModule.eventTypes[eventName].dependencies; } /** @@ -136,7 +137,7 @@ var EventPluginRegistry = { /** * Mapping from registration name to event name */ - registrationNameEventNames: {}, + registrationNameDependencies: {}, /** * Injects an ordering of plugins (by plugin name). This allows the ordering