Merge pull request #4033 from spicyj/dc-events

Convert form, iframe, img to not use wrappers
This commit is contained in:
Ben Alpert
2015-06-04 17:23:48 -07:00
7 changed files with 71 additions and 237 deletions
@@ -1,54 +0,0 @@
/**
* Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule LocalEventTrapMixin
*/
'use strict';
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
var accumulateInto = require('accumulateInto');
var findDOMNode = require('findDOMNode');
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');
// If a component renders to null or if another component fatals and causes
// the state of the tree to be corrupted, `node` here can be null.
var node = findDOMNode(this);
invariant(
node,
'LocalEventTrapMixin.trapBubbledEvent(...): Requires node to be rendered.'
);
var listener = ReactBrowserEventEmitter.trapBubbledEvent(
topLevelType,
handlerBaseName,
node
);
this._localEventListeners =
accumulateInto(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;
@@ -1,44 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactDOMForm
*/
'use strict';
var EventConstants = require('EventConstants');
var LocalEventTrapMixin = require('LocalEventTrapMixin');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactClass = require('ReactClass');
var ReactElement = require('ReactElement');
var form = ReactElement.createFactory('form');
/**
* Since onSubmit doesn't bubble OR capture on the top level in IE8, we need
* to capture it on the <form> element itself. There are lots of hacks we could
* do to accomplish this, but the most reliable is to make <form> a
* composite component and use `componentDidMount` to attach the event handlers.
*/
var ReactDOMForm = ReactClass.createClass({
displayName: 'ReactDOMForm',
tagName: 'FORM',
mixins: [ReactBrowserComponentMixin, LocalEventTrapMixin],
render: function() {
return form(this.props);
},
componentDidMount: function() {
this.trapBubbledEvent(EventConstants.topLevelTypes.topReset, 'reset');
this.trapBubbledEvent(EventConstants.topLevelTypes.topSubmit, 'submit');
},
});
module.exports = ReactDOMForm;
@@ -1,43 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactDOMIframe
*/
'use strict';
var EventConstants = require('EventConstants');
var LocalEventTrapMixin = require('LocalEventTrapMixin');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactClass = require('ReactClass');
var ReactElement = require('ReactElement');
var iframe = ReactElement.createFactory('iframe');
/**
* Since onLoad doesn't bubble OR capture on the top level in IE8, we need to
* capture it on the <iframe> element itself. There are lots of hacks we could
* do to accomplish this, but the most reliable is to make <iframe> a composite
* component and use `componentDidMount` to attach the event handlers.
*/
var ReactDOMIframe = ReactClass.createClass({
displayName: 'ReactDOMIframe',
tagName: 'IFRAME',
mixins: [ReactBrowserComponentMixin, LocalEventTrapMixin],
render: function() {
return iframe(this.props);
},
componentDidMount: function() {
this.trapBubbledEvent(EventConstants.topLevelTypes.topLoad, 'load');
},
});
module.exports = ReactDOMIframe;
@@ -1,44 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactDOMImg
*/
'use strict';
var EventConstants = require('EventConstants');
var LocalEventTrapMixin = require('LocalEventTrapMixin');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactClass = require('ReactClass');
var ReactElement = require('ReactElement');
var img = ReactElement.createFactory('img');
/**
* Since onLoad doesn't bubble OR capture on the top level in IE8, we need to
* capture it on the <img> element itself. There are lots of hacks we could do
* to accomplish this, but the most reliable is to make <img> a composite
* component and use `componentDidMount` to attach the event handlers.
*/
var ReactDOMImg = ReactClass.createClass({
displayName: 'ReactDOMImg',
tagName: 'IMG',
mixins: [ReactBrowserComponentMixin, LocalEventTrapMixin],
render: function() {
return img(this.props);
},
componentDidMount: function() {
this.trapBubbledEvent(EventConstants.topLevelTypes.topLoad, 'load');
this.trapBubbledEvent(EventConstants.topLevelTypes.topError, 'error');
},
});
module.exports = ReactDOMImg;
@@ -1,46 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/
'use strict';
describe('LocalEventTrapMixin', function() {
var EventConstants;
var LocalEventTrapMixin;
var React;
var ReactTestUtils;
beforeEach(function() {
EventConstants = require('EventConstants');
LocalEventTrapMixin = require('LocalEventTrapMixin');
React = require('React');
ReactTestUtils = require('ReactTestUtils');
});
it('throws when trapping bubbled state on null', function() {
var BadImage = React.createClass({
mixins: [LocalEventTrapMixin],
render: function() {
return null;
},
componentDidMount: function() {
this.trapBubbledEvent(EventConstants.topLevelTypes.topLoad, 'load');
},
});
expect(function() {
ReactTestUtils.renderIntoDocument(<BadImage />);
}).toThrow(
'Invariant Violation: ' +
'LocalEventTrapMixin.trapBubbledEvent(...): ' +
'Requires node to be rendered.'
);
});
});
@@ -18,6 +18,7 @@ var AutoFocusUtils = require('AutoFocusUtils');
var CSSPropertyOperations = require('CSSPropertyOperations');
var DOMProperty = require('DOMProperty');
var DOMPropertyOperations = require('DOMPropertyOperations');
var EventConstants = require('EventConstants');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
var ReactComponentBrowserEnvironment =
require('ReactComponentBrowserEnvironment');
@@ -176,6 +177,58 @@ function putListener() {
);
}
function trapBubbledEventsLocal() {
var inst = this;
// If a component renders to null or if another component fatals and causes
// the state of the tree to be corrupted, `node` here can be null.
invariant(inst._rootNodeID, 'Must be mounted to trap events');
var node = ReactMount.getNode(inst._rootNodeID);
invariant(
node,
'trapBubbledEvent(...): Requires node to be rendered.'
);
switch (inst._tag) {
case 'iframe':
inst._wrapperState.listeners = [
ReactBrowserEventEmitter.trapBubbledEvent(
EventConstants.topLevelTypes.topLoad,
'load',
node
),
];
break;
case 'img':
inst._wrapperState.listeners = [
ReactBrowserEventEmitter.trapBubbledEvent(
EventConstants.topLevelTypes.topError,
'error',
node
),
ReactBrowserEventEmitter.trapBubbledEvent(
EventConstants.topLevelTypes.topLoad,
'load',
node
),
];
break;
case 'form':
inst._wrapperState.listeners = [
ReactBrowserEventEmitter.trapBubbledEvent(
EventConstants.topLevelTypes.topReset,
'reset',
node
),
ReactBrowserEventEmitter.trapBubbledEvent(
EventConstants.topLevelTypes.topSubmit,
'submit',
node
),
];
break;
}
}
// For HTML, certain tags should omit their close tag. We keep a whitelist for
// those special cased tags.
@@ -285,6 +338,14 @@ ReactDOMComponent.Mixin = {
var props = this._currentElement.props;
switch (this._tag) {
case 'iframe':
case 'img':
case 'form':
this._wrapperState = {
listeners: null,
};
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
case 'button':
props = ReactDOMButton.getNativeProps(this, props, context);
break;
@@ -679,6 +740,16 @@ ReactDOMComponent.Mixin = {
*/
unmountComponent: function() {
switch (this._tag) {
case 'iframe':
case 'img':
case 'form':
var listeners = this._wrapperState.listeners;
if (listeners) {
for (var i = 0; i < listeners.length; i++) {
listeners[i].remove();
}
}
break;
case 'input':
ReactDOMInput.unmountWrapper(this);
break;
@@ -24,10 +24,7 @@ var ReactComponentBrowserEnvironment =
require('ReactComponentBrowserEnvironment');
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
var ReactDOMComponent = require('ReactDOMComponent');
var ReactDOMForm = require('ReactDOMForm');
var ReactDOMImg = require('ReactDOMImg');
var ReactDOMIDOperations = require('ReactDOMIDOperations');
var ReactDOMIframe = require('ReactDOMIframe');
var ReactDOMOption = require('ReactDOMOption');
var ReactDOMSelect = require('ReactDOMSelect');
var ReactDOMTextComponent = require('ReactDOMTextComponent');
@@ -109,9 +106,6 @@ function inject() {
ReactInjection.Class.injectMixin(ReactBrowserComponentMixin);
ReactInjection.NativeComponent.injectComponentClasses({
'form': ReactDOMForm,
'iframe': ReactDOMIframe,
'img': ReactDOMImg,
'option': ReactDOMOption,
'select': ReactDOMSelect,
});