Using Bootstrap in a client project, I had to fix a bug where a Popover was incorrectly positioned. The detail was that the mix of markup and JavaScript on the page created the popover, set the focus to the triggering field (causing the Popover to appear), but then other JavaScript modified the DOM (adding an alert to the top of the page) that shifted down the triggering field ... and the Popover stayed positioned relative to the fields original position.
I also noticed that resizing the browser window also fails to reposition the Popover when, again, the location of the underlying field has changed.
My brief research on DOM events indicates that support for generic DOM tree modification events is spotty and buggy, but handling the window resize event would be sufficient for many people.
My approach to this is a bit of a hack (and has some Apache Tapestry details mixed in).
/** Adds a new operation, "reposition", to Bootstrap's popover that
* re-positions the tooltip/popover if it is currently visible. This is used to handle
* changes to the DOM and window resizing.
*/
jQuery.fn.popover.Constructor.prototype.reposition = function () {
if (this.enabled && this.tip().hasClass("in")) {
this.show();
}
}
... and in the code that creates the Popover:
matches.popover(options);
function doReposition() {
matches.popover("reposition");
}
jQuery(window).resize(_.throttle(doReposition, 250));
// Ideally, there would just be a way to handle this for anything that modifies the DOM.
T5.sub(T5.events.ADD_ALERT, null, doReposition);
This works acceptibly; there's a slighe fade in/fade out animation here that I'd prefer to avoid; it would be nice if the tooltip show() function could be broken up a little, so that the code that determines position could be invoked directly, without all the other stuff that show() does.
are you triggering the popover manually? Trying to understand how you're resizing a page while also triggering a popover
In this case, the popover is triggered via on "focus", rather than "hover" (it's providing extra help as you tab into form fields). On my Mac, in both FF and Chrome, dragging the window to resize does not seem to cause the field to lose focus. Maybe its different in other OSes.
hm... you might want to just add this:
$(window).on('resize', function () {
$('input:focus').tooltip('show')
})
Alternatively - you could position the element relative to a container which resizes with the input.
To do that when you initialize the popover specify placement like "inside top" - this will put the popover markup within the element it's acting on. You may have to do some fidgeting with styles to get it working, but this is really the best way forward.
My point in adding this issue wasn't that I was stuck, but that one case (window resizing) wasn't handled at all, and could easily be by Bootstrap, and that the other (DOM modifications) doesn't have an easy solution that doesn't involve hacking the Bootstrap JavaScript.
I don't know that "inside top" is a valid approach, in fact "inside" for placement isn't documented or present, to my knowledge, in Bootstrap 2.0.2. Just checked the code ... present but not documented, and wierd ... looks like you can say "top" or "in top" or "inxyz top".
Anyway, you can't nest elements inside an so that's pretty much less than satisfactory.
I'd find it reasonable if there was just a built-in "reposition" option, so that when my code determines that the tooltip or popover may not be positioned correctly, I can ask it to reposition itself; as I said, my workaround does some extra fade-out/fade-in animation I'm not happy with.
So yes, it is easier to close this issue than it is to read it, but that doesn't mean closing it out of hand is appropriate.
I'd position the tooltip off of the .input-wrapper below if i were worried about people resizing. Recalculating the element position in js on resize risks being really slow, and jagged. Also - the tooltip wasn't really designed for that.
It also solves the problem of inserting things in the dom - because the element that the tooltip is positioned relative too - is being effected by the dom manipulation in the same way as the element.
Listening for dom change events, etc. is just something that will never make it into the core of bootstrap.
Wrap your input in a container div, and attach the tooltip to it like shown below.
inside isn't documented yet as it's experimental - for odd cases like this. I want to add some more test cases around it and work with mark to see if we can do some more magic out of the box stuff in css. But honestly, it's your best bet here.
<div class="input-wrapper">
<input type="text">
</div>
I've just come across this problem as well, so here's what I did to solve it:
The problem is that if you have a popover on an input that is triggered by a 'focus' AND the popover is visible AND you resize the window, the popover does not close and does not move relative to the input. In my view the problem is not that the popover does not reposition, the problem is that the popover does not close when the resize event occurs. There are a few ways of solving this problem, like manually closing the popover but in my case I just did this:
$(window).resize(function () {
$(":input:focus").blur();
});
Its not the perfect fix, but its a quick and effective fix.
Another hack/workaround (avoiding CSS transitions during resize):
$(window).resize(function()
{
if ($(".popover").is(":visible"))
{
var popover = $(".popover");
popover.addClass("noTransition");
$("input:focus").popover('show');
popover.removeClass("noTransition");
}
});
CSS:
.noTransition
{
-moz-transition: none !important;
-webkit-transition: none !important;
-o-transition: none !important;
transition: none !important;
}
for anyone who stumbles on to this one, tooltip-append-to-body=false helps, in case it had been set to true.
on window resize event, call mouseout() on the anchor of the popover:
$('#itemWithPopoverAttached').mouseout()
the popover will move, and the tooltip (if present) will not be activated.
Because of the location of the item that displays the tooltip I really needed something that that could recalculate the tooltip's position. I do think that @hlship is absolutely right about this being broken up in the show function. I took the show function and eliminated everything that I thought was not needed for a recalc.
Here is what I have:
$.fn.tooltip.Constructor.prototype.recalculatePosition = function(){
var $tip = this.tip()
if($tip.is(':visible')){
var placement = typeof this.options.placement == 'function' ?
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement
var autoToken = /\s?auto?\s?/i
var autoPlace = autoToken.test(placement)
if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
$tip.addClass(placement)
var pos = this.getPosition()
var actualWidth = $tip[0].offsetWidth
var actualHeight = $tip[0].offsetHeight
if (autoPlace) {
var orgPlacement = placement
var viewportDim = this.getPosition(this.$viewport)
placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
placement
$tip.removeClass(orgPlacement).addClass(placement)
}
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
this.applyPlacement(calculatedOffset, placement)
}
Using this I was able to then call the following anywhere in my code to recalculate the tooltip's position
$('<selector to element with tooltip>').tooltip('recalculatePosition');
With that you can attach it to event listeners, etc. I hope this helps someone. I couldn't use this show function since it triggered the transition animation. This in my mind was a better solution.
As for popovers, I'm sure this could be adapted for it.
Most helpful comment
Because of the location of the item that displays the tooltip I really needed something that that could recalculate the tooltip's position. I do think that @hlship is absolutely right about this being broken up in the
showfunction. I took theshowfunction and eliminated everything that I thought was not needed for a recalc.Here is what I have:
Using this I was able to then call the following anywhere in my code to recalculate the tooltip's position
With that you can attach it to event listeners, etc. I hope this helps someone. I couldn't use this
showfunction since it triggered the transition animation. This in my mind was a better solution.As for popovers, I'm sure this could be adapted for it.