Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tooltip/Popover should reposition itself automatically when window is resized or otherwise #3117

Closed
hlship opened this issue Apr 17, 2012 · 11 comments
Labels

Comments

@hlship
Copy link

hlship commented Apr 17, 2012

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.

@fat
Copy link
Member

fat commented Apr 18, 2012

are you triggering the popover manually? Trying to understand how you're resizing a page while also triggering a popover

@hlship
Copy link
Author

hlship commented Apr 18, 2012

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.

@fat
Copy link
Member

fat commented Apr 19, 2012

hm... you might want to just add this:

$(window).on('resize', function () {
  $('input:focus').tooltip('show')
})

@fat
Copy link
Member

fat commented Apr 19, 2012

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.

@fat fat closed this as completed Apr 19, 2012
@hlship
Copy link
Author

hlship commented Apr 19, 2012

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.

@fat
Copy link
Member

fat commented Apr 19, 2012

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>

@darkoz
Copy link

darkoz commented Dec 19, 2012

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.

@fluidblue
Copy link

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;
}

@seanhelvey
Copy link

seanhelvey commented Jul 14, 2016

for anyone who stumbles on to this one, tooltip-append-to-body=false helps, in case it had been set to true.

@olgn
Copy link

olgn commented Aug 10, 2016

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.

@mcharris
Copy link

mcharris commented Apr 4, 2017

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants