Search code examples
javascriptjquerytwitter-bootstraptooltiptwitter-bootstrap-tooltip

Workaround for Bootstrap tooltip in multiple-line links


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?


Solution

  • 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