Skip to content

Commit

Permalink
[added] Portal component; replaces OverlayMixin
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense committed Jun 30, 2015
1 parent 1a4c5ec commit f799110
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 13 deletions.
17 changes: 17 additions & 0 deletions docs/src/ComponentsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -788,8 +788,24 @@ const ComponentsPage = React.createClass({
<PropTable component='InputBase'/>
</div>

{/* Utilities */}
<div className='bs-docs-section'>
<h1 id='utilities' className='page-header'>Utilities <small>Portal</small></h1>

<h2 id='utilities-Portal'>Portal</h2>
<p>
A Component that renders its children into a new React "subtree" or <code>container</code>. The Portal component kind of like the React
equivillent to jQuery's <code>.appendTo()</code>, which is helpful for components that need to be appended to a DOM node other than
the component's direct parent. The Modal, and Overlay components use the Portal component internally.
</p>
<h3 id='utilities-props'>Props</h3>

<PropTable component='Portal'/>
</div>
</div>



<div className='col-md-3'>
<Affix
className='bs-docs-sidebar hidden-print'
Expand Down Expand Up @@ -829,6 +845,7 @@ const ComponentsPage = React.createClass({
<NavItem href='#glyphicons' key={24}>Glyphicons</NavItem>
<NavItem href='#tables' key={25}>Tables</NavItem>
<NavItem href='#input' key={26}>Input</NavItem>
<NavItem href='#utilities' key={26}>Input</NavItem>
</Nav>
<a className='back-to-top' href='#top'>
Back to top
Expand Down
48 changes: 35 additions & 13 deletions src/OverlayMixin.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
import React from 'react';
import CustomPropTypes from './utils/CustomPropTypes';
import domUtils from './utils/domUtils';
import deprecationWarning from './utils/deprecationWarning';

export default {
export const OverlayMixin = {
propTypes: {

container: CustomPropTypes.mountable
},

componentWillUnmount() {
this._unrenderOverlay();
if (this._overlayTarget) {
this.getContainerDOMNode()
.removeChild(this._overlayTarget);
this._overlayTarget = null;
}

componentDidMount() {
this._renderOverlay();
},

componentDidUpdate() {
this._renderOverlay();
},

componentDidMount() {
this._renderOverlay();
componentWillUnmount() {
this._unrenderOverlay();
this._mountOverlayTarget();
},

_mountOverlayTarget() {
this._overlayTarget = document.createElement('div');
this.getContainerDOMNode()
.appendChild(this._overlayTarget);
if (!this._overlayTarget) {
this._overlayTarget = document.createElement('div');
this.getContainerDOMNode()
.appendChild(this._overlayTarget);
}
},

_unmountOverlayTarget() {
if (this._overlayTarget) {
this.getContainerDOMNode()
.removeChild(this._overlayTarget);
this._overlayTarget = null;
}
},

_renderOverlay() {
Expand All @@ -39,10 +48,12 @@ export default {

// Save reference to help testing
if (overlay !== null) {
this._mountOverlayTarget();
this._overlayInstance = React.render(overlay, this._overlayTarget);
} else {
// Unrender if the component is null for transitions to null
this._unrenderOverlay();
this._unmountOverlayTarget();
}
},

Expand All @@ -67,3 +78,14 @@ export default {
return React.findDOMNode(this.props.container) || domUtils.ownerDocument(this).body;
}
};

export default {

...OverlayMixin,

componentWillMount() {
deprecationWarning(
'Overlay mixin', 'the `<Portal/>` Component'
, 'http://react-bootstrap.github.io/components.html#modals-custom');
}
};
34 changes: 34 additions & 0 deletions src/Portal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import CustomPropTypes from './utils/CustomPropTypes';
import { OverlayMixin } from './OverlayMixin';

let Portal = React.createClass({

displayName: 'Portal',

propTypes: {
/**
* The DOM Node that the Component will render it's children into
*/
container: CustomPropTypes.mountable
},

// we use the mixin for now, to avoid duplicating a bunch of code.
// when the deprecation is removed we need to move the logic here from OverlayMixin
mixins: [ OverlayMixin ],

renderOverlay() {
if (!this.props.children) {
return null;
}

return React.Children.only(this.props.children);
},

render() {
return null;
}
});


export default Portal;
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ import utils from './utils';
import Well from './Well';
import styleMaps from './styleMaps';

import Portal from './Portal';

export default {
Accordion,
Affix,
Expand Down Expand Up @@ -98,6 +100,7 @@ export default {
Pager,
Pagination,
Popover,
Portal,
ProgressBar,
Row,
SplitButton,
Expand Down
78 changes: 78 additions & 0 deletions test/PortalSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import Portal from '../src/Portal';

describe('Portal', function () {
let instance;

let Overlay = React.createClass({
render() {
return (
<div>
<Portal ref='p' {...this.props}>{this.props.overlay}</Portal>
</div>
);
},
getOverlayDOMNode(){
return this.refs.p.getOverlayDOMNode();
}
});

afterEach(function() {
if (instance && ReactTestUtils.isCompositeComponent(instance) && instance.isMounted()) {
React.unmountComponentAtNode(React.findDOMNode(instance));
}
});

it('Should render overlay into container (DOMNode)', function() {
let container = document.createElement('div');

instance = ReactTestUtils.renderIntoDocument(
<Overlay container={container} overlay={<div id="test1" />} />
);

assert.equal(container.querySelectorAll('#test1').length, 1);
});

it('Should render overlay into container (ReactComponent)', function() {
let Container = React.createClass({
render() {
return <Overlay container={this} overlay={<div id="test1" />} />;
}
});

instance = ReactTestUtils.renderIntoDocument(
<Container />
);

assert.equal(React.findDOMNode(instance).querySelectorAll('#test1').length, 1);
});

it('Should not render a null overlay', function() {
let Container = React.createClass({
render() {
return <Overlay ref='overlay' container={this} overlay={null} />;
}
});

instance = ReactTestUtils.renderIntoDocument(
<Container />
);

assert.equal(instance.refs.overlay.getOverlayDOMNode(), null);
});

it('Should render only an overlay', function() {
let OnlyOverlay = React.createClass({
render() {
return <Portal ref='p' {...this.props}>{this.props.overlay}</Portal>;
}
});

let overlayInstance = ReactTestUtils.renderIntoDocument(
<OnlyOverlay overlay={<div id="test1" />} />
);

assert.equal(overlayInstance.refs.p.getOverlayDOMNode().nodeName, 'DIV');
});
});

0 comments on commit f799110

Please sign in to comment.