When I use Bootstrap normally, my popovers reference DOM nodes, since that’s the easiest way to supply them with html-formatted content. I spent a while trying to get that to work, but there’s really no need in React.js — when you have full javascript, it seems easy to have the content passed in as a string, which it can be the parent’s responsibility to supply.
So, all we need to do is write a mixin, like this:
// The hasPopover attribute is read by the controller, described below. // It's written into the className string by the ClassNameHelper. var PopoverMixin = { getDefaultProps: function(){ return {hasPopover: true};; }, componentDidMount: function() { $(this.getDOMNode()).popover({ content: this.props.payload, animation: this.props.animation, placement: this.props.placement, title: this.props.title, trigger: 'click', html: true, }); } };
Because Bootstrap supplies a click-to-trigger property, we don’t even need to ask the client to bind an onClick method. The popover behaviour is entirely encapsulated in the mixin, with one exception. We also want to disable the popover, and we want to do that by clicking anywhere. We go one better than Bootstrap here. The default behaviour in Bootstrap requires popovers to be disabled by clicking their container button, which is quite nasty. We can implement this feature by listening for events on the controller class:
var Controller = React.createClass({ // Bind this to onClick managePopover: function(e) { // This enforces one popover open at once. When you click anywhere, // the popover closes; if you click another popover link, that one will // open. if (this.state.activePopover) { this.state.activePopover.popover('hide'); this.setState({activePopover: null}); } if ($(e.target).hasClass('hasPopover')) { this.setState({activePopover: $(e.target)}); } }, getInitialState: function() { // State: answers, currentToken, doc return {activePopover: null}; }, });
The implementation of that feature is very ugly in the old version of my app. I have this bit of setup code:
$(':not(#anything)').on('click', (e) -> $('.popover-link.active').each () -> #the 'is' for buttons that trigger popups #the 'has' for icons and other elements within a button that triggers a popup if (not $(this).is(e.target) and $(this).has(e.target).length is 0 and $('.popover').has(e.target).length is 0) $(this).popover('hide') return # Special-case for having a different target and trigger. Shrug. if not $('#login-button').is(e.target) $('#login-password').popover('hide') $('#login-form').popover('hide') )
That’s pasted verbatim — including the comment! I got it out of some Stackoverflow thread, after searching a long time. I think this was probably the blog post. Check out the Google results for
“Bootstrap close popover”. Clearly I’m not the only one who struggled with this. The event system on React is a nice feature; we get the right thing immediately here.