diff --git a/src/browser/ReactEventEmitter.js b/src/browser/ReactEventEmitter.js index 5f1956d7d9db4..26cbfdfee3698 100644 --- a/src/browser/ReactEventEmitter.js +++ b/src/browser/ReactEventEmitter.js @@ -144,10 +144,12 @@ function getListeningForDocument(mountAt) { * @param {string} topLevelType Record from `EventConstants`. * @param {string} handlerBaseName Event name (e.g. "click"). * @param {DOMEventTarget} element Element on which to attach listener. + * @return {object} An object with a remove function which will forcefully + * remove the listener. * @internal */ function trapBubbledEvent(topLevelType, handlerBaseName, element) { - EventListener.listen( + return EventListener.listen( element, handlerBaseName, ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback( @@ -162,10 +164,12 @@ function trapBubbledEvent(topLevelType, handlerBaseName, element) { * @param {string} topLevelType Record from `EventConstants`. * @param {string} handlerBaseName Event name (e.g. "click"). * @param {DOMEventTarget} element Element on which to attach listener. + * @return {object} An object with a remove function which will forcefully + * remove the listener. * @internal */ function trapCapturedEvent(topLevelType, handlerBaseName, element) { - EventListener.capture( + return EventListener.capture( element, handlerBaseName, ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback( diff --git a/src/browser/ui/dom/components/LocalEventTrapMixin.js b/src/browser/ui/dom/components/LocalEventTrapMixin.js new file mode 100644 index 0000000000000..1e4ec4f101d2f --- /dev/null +++ b/src/browser/ui/dom/components/LocalEventTrapMixin.js @@ -0,0 +1,52 @@ +/** + * Copyright 2014 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 LocalEventTrapMixin + */ + +"use strict"; + +var ReactEventEmitter = require('ReactEventEmitter'); + +var accumulate = require('accumulate'); +var forEachAccumulated = require('forEachAccumulated'); +var invariant = require('invariant'); + +function remove(event) { + event.remove(); +} + +var LocalEventTrapMixin = { + trapBubbledEvent(topLevelType, handlerBaseName) { + invariant(this.isMounted(), 'Must be mounted to trap events'); + var listener = ReactEventEmitter.trapBubbledEvent( + topLevelType, + handlerBaseName, + this.getDOMNode() + ); + this._localEventListeners = accumulate(this._localEventListeners, listener); + }, + + // trapCapturedEvent would look nearly identical. We don't implement that + // method because it isn't currently needed. + + componentWillUnmount() { + if (this._localEventListeners) { + forEachAccumulated(this._localEventListeners, remove); + } + } +}; + +module.exports = LocalEventTrapMixin; diff --git a/src/browser/ui/dom/components/ReactDOMForm.js b/src/browser/ui/dom/components/ReactDOMForm.js index b8696be86085b..867f046b03c38 100644 --- a/src/browser/ui/dom/components/ReactDOMForm.js +++ b/src/browser/ui/dom/components/ReactDOMForm.js @@ -18,11 +18,11 @@ "use strict"; +var EventConstants = require('EventConstants'); +var LocalEventTrapMixin = require('LocalEventTrapMixin'); var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin'); var ReactCompositeComponent = require('ReactCompositeComponent'); var ReactDOM = require('ReactDOM'); -var ReactEventEmitter = require('ReactEventEmitter'); -var EventConstants = require('EventConstants'); // Store a reference to the
`ReactDOMComponent`. var form = ReactDOM.form; @@ -36,7 +36,7 @@ var form = ReactDOM.form; var ReactDOMForm = ReactCompositeComponent.createClass({ displayName: 'ReactDOMForm', - mixins: [ReactBrowserComponentMixin], + mixins: [ReactBrowserComponentMixin, LocalEventTrapMixin], render: function() { // TODO: Instead of using `ReactDOM` directly, we should use JSX. However, @@ -46,16 +46,8 @@ var ReactDOMForm = ReactCompositeComponent.createClass({ }, componentDidMount: function() { - ReactEventEmitter.trapBubbledEvent( - EventConstants.topLevelTypes.topReset, - 'reset', - this.getDOMNode() - ); - ReactEventEmitter.trapBubbledEvent( - EventConstants.topLevelTypes.topSubmit, - 'submit', - this.getDOMNode() - ); + this.trapBubbledEvent(EventConstants.topLevelTypes.topReset, 'reset'); + this.trapBubbledEvent(EventConstants.topLevelTypes.topSubmit, 'submit'); } }); diff --git a/src/browser/ui/dom/components/ReactDOMImg.js b/src/browser/ui/dom/components/ReactDOMImg.js index 46b154f69d6dd..c6cb53dcd3c95 100644 --- a/src/browser/ui/dom/components/ReactDOMImg.js +++ b/src/browser/ui/dom/components/ReactDOMImg.js @@ -18,11 +18,11 @@ "use strict"; +var EventConstants = require('EventConstants'); +var LocalEventTrapMixin = require('LocalEventTrapMixin'); var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin'); var ReactCompositeComponent = require('ReactCompositeComponent'); var ReactDOM = require('ReactDOM'); -var ReactEventEmitter = require('ReactEventEmitter'); -var EventConstants = require('EventConstants'); // Store a reference to the `ReactDOMComponent`. var img = ReactDOM.img; @@ -37,24 +37,15 @@ var ReactDOMImg = ReactCompositeComponent.createClass({ displayName: 'ReactDOMImg', tagName: 'IMG', - mixins: [ReactBrowserComponentMixin], + mixins: [ReactBrowserComponentMixin, LocalEventTrapMixin], render: function() { return img(this.props); }, componentDidMount: function() { - var node = this.getDOMNode(); - ReactEventEmitter.trapBubbledEvent( - EventConstants.topLevelTypes.topLoad, - 'load', - node - ); - ReactEventEmitter.trapBubbledEvent( - EventConstants.topLevelTypes.topError, - 'error', - node - ); + this.trapBubbledEvent(EventConstants.topLevelTypes.topReset, 'load'); + this.trapBubbledEvent(EventConstants.topLevelTypes.topSubmit, 'error'); } });