2

I know that in the official Bootstrap page it says that white-space: nowrap; should be added in multiple-line links with tooltips.

However, I am developing a Chrome extension and I want to add tooltips on any Web page, without changing its original layout. So, the proposed solution is not ideal for me.

I tried to manually set the placement of the tooltip. When this happens, it would return the leftmost position. So, I tried this solution, which returns the correct position (the beggining of the link, instead of the leftmost position).

The problem is that, either I set the placement left or right, it never positions as I want. If set to right, it shows outside the window, as it places starting on the rightmost position. If set to left, it also shows outside the window, as it places on the leftmost position.

See Fiddle

In this case, I would ideally want it to be placed left to the starting point of the link (in the first line).

Is there a workaround to make this happen?

Community
  • 1
  • 1
Hugo Sousa
  • 1,904
  • 2
  • 15
  • 28

2 Answers2

1

You can either use mouse position as tooltip anchor or you can modify the tooltip javascript code to achieve this as in How to make popover appear where my mouse enters the hover target?. You can merge the bottom code with the one in the link to achieve both and choose the one you like.

In bootstrap-tooltip.js, replace (line 1602 I think) (Sorry for the poor format)

 Tooltip.prototype.getPosition 
 and
 Tooltip.prototype.getCalculatedOffset

functions with respectively

Tooltip.prototype.getPosition = function($element) {
  $element = $element || this.$element

  var el = $element[0]
  var isBody = el.tagName == 'BODY'

  var elRect = el.getBoundingClientRect()
  if (elRect.width == null) {
    // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
    elRect = $.extend({}, elRect, {
      width: elRect.right - elRect.left,
      height: elRect.bottom - elRect.top
    })
  }

  var rects = el.getClientRects();
  var firstRect = rects[0];
  var lastRect = rects[rects.length - 1];

  firstRect = $.extend({}, firstRect, {
    width: firstRect.right - firstRect.left,
    height: firstRect.bottom - firstRect.top
  })
  lastRect = $.extend({}, lastRect, {
    width: lastRect.right - lastRect.left,
    height: lastRect.bottom - lastRect.top
  })



  var elOffset = isBody ? {
    top: 0,
    left: 0
  } : $element.offset()
  var elScrollTop = elOffset.top - elRect.top;
  var elScrollLeft = elOffset.left - elRect.left;
  firstRect.top += elScrollTop;
  lastRect.top += elScrollTop;
  firstRect.left += elScrollLeft;
  lastRect.left += elScrollLeft;

  firstRect = {
    firstRect: firstRect
  };
  lastRect = {
    lastRect: lastRect
  };

  var scroll = {
    scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop()
  }
  var outerDims = isBody ? {
    width: $(window).width(),
    height: $(window).height()
  } : null

  return $.extend({}, elRect, scroll, outerDims, elOffset, firstRect, lastRect)
}

Tooltip.prototype.getCalculatedOffset = function(placement, pos, actualWidth, actualHeight) {
  return placement == 'bottom' ? {
      top: pos.top + pos.height,
      left: pos.left + pos.width / 2 - actualWidth / 2
    } :
    placement == 'top' ? {
      top: pos.top - actualHeight,
      left: pos.left + pos.width / 2 - actualWidth / 2
    } :
    placement == 'left' ? {
      top: pos.top + pos.height / 2 - actualHeight / 2,
      left: pos.left - actualWidth
    } :
    placement == 'textleft' ? {
      top: pos.firstRect.top + (pos.firstRect.height / 2) - actualHeight / 2,
      left: pos.firstRect.left - actualWidth
    } :
    placement == 'textright' ? {
      top: pos.lastRect.top + (pos.lastRect.height / 2) - actualHeight / 2,
      left: pos.lastRect.right
    } :
    /* placement == 'right' */
    {
      top: pos.top + pos.height / 2 - actualHeight / 2,
      left: pos.left + pos.width
    }
}

Now you can use placements "textleft" and "textright". This works for Bootstrap v3.3.6. It can be improved by not directly editing the bootstrap source but extending it (and it is somewhat dirty).

Also you need to add this to css for tooltip arrows.

.tooltip.textright {
  padding: 0 5px;
  margin-left: 3px;
}
.tooltip.textleft {
  padding: 0 5px;
  margin-left: -3px;
}

.tooltip.textleft .tooltip-arrow{
  top: 50%;
  right: 0;
  margin-top: -5px;
  border-width: 5px 0 5px 5px;
  border-left-color: #000;
}

.tooltip.textright .tooltip-arrow{
  top: 50%;
  left: 0;
  margin-top: -5px;
  border-width: 5px 5px 5px 0;
  border-right-color: #000;
}

Here is the fiddle: JSFiddle

Community
  • 1
  • 1
Gokhan Kurt
  • 8,239
  • 1
  • 27
  • 51
  • Thanks for pointing out a direction to follow at least. I've been trying this (in Bootstrap v3.3.6 though), but I guess this is not the solution yet. This only changes where the tooltip initial position is (top, left), but doesn't change wether the tooltip placement is `left` or `right`. So, the tooltip keeps trying to go to the right, going out of the window. The mouse idea is also good, but as I have a delay in the tooltip show, it looks bad, as the position is the one when you first hover the element. – Hugo Sousa Apr 19 '16 at 14:23
  • Seems like Bootstrap v3.3.6 and v2.x.x had huge differences in the source code. I have added the code for latest version. – Gokhan Kurt Apr 20 '16 at 08:21
  • Nice ! I forgot the CSS part when trying yesterday, and your changes look more organized than mine too. I'm still working on it in order to place `textleft` or `textright` depending if the user hovers the upper line or the bottom line, as I said in the question, so it looks much better. Should I edit your answer? – Hugo Sousa Apr 20 '16 at 09:25
  • It should look much better as you said. Feel free to make changes. – Gokhan Kurt Apr 20 '16 at 09:34
  • I think there is a problem with the `getClientRects`. If you resize the window (in JSFiddle you need to put it really small, as the ouput window is small too), the `textright` and `textleft` tooltips show in wrong positions, depending on how much you scroll down/up. Can you think of a fix for that? – Hugo Sousa Apr 20 '16 at 09:48
  • Figured some bug like this would occur. I fixed it. It happened because normally jquery's offset function is used to get scroll offset but there is no getClientRects alternative in jquery. – Gokhan Kurt Apr 20 '16 at 11:20
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/109674/discussion-between-hugo-sousa-and-gokhan-kurt). – Hugo Sousa Apr 20 '16 at 12:34
0

This is an improvement of @Gökhan Kurt answer.

His solution places the tooltip to the left or right of the multiline link. However, I wanted to improve this and make it look better. So, when you hover the upper line, the tooltip appears on the left side of the link; and when you hover the bottom line, the tooltip appears on the right side of the link.

For that, I detect where the user hovers the link. If it it's near the right side of the window, it means it's the upper line. Otherwise, it's the bottom line. This works if you set the container: 'body' in the tooltip options.

For Bootstrap v3.3.6, tooltip plugin source code:

Get the mouse position in the enter function:

if (obj instanceof $.Event) {
  self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
  var mousePos = { x: -1, y: -1 };
  mousePos.x = obj.pageX;
  mousePos.y = obj.pageY;
  window.mousePos = mousePos;
}

In the show function clear the placement CSS class (because it may be different each time you hover the link) and set the new placement depending on the mouse position:

var isTextPlacement = /textleft|textright/.test(placement)
if(isTextPlacement){
    $tip.removeClass("textright");
    $tip.removeClass("textleft");
}

//390 is hardcoded value which is the max size of the tooltip, you may adjust to your case
if(placement == 'textleft' && window.mousePos.x < 390){
  placement = 'textright';
}else if(placement == 'textright' && window.mousePos.x > $(window).width() - 390){
  placement = 'textleft';
}

For the getCalculatedOffset, I used the following, which applies textleft if textright can't be applied.

  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
    var windowWidth = $(window).width();

    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :
           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
           placement=='left'?{ top: pos.top+pos.height/2-actualHeight/2, left: pos.left-actualWidth }:
           placement == 'right' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } :
           placement=='textleft' && window.mousePos.x > 390 ? { top: pos.firstRect.top+(pos.firstRect.height/2)-actualHeight/2, left: pos.firstRect.left-actualWidth }:
           /*placement=='textright'*/ window.mousePos.x < windowWidth - 390 ? { top: pos.lastRect.top+(pos.lastRect.height/2)-actualHeight/2, left: pos.lastRect.right }:
           /*apply textleft*/         { top: pos.firstRect.top+(pos.firstRect.height/2)-actualHeight/2, left: pos.firstRect.left-actualWidth }
  }

And that's it. Check the JSFiddle (Resize the output window in order to make the links span over two lines and compare with @Gökhan Kurt answer. You might need to set it bigger, because of the hardcoded 390px).

Hugo Sousa
  • 1,904
  • 2
  • 15
  • 28