Skip to content

Commit

Permalink
Merge pull request react-bootstrap#839 from taion/root-close-replace
Browse files Browse the repository at this point in the history
[fixed] rootClose behavior on replaced elements
  • Loading branch information
mtscout6 committed Jun 15, 2015
2 parents 62181b3 + b67081b commit 3c0badc
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 10 deletions.
8 changes: 6 additions & 2 deletions src/OverlayMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default {

let overlay = this.renderOverlay();

// Save reference to help testing
// Save reference for future access.
if (overlay !== null) {
this._overlayInstance = React.render(overlay, this._overlayTarget);
} else {
Expand All @@ -57,7 +57,11 @@ export default {
}

if (this._overlayInstance) {
return React.findDOMNode(this._overlayInstance);
if (this._overlayInstance.getWrappedDOMNode) {
return this._overlayInstance.getWrappedDOMNode();
} else {
return React.findDOMNode(this._overlayInstance);
}
}

return null;
Expand Down
31 changes: 26 additions & 5 deletions src/RootCloseWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import EventListener from './utils/EventListener';

// TODO: Merge this logic with dropdown logic once #526 is done.

// TODO: Consider using an ES6 symbol here, once we use babel-runtime.
const CLICK_WAS_INSIDE = '__click_was_inside';

function suppressRootClose(event) {
// Tag the native event to prevent the root close logic on document click.
// This seems safer than using event.nativeEvent.stopImmediatePropagation(),
// which is only supported in IE >= 9.
event.nativeEvent[CLICK_WAS_INSIDE] = true;
}

export default class RootCloseWrapper extends React.Component {
constructor(props) {
Expand All @@ -23,10 +32,8 @@ export default class RootCloseWrapper extends React.Component {
}

handleDocumentClick(e) {
// If the click originated from within this component, don't do anything.
// e.srcElement is required for IE8 as e.target is undefined
let target = e.target || e.srcElement;
if (domUtils.contains(React.findDOMNode(this), target)) {
// This is now the native event.
if (e[CLICK_WAS_INSIDE]) {
return;
}

Expand Down Expand Up @@ -54,7 +61,21 @@ export default class RootCloseWrapper extends React.Component {
}

render() {
return React.Children.only(this.props.children);
// Wrap the child in a new element, so the child won't have to handle
// potentially combining multiple onClick listeners.
return (
<div onClick={suppressRootClose}>
{React.Children.only(this.props.children)}
</div>
);
}

getWrappedDOMNode() {
// We can't use a ref to identify the wrapped child, since we might be
// stealing the ref from the owner, but we know exactly the DOM structure
// that will be rendered, so we can just do this to get the child's DOM
// node for doing size calculations in OverlayMixin.
return React.findDOMNode(this).children[0];
}

componentWillUnmount() {
Expand Down
59 changes: 56 additions & 3 deletions test/OverlayTriggerSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,13 +236,66 @@ describe('OverlayTrigger', function() {
});

it('Should have correct isOverlayShown state', function () {
const event = document.createEvent('HTMLEvents');
event.initEvent('click', true, true);
document.documentElement.dispatchEvent(event);
document.documentElement.click();

// Need to click this way for it to propagate to document element.
instance.state.isOverlayShown.should.equal(testCase.shownAfterClick);
});
});
});

describe('replaced overlay', function () {
let instance;

beforeEach(function () {
class ReplacedOverlay extends React.Component {
constructor(props) {
super(props);

this.handleClick = this.handleClick.bind(this);
this.state = {replaced: false};
}

handleClick() {
this.setState({replaced: true});
}

render() {
if (this.state.replaced) {
return (
<div>replaced</div>
);
} else {
return (
<div>
<a id="replace-overlay" onClick={this.handleClick}>
original
</a>
</div>
);
}
}
}

instance = ReactTestUtils.renderIntoDocument(
<OverlayTrigger
overlay={<ReplacedOverlay />}
trigger='click' rootClose={true}
>
<button>button</button>
</OverlayTrigger>
);
const overlayTrigger = React.findDOMNode(instance);
ReactTestUtils.Simulate.click(overlayTrigger);
});

it('Should still be shown', function () {
// Need to click this way for it to propagate to document element.
const replaceOverlay = document.getElementById('replace-overlay');
replaceOverlay.click();

instance.state.isOverlayShown.should.be.true;
});
});
});
});

0 comments on commit 3c0badc

Please sign in to comment.