Popover mixin

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.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s