Skip to content

Commit

Permalink
Compensate for body tag with position absolute, relative or static
Browse files Browse the repository at this point in the history
Also opts for making all placement based on top rather than bottom.
  • Loading branch information
Joel Steres committed Aug 25, 2016
1 parent 147c59c commit 2a3f2e7
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 46 deletions.
48 changes: 24 additions & 24 deletions src/placementcalculator.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,52 +38,52 @@ function PlacementCalculator() {
// calculate the appropriate x and y position in the document
switch (placement) {
case 'n':
coords.set('left', position.left - (tipWidth / 2));
coords.set('bottom', session.windowHeight - position.top + offset);
coords.set('left', position.left - (tipWidth / 2) - session.positionCompensation.left);
coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
break;
case 'e':
coords.set('left', position.left + offset);
coords.set('top', position.top - (tipHeight / 2));
coords.set('left', position.left + offset - session.positionCompensation.left);
coords.set('top', position.top - (tipHeight / 2) - session.positionCompensation.top);
break;
case 's':
coords.set('left', position.left - (tipWidth / 2));
coords.set('top', position.top + offset);
coords.set('left', position.left - (tipWidth / 2) - session.positionCompensation.left);
coords.set('top', position.top + offset - session.positionCompensation.top);
break;
case 'w':
coords.set('top', position.top - (tipHeight / 2));
coords.set('right', session.windowWidth - position.left + offset);
coords.set('top', position.top - (tipHeight / 2) - session.positionCompensation.top);
coords.set('right', session.windowWidth - position.left + offset - session.positionCompensation.right);
break;
case 'nw':
coords.set('bottom', session.windowHeight - position.top + offset);
coords.set('right', session.windowWidth - position.left - 20);
coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
coords.set('right', session.windowWidth - position.left - session.positionCompensation.right - 20);
break;
case 'nw-alt':
coords.set('left', position.left);
coords.set('bottom', session.windowHeight - position.top + offset);
coords.set('left', position.left - session.positionCompensation.left);
coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
break;
case 'ne':
coords.set('left', position.left - 20);
coords.set('bottom', session.windowHeight - position.top + offset);
coords.set('left', position.left - session.positionCompensation.left - 20);
coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
break;
case 'ne-alt':
coords.set('bottom', session.windowHeight - position.top + offset);
coords.set('right', session.windowWidth - position.left);
coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
coords.set('right', session.windowWidth - position.left - session.positionCompensation.right);
break;
case 'sw':
coords.set('top', position.top + offset);
coords.set('right', session.windowWidth - position.left - 20);
coords.set('top', position.top + offset - session.positionCompensation.top);
coords.set('right', session.windowWidth - position.left - session.positionCompensation.right - 20);
break;
case 'sw-alt':
coords.set('left', position.left);
coords.set('top', position.top + offset);
coords.set('left', position.left - session.positionCompensation.left);
coords.set('top', position.top + offset - session.positionCompensation.top);
break;
case 'se':
coords.set('left', position.left - 20);
coords.set('top', position.top + offset);
coords.set('left', position.left - session.positionCompensation.left - 20);
coords.set('top', position.top + offset - session.positionCompensation.top);
break;
case 'se-alt':
coords.set('top', position.top + offset);
coords.set('right', session.windowWidth - position.left);
coords.set('top', position.top + offset - session.positionCompensation.top);
coords.set('right', session.windowWidth - position.left - session.positionCompensation.right);
break;
}

Expand Down
16 changes: 8 additions & 8 deletions src/tooltipcontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,8 @@ function TooltipController(options) {

// support mouse-follow and fixed position tips at the same time by
// moving the tooltip to the last cursor location after it is hidden
coords.set('top', session.currentY + options.offset);
coords.set('left', session.currentX + options.offset);
coords.set('top', session.currentY + options.offset - session.positionCompensation.top);
coords.set('left', session.currentX + options.offset - session.positionCompensation.left);
tipElement.css(coords);

// trigger powerTipClose event
Expand Down Expand Up @@ -231,8 +231,8 @@ function TooltipController(options) {
collisionCount;

// grab collisions
coords.set('top', session.currentY + options.offset);
coords.set('left', session.currentX + options.offset);
coords.set('top', session.currentY + options.offset - session.positionCompensation.top);
coords.set('left', session.currentX + options.offset - session.positionCompensation.left);
collisions = getViewportCollisions(
coords,
tipWidth,
Expand All @@ -246,16 +246,16 @@ function TooltipController(options) {
// if there is only one collision (bottom or right) then
// simply constrain the tooltip to the view port
if (collisions === Collision.right) {
coords.set('left', session.windowWidth - tipWidth);
coords.set('left', session.windowWidth - tipWidth - session.positionCompensation.left);
} else if (collisions === Collision.bottom) {
coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
coords.set('top', session.scrollTop + session.windowHeight - tipHeight - session.positionCompensation.top);
}
} else {
// if the tooltip has more than one collision then it is
// trapped in the corner and should be flipped to get it out
// of the users way
coords.set('left', session.currentX - tipWidth - options.offset);
coords.set('top', session.currentY - tipHeight - options.offset);
coords.set('left', session.currentX - tipWidth - options.offset - session.positionCompensation.left);
coords.set('top', session.currentY - tipHeight - options.offset - session.positionCompensation.top);
}
}

Expand Down
46 changes: 44 additions & 2 deletions src/utility.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ function getViewportDimensions() {
session.scrollTop = $window.scrollTop();
session.windowWidth = $window.width();
session.windowHeight = $window.height();
session.positionCompensation = computePositionCompensation(session.windowWidth, session.windowHeight);
}

/**
Expand All @@ -68,6 +69,7 @@ function getViewportDimensions() {
function trackResize() {
session.windowWidth = $window.width();
session.windowHeight = $window.height();
session.positionCompensation = computePositionCompensation(session.windowWidth, session.windowHeight);
}

/**
Expand Down Expand Up @@ -165,8 +167,10 @@ function getTooltipContent(element) {
* @return {number} Value with the collision flags.
*/
function getViewportCollisions(coords, elementWidth, elementHeight) {
var viewportTop = session.scrollTop,
viewportLeft = session.scrollLeft,
// adjusting viewport even though it might be negative because coords
// comparing with are relative to compensated position
var viewportTop = session.scrollTop - session.positionCompensation.top,
viewportLeft = session.scrollLeft - session.positionCompensation.left,
viewportBottom = viewportTop + session.windowHeight,
viewportRight = viewportLeft + session.windowWidth,
collisions = Collision.none;
Expand Down Expand Up @@ -200,3 +204,41 @@ function countFlags(value) {
}
return count;
}

/**
* Compute compensating position offsets if body element has non-standard position attribute.
* @private
* @param {number} windowWidth Window width in pixels.
* @param {number} windowHeight Window height in pixels.
* @return {Offsets} The top, left, right, bottom offset in pixels
*/
function computePositionCompensation(windowWidth, windowHeight) {
var bodyWidthWithMargin,
bodyHeightWithMargin,
offsets,
bodyPositionPx;

switch ($body.css('position')) {
case 'absolute':
case 'fixed':
case 'relative':
// jquery offset and position functions return top and left
// offset function computes position + margin
offsets = $body.offset();
bodyPositionPx = $body.position();
// because element might be positioned compute right margin using the different between
// outerWidth computations and add position offset
bodyWidthWithMargin = $body.outerWidth(true);
bodyHeightWithMargin = $body.outerHeight(true);
// right offset = right margin + body right position
offsets.right = (bodyWidthWithMargin - $body.outerWidth() - (offsets.left - bodyPositionPx.left)) + (windowWidth - bodyWidthWithMargin - bodyPositionPx.left);
// bottom offset = bottom margin + body bottom position
offsets.bottom = (bodyHeightWithMargin - $body.outerHeight() - offsets.top) + (windowHeight - bodyHeightWithMargin - bodyPositionPx.top);
break;
default:
// even though body may have offset, no compensation is required
offsets = { top: 0, bottom: 0, left: 0, right: 0 };
}

return offsets;
}
78 changes: 78 additions & 0 deletions test/bodyoffset-abs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>PowerTip Test Suite</title>

<!-- Library Resources -->
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>

<!-- PowerTip Core Resources -->
<script type="text/javascript" src="../src/core.js"></script>
<script type="text/javascript" src="../src/csscoordinates.js"></script>
<script type="text/javascript" src="../src/displaycontroller.js"></script>
<script type="text/javascript" src="../src/placementcalculator.js"></script>
<script type="text/javascript" src="../src/tooltipcontroller.js"></script>
<script type="text/javascript" src="../src/utility.js"></script>
<link rel="stylesheet" type="text/css" href="../css/jquery.powertip.css" />

<!-- Unit Test Scripts -->
<script type="text/javascript" src="tests-bodyoffset.js"></script>

<!-- Custom Styles For Test Cases -->
<style type="text/css">
header, section { margin-bottom: 20px; }
section { border: 1px solid #CCC; margin: 20px; padding: 20px; }
#powerTip { white-space: normal; }
#huge-text div, #huge-text-smart div { text-align: center; }
#huge-text input, #huge-text-smart input { margin: 10px; padding: 10px; }
#huge-text .east, #huge-text-smart .east { margin-left: 450px; }
#session { position: fixed; right: 10px; top: 10px; font-size: 10px; width: 160px; background-color: #fff; border: 1px solid #ccc; padding: 10px; overflow: hidden; }
#session pre { margin: 0; }
</style>
</head>
<body style="position:absolute; left:50px; right:100px; top:25px; bottom:75px;">
<section id="huge-text">
<h2>Huge Text</h2>
<p>The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.</p>
<div>
<input type="button" class="north-west-alt" value="North West Alt" />
<input type="button" class="north-west" value="North West" />
<input type="button" class="north" value="North" />
<input type="button" class="north-east" value="North East" />
<input type="button" class="north-east-alt" value="North East Alt" /><br />
<input type="button" class="west" value="West" />
<input type="button" class="east" value="East" /><br />
<input type="button" class="south-west-alt" value="South West Alt" />
<input type="button" class="south-west" value="South West" />
<input type="button" class="south" value="South" />
<input type="button" class="south-east" value="South East" />
<input type="button" class="south-east-alt" value="South East Alt" />
</div>
</section>
<section id="huge-text-smart">
<h2>Huge Text with Smart Placement</h2>
<p>The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.</p>
<div>
<input type="button" class="north-west-alt" value="North West Alt" />
<input type="button" class="north-west" value="North West" />
<input type="button" class="north" value="North" />
<input type="button" class="north-east" value="North East" />
<input type="button" class="north-east-alt" value="North East Alt" /><br />
<input type="button" class="west" value="West" />
<input type="button" class="east" value="East" /><br />
<input type="button" class="south-west-alt" value="South West Alt" />
<input type="button" class="south-west" value="South West" />
<input type="button" class="south" value="South" />
<input type="button" class="south-east" value="South East" />
<input type="button" class="south-east-alt" value="South East Alt" />
</div>
</section>
<section id="trapped-mousefollow" data-powertip="This is the tooltip text.&lt;br /&gt;It is tall so you can test the padding.">
<h2>Trapped mouse following tooltip</h2>
<p>This box has a mouse following tooltip.</p>
<p>Trap it in the bottom right corner of the viewport. It should flip out of the way. It should not flip if it only hits one edge.</p>
</section>
<div id="session"><pre /></div>
</body>
</html>
78 changes: 78 additions & 0 deletions test/bodyoffset-rel.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>PowerTip Test Suite</title>

<!-- Library Resources -->
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>

<!-- PowerTip Core Resources -->
<script type="text/javascript" src="../src/core.js"></script>
<script type="text/javascript" src="../src/csscoordinates.js"></script>
<script type="text/javascript" src="../src/displaycontroller.js"></script>
<script type="text/javascript" src="../src/placementcalculator.js"></script>
<script type="text/javascript" src="../src/tooltipcontroller.js"></script>
<script type="text/javascript" src="../src/utility.js"></script>
<link rel="stylesheet" type="text/css" href="../css/jquery.powertip.css" />

<!-- Unit Test Scripts -->
<script type="text/javascript" src="tests-bodyoffset.js"></script>

<!-- Custom Styles For Test Cases -->
<style type="text/css">
header, section { margin-bottom: 20px; }
section { border: 1px solid #CCC; margin: 20px; padding: 20px; }
#powerTip { white-space: normal; }
#huge-text div, #huge-text-smart div { text-align: center; }
#huge-text input, #huge-text-smart input { margin: 10px; padding: 10px; }
#huge-text .east, #huge-text-smart .east { margin-left: 450px; }
#session { position: fixed; right: 10px; top: 10px; font-size: 10px; width: 160px; background-color: #fff; border: 1px solid #ccc; padding: 10px; overflow: hidden; }
#session pre { margin: 0; }
</style>
</head>
<body style="position:relative">
<section id="huge-text">
<h2>Huge Text</h2>
<p>The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.</p>
<div>
<input type="button" class="north-west-alt" value="North West Alt" />
<input type="button" class="north-west" value="North West" />
<input type="button" class="north" value="North" />
<input type="button" class="north-east" value="North East" />
<input type="button" class="north-east-alt" value="North East Alt" /><br />
<input type="button" class="west" value="West" />
<input type="button" class="east" value="East" /><br />
<input type="button" class="south-west-alt" value="South West Alt" />
<input type="button" class="south-west" value="South West" />
<input type="button" class="south" value="South" />
<input type="button" class="south-east" value="South East" />
<input type="button" class="south-east-alt" value="South East Alt" />
</div>
</section>
<section id="huge-text-smart">
<h2>Huge Text with Smart Placement</h2>
<p>The tooltips for the buttons below have a lot of text. The tooltip div is completely elastic for this demo. The tooltips should be properly placed when they render.</p>
<div>
<input type="button" class="north-west-alt" value="North West Alt" />
<input type="button" class="north-west" value="North West" />
<input type="button" class="north" value="North" />
<input type="button" class="north-east" value="North East" />
<input type="button" class="north-east-alt" value="North East Alt" /><br />
<input type="button" class="west" value="West" />
<input type="button" class="east" value="East" /><br />
<input type="button" class="south-west-alt" value="South West Alt" />
<input type="button" class="south-west" value="South West" />
<input type="button" class="south" value="South" />
<input type="button" class="south-east" value="South East" />
<input type="button" class="south-east-alt" value="South East Alt" />
</div>
</section>
<section id="trapped-mousefollow" data-powertip="This is the tooltip text.&lt;br /&gt;It is tall so you can test the padding.">
<h2>Trapped mouse following tooltip</h2>
<p>This box has a mouse following tooltip.</p>
<p>Trap it in the bottom right corner of the viewport. It should flip out of the way. It should not flip if it only hits one edge.</p>
</section>
<div id="session"><pre /></div>
</body>
</html>
Loading

0 comments on commit 2a3f2e7

Please sign in to comment.