Changing the position of Bootstrap popovers based on the popover's X position in relation to window edge?

JqueryTwitter BootstrapOffsetPopover

Jquery Problem Overview


I have scoured the Internet far and wide and while I found this stackoverflow post insightful <https://stackoverflow.com/questions/8166101/is-it-possible-to-change-the-position-of-bootstrap-popovers-based-on-screen-widt>;, it still didn't answer my question, likely due to my trouble understanding position/offset.

Here's what I'm trying to do. I want the Twitter Bootstap Popover position to be RIGHT unless the hotspot popover will be positioned beyond the viewport, then I want it to be positioned LEFT. I'm making an iPad web app that has hot spots, when you push on a hotspot, information appears but I don't want it to appear outside of the viewport when horizontal scrolling is locked.

I'm thinking it'd go something like this:

$('.infopoint').popover({
		trigger:'hover',
		animation: false,
		placement: wheretoplace	
	});
	function wheretoplace(){
	    var myLeft = $(this).offset.left;

	    if (myLeft<500) return 'left';
	    return 'right';
	}

Thoughts? Thanks!

Jquery Solutions


Solution 1 - Jquery

I just noticed that the placement option could either be a string or a function returning a string that makes the calculation each time you click on a popover-able link.

This makes it real easy to replicate what you did without the initial $.each function:

var options = {
    placement: function (context, source) {
        var position = $(source).position();

        if (position.left > 515) {
            return "left";
        }

        if (position.left < 515) {
            return "right";
        }

        if (position.top < 110){
            return "bottom";
        }

        return "top";
    }
    , trigger: "click"
};
$(".infopoint").popover(options);

Solution 2 - Jquery

For Bootstrap 3, there is

placement: 'auto right'

which is easier. By default, it will be right, but if the element is located in the right side of the screen, the popover will be left. So it should be:

$('.infopoint').popover({
   trigger:'hover',
   animation: false,
   placement: 'auto right'
});

Solution 3 - Jquery

Based on the documentation you should be able to use auto in combination with the preferred placement e.g. auto left

http://getbootstrap.com/javascript/#popovers: "When "auto" is specified, it will dynamically reorient the popover. For example, if placement is "auto left", the tooltip will display to the left when possible, otherwise it will display right."

I was trying to do the same thing and then realised that this functionality already existed.

Solution 4 - Jquery

So I figured out a way to do this effectively. For my particular project I'm building an iPad website so I knew exactly what the pixel X/Y value would be to reach the edge of the screen. Your thisX and thisY values will vary.

Since the popovers are being placed by inline styling anyway, I simply grab the left and top values for each link to decide which direction the popover should be. This is done by appending html5 data- values that .popover() uses upon execution.

if ($('.infopoint').length>0){
	$('.infopoint').each(function(){
		var thisX = $(this).css('left').replace('px','');
		var thisY = $(this).css('top').replace('px','');
		if (thisX > 515)
			$(this).attr('data-placement','left');
		if (thisX < 515)
			$(this).attr('data-placement','right');
		if (thisY > 480)
			$(this).attr('data-placement','top');
		if (thisY < 110)
			$(this).attr('data-placement','bottom');
	});
	$('.infopoint').popover({
		trigger:'hover',
		animation: false,
		html: true
	});
}

Any comments/improvements are welcome but this gets the job done if you're willing to put the values in manually.

Solution 5 - Jquery

bchhun's answer got me on the right track, but I wanted to check for actual space available between the source and the viewport edge. I also wanted to respect the data-placement attribute as a preference with appropriate fallbacks if there wasn't enough space. That way "right" would always go right unless there wasn't enough space for the popover to show on the right side, for example. This was the way I handled it. It works for me, but it feels a bit cumbersome. If anyone has any ideas for a cleaner, more concise solution, I'd be interested to see it.

var options = {
  placement: function (context, source) {
    var $win, $source, winWidth, popoverWidth, popoverHeight, offset, toRight, toLeft, placement, scrollTop;

    $win = $(window);
    $source = $(source);
    placement = $source.attr('data-placement');
    popoverWidth = 400;
    popoverHeight = 110;
    offset = $source.offset();

    // Check for horizontal positioning and try to use it.
    if (placement.match(/^right|left$/)) {
      winWidth = $win.width();
      toRight = winWidth - offset.left - source.offsetWidth;
      toLeft = offset.left;

      if (placement === 'left') {
        if (toLeft > popoverWidth) {
          return 'left';
        }
        else if (toRight > popoverWidth) {
          return 'right';
        }
      }
      else {
        if (toRight > popoverWidth) {
          return 'right';
        }
        else if (toLeft > popoverWidth) {
          return 'left';
        }
      }
    }

    // Handle vertical positioning.
    scrollTop = $win.scrollTop();
    if (placement === 'bottom') {
      if (($win.height() + scrollTop) - (offset.top + source.offsetHeight) > popoverHeight) {
        return 'bottom';
      }
      return 'top';
    }
    else {
      if (offset.top - scrollTop > popoverHeight) {
        return 'top';
      }
      return 'bottom';
    }
  },
  trigger: 'click'
};
$('.infopoint').popover(options);

Solution 6 - Jquery

You can also try this one,

==> Get target/source position in viewport
==> Check whether space from bottom is less that your tooltip height
==> If space from bottom is less that your tooltip height, then just scroll page up

placement: function(context, source){
  //get target/source position in viewport
  var bounds = $(source)[0].getBoundingClientRect();
      winHeight = $(window).height();

  //check wheather space from bottom is less that your tooltip height
  var rBottom = winHeight - bounds.bottom;

  //Consider 180 == your Tooltip height
  //if space from bottom is less that your tooltip height, this will scrolls page up
  //We are keeping tooltip position is fixed(at bottom)

  if (rBottom < 180){
    $('html, body').animate({ scrollTop: $(document).scrollTop()+180 }, 'slow');
  }

  return "bottom";
}

Solution 7 - Jquery

In addition to bchhun's great answer, if you want absoulte positioning, you can do this

var options = {
    placement: function (context, source) {
         setTimeout(function () {
		       $(context).css('top',(source.getBoundingClientRect().top+ 500) + 'px')
		 },0)
		 return "top";
    },
    trigger: "click"
};
$(".infopoint").popover(options);

Solution 8 - Jquery

I solved my problem in AngularJS as follows:

var configPopOver = {
		animation: 500,
		container: 'body',
		placement: function (context, source) {
			        var elBounding = source.getBoundingClientRect();
			        var pageWidth = angular.element('body')[0].clientWidth
			        var pageHeith = angular.element('body')[0].clientHeith
			        
			        if (elBounding.left > (pageWidth*0.34) && elBounding.width < (pageWidth*0.67)) {
			            return "left";
			        }
	
			        if (elBounding.left < (pageWidth*0.34) && elBounding.width < (pageWidth*0.67)) {
			            return "right";
			        }
	
			        if (elBounding.top < 110){
			            return "bottom";
			        }
	
			        return "top";
	    		},
		html: true
	};

This function do the position of Bootstrap popover float to the best position, based on element position.

Solution 9 - Jquery

$scope.popoverPostion = function(event){
	$scope.pos = '';
	
	   if(event.x <= 207 && event.x >= 140){
		   $scope.pos = 'top';
		   event.x = '';
	   }
	   else if(event.x < 140){
		   $scope.pos = 'top-left';
		   event.x = '';
	   }
	   else if(event.x > 207){
		   $scope.pos = 'top-right';
		   event.x = '';
	   }
	
};

Solution 10 - Jquery

You can use auto in data placement like data-placement="auto left". It will automatic adjust according to your screen size and default placement will be left.

Solution 11 - Jquery

You can also try this one if u need it in almost all places in the page.

U can configure it in a general way then just use it.

In this way u can also use HTML element and anything u want :)

$(document).ready(function()
                  {
  var options =
      {
        placement: function (context, source)
        {
          var position = $(source).position();
          var content_width = 515;  //Can be found from a JS function for more dynamic output
          var content_height = 110;  //Can be found from a JS function for more dynamic output

          if (position.left > content_width)
          {
            return "left";
          }

          if (position.left < content_width)
          {
            return "right";
          }

          if (position.top < content_height)
          {
            return "bottom";
          }

          return "top";
        }
        , trigger: "hover"
        , animation: "true"
        , html:"true"
      };
  $('[data-toggle="popover"]').popover(options);
});

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"/>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>


<div class="container">
	<h3>Popover Example</h3>
	<a id="try_ppover" href="#" data-toggle="popover" title="Popover HTML Header" data-content="<div class='jumbotron'>Some HTML content inside the popover</div>">Toggle popover</a>
</div>

Addition


> For setting more options, u can go here.

> Even more can be found here.

Update


If u want the popup after click, u can change the JS option to trigger: "click" like this-

		return ..;
	}
    ......
	, trigger: "click"
	......
};

U also can customize it in HTML ading data-trigger="click" like this-

<a id="try_ppover" href="#" data-toggle="popover" data-trigger="click" title="Popover Header" data-content="<div class='jumbotron'>Some content inside the popover</div>">Toggle popover</a>

I think it will be more oriented code and more re-usable and more helpful to all :).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionj0eView Question on Stackoverflow
Solution 1 - JquerybchhunView Answer on Stackoverflow
Solution 2 - JqueryHans TionoView Answer on Stackoverflow
Solution 3 - JqueryHamish RouseView Answer on Stackoverflow
Solution 4 - Jqueryj0eView Answer on Stackoverflow
Solution 5 - JquerychasingmaxwellView Answer on Stackoverflow
Solution 6 - JqueryMohan DereView Answer on Stackoverflow
Solution 7 - Jqueryapple16View Answer on Stackoverflow
Solution 8 - JqueryGustavo BenícioView Answer on Stackoverflow
Solution 9 - JquerySuvraView Answer on Stackoverflow
Solution 10 - JqueryAbhishek AnandView Answer on Stackoverflow
Solution 11 - JqueryAbrar JahinView Answer on Stackoverflow