Wrangling the Twitter Bootstrap popover plugin in Meteor JS
We ran into some interesting problems with Bootstrap's built-in popover plugin, implemented in the context of Meteor.
Our original implementation was something like this:
Template.templateName.rendered = function() { $('.some-class').popover({ // options }); $('.some-class').click(function() { // Do more things }); }
There are two major problems with this:
In this case, the jQuery selector is far too broad. This was actually leading to performance problems. Every popover would be initialized everywhere it might appear. That wasn't necessary for our use case.
This particular plugin doesn't seem to behave quite right if you initialize it more than once. Template.templateName.rendered is called every time a reactive computation triggers a re-render of the template.
Still, it's not always wrong to initialize your plugins inside [code].rendered[/code]. Just make sure you include some logic to make sure it only gets set once.
However, we wound up working around the whole thing using an appropriate [code]click[/code] handler in [code]Template.templateName.events[/code]. We had to do some workarounds to make the popover plugin work the same (often manually calling its functions), but the solution performed much better and more predictably. For reference (in case you're having this exact problem), our solution more or less looks like:
Template.templateName.events({ 'click .bar.point-bar': function (event) { var self = this; event.preventDefault(); event.stopPropagation(); togglePopover($(event.target), '.card'); } });
this.togglePopover = function ($element, parentSelector, elementSelector, popoverSelector, rowSelector) { elementSelector = elementSelector || '.bar.point-bar'; popoverSelector = popoverSelector || '.progress .popover'; rowSelector = rowSelector || '.resource-bar-chart'; var popupSelector = parentSelector + ' ' + popoverSelector; // We're smart here. We only call destroy on parents of existing popups. Way faster than destroying everything everywhere that isn't the current element's popup. $(popoverSelector).parents(rowSelector).find(elementSelector).not($element).popover('destroy'); var $findPopup = $element.parents(rowSelector).find(popoverSelector); var isVisible = $findPopup.length > 0; if (isVisible) { // Hide the popup, and stop. $element.popover('destroy'); return; } $element.popover({ animation: true, trigger: 'none', html: true }).popover('show'); // Don't close the popup if clicking the input box. $(popupSelector + ' input').click(function(event) { event.preventDefault(); event.stopPropagation(); }); $(popupSelector).click(function() { $element.popover('destroy'); }); };
There are some assumptions here, and some things targeted specifically towards our application. But if you study the code and read the comments, you should be able to see what we did.
Happy rendering!