Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit a1184f4

Browse files
author
Joel Steres
committed
Compensate for body tag with position absolute, relative or static
Also opts for making all placement based on top rather than bottom.
1 parent f62f25d commit a1184f4

7 files changed

+303
-46
lines changed

src/placementcalculator.js

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -38,52 +38,52 @@ function PlacementCalculator() {
3838
// calculate the appropriate x and y position in the document
3939
switch (placement) {
4040
case 'n':
41-
coords.set('left', position.left - (tipWidth / 2));
42-
coords.set('bottom', session.windowHeight - position.top + offset);
41+
coords.set('left', position.left - (tipWidth / 2) - session.positionCompensation.left);
42+
coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
4343
break;
4444
case 'e':
45-
coords.set('left', position.left + offset);
46-
coords.set('top', position.top - (tipHeight / 2));
45+
coords.set('left', position.left + offset - session.positionCompensation.left);
46+
coords.set('top', position.top - (tipHeight / 2) - session.positionCompensation.top);
4747
break;
4848
case 's':
49-
coords.set('left', position.left - (tipWidth / 2));
50-
coords.set('top', position.top + offset);
49+
coords.set('left', position.left - (tipWidth / 2) - session.positionCompensation.left);
50+
coords.set('top', position.top + offset - session.positionCompensation.top);
5151
break;
5252
case 'w':
53-
coords.set('top', position.top - (tipHeight / 2));
54-
coords.set('right', session.windowWidth - position.left + offset);
53+
coords.set('top', position.top - (tipHeight / 2) - session.positionCompensation.top);
54+
coords.set('right', session.windowWidth - position.left + offset - session.positionCompensation.right);
5555
break;
5656
case 'nw':
57-
coords.set('bottom', session.windowHeight - position.top + offset);
58-
coords.set('right', session.windowWidth - position.left - 20);
57+
coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
58+
coords.set('right', session.windowWidth - position.left - session.positionCompensation.right - 20);
5959
break;
6060
case 'nw-alt':
61-
coords.set('left', position.left);
62-
coords.set('bottom', session.windowHeight - position.top + offset);
61+
coords.set('left', position.left - session.positionCompensation.left);
62+
coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
6363
break;
6464
case 'ne':
65-
coords.set('left', position.left - 20);
66-
coords.set('bottom', session.windowHeight - position.top + offset);
65+
coords.set('left', position.left - session.positionCompensation.left - 20);
66+
coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
6767
break;
6868
case 'ne-alt':
69-
coords.set('bottom', session.windowHeight - position.top + offset);
70-
coords.set('right', session.windowWidth - position.left);
69+
coords.set('top', position.top - tipHeight - offset - session.positionCompensation.top);
70+
coords.set('right', session.windowWidth - position.left - session.positionCompensation.right);
7171
break;
7272
case 'sw':
73-
coords.set('top', position.top + offset);
74-
coords.set('right', session.windowWidth - position.left - 20);
73+
coords.set('top', position.top + offset - session.positionCompensation.top);
74+
coords.set('right', session.windowWidth - position.left - session.positionCompensation.right - 20);
7575
break;
7676
case 'sw-alt':
77-
coords.set('left', position.left);
78-
coords.set('top', position.top + offset);
77+
coords.set('left', position.left - session.positionCompensation.left);
78+
coords.set('top', position.top + offset - session.positionCompensation.top);
7979
break;
8080
case 'se':
81-
coords.set('left', position.left - 20);
82-
coords.set('top', position.top + offset);
81+
coords.set('left', position.left - session.positionCompensation.left - 20);
82+
coords.set('top', position.top + offset - session.positionCompensation.top);
8383
break;
8484
case 'se-alt':
85-
coords.set('top', position.top + offset);
86-
coords.set('right', session.windowWidth - position.left);
85+
coords.set('top', position.top + offset - session.positionCompensation.top);
86+
coords.set('right', session.windowWidth - position.left - session.positionCompensation.right);
8787
break;
8888
}
8989

src/tooltipcontroller.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,8 @@ function TooltipController(options) {
202202

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

209209
// trigger powerTipClose event
@@ -231,8 +231,8 @@ function TooltipController(options) {
231231
collisionCount;
232232

233233
// grab collisions
234-
coords.set('top', session.currentY + options.offset);
235-
coords.set('left', session.currentX + options.offset);
234+
coords.set('top', session.currentY + options.offset - session.positionCompensation.top);
235+
coords.set('left', session.currentX + options.offset - session.positionCompensation.left);
236236
collisions = getViewportCollisions(
237237
coords,
238238
tipWidth,
@@ -246,16 +246,16 @@ function TooltipController(options) {
246246
// if there is only one collision (bottom or right) then
247247
// simply constrain the tooltip to the view port
248248
if (collisions === Collision.right) {
249-
coords.set('left', session.windowWidth - tipWidth);
249+
coords.set('left', session.windowWidth - tipWidth - session.positionCompensation.left);
250250
} else if (collisions === Collision.bottom) {
251-
coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
251+
coords.set('top', session.scrollTop + session.windowHeight - tipHeight - session.positionCompensation.top);
252252
}
253253
} else {
254254
// if the tooltip has more than one collision then it is
255255
// trapped in the corner and should be flipped to get it out
256256
// of the users way
257-
coords.set('left', session.currentX - tipWidth - options.offset);
258-
coords.set('top', session.currentY - tipHeight - options.offset);
257+
coords.set('left', session.currentX - tipWidth - options.offset - session.positionCompensation.left);
258+
coords.set('top', session.currentY - tipHeight - options.offset - session.positionCompensation.top);
259259
}
260260
}
261261

src/utility.js

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ function getViewportDimensions() {
5959
session.scrollTop = $window.scrollTop();
6060
session.windowWidth = $window.width();
6161
session.windowHeight = $window.height();
62+
session.positionCompensation = computePositionCompensation(session.windowWidth, session.windowHeight);
6263
}
6364

6465
/**
@@ -68,6 +69,7 @@ function getViewportDimensions() {
6869
function trackResize() {
6970
session.windowWidth = $window.width();
7071
session.windowHeight = $window.height();
72+
session.positionCompensation = computePositionCompensation(session.windowWidth, session.windowHeight);
7173
}
7274

7375
/**
@@ -165,8 +167,10 @@ function getTooltipContent(element) {
165167
* @return {number} Value with the collision flags.
166168
*/
167169
function getViewportCollisions(coords, elementWidth, elementHeight) {
168-
var viewportTop = session.scrollTop,
169-
viewportLeft = session.scrollLeft,
170+
// adjusting viewport even though it might be negative because coords
171+
// comparing with are relative to compensated position
172+
var viewportTop = session.scrollTop - session.positionCompensation.top,
173+
viewportLeft = session.scrollLeft - session.positionCompensation.left,
170174
viewportBottom = viewportTop + session.windowHeight,
171175
viewportRight = viewportLeft + session.windowWidth,
172176
collisions = Collision.none;
@@ -200,3 +204,41 @@ function countFlags(value) {
200204
}
201205
return count;
202206
}
207+
208+
/**
209+
* Compute compensating position offsets if body element has non-standard position attribute.
210+
* @private
211+
* @param {number} windowWidth Window width in pixels.
212+
* @param {number} windowHeight Window height in pixels.
213+
* @return {Offsets} The top, left, right, bottom offset in pixels
214+
*/
215+
function computePositionCompensation(windowWidth, windowHeight) {
216+
var bodyWidthWithMargin,
217+
bodyHeightWithMargin,
218+
offsets,
219+
bodyPositionPx;
220+
221+
switch ($body.css('position')) {
222+
case 'absolute':
223+
case 'fixed':
224+
case 'relative':
225+
// jquery offset and position functions return top and left
226+
// offset function computes position + margin
227+
offsets = $body.offset();
228+
bodyPositionPx = $body.position();
229+
// because element might be positioned compute right margin using the different between
230+
// outerWidth computations and add position offset
231+
bodyWidthWithMargin = $body.outerWidth(true);
232+
bodyHeightWithMargin = $body.outerHeight(true);
233+
// right offset = right margin + body right position
234+
offsets.right = (bodyWidthWithMargin - $body.outerWidth() - (offsets.left - bodyPositionPx.left)) + (windowWidth - bodyWidthWithMargin - bodyPositionPx.left);
235+
// bottom offset = bottom margin + body bottom position
236+
offsets.bottom = (bodyHeightWithMargin - $body.outerHeight() - offsets.top) + (windowHeight - bodyHeightWithMargin - bodyPositionPx.top);
237+
break;
238+
default:
239+
// even though body may have offset, no compensation is required
240+
offsets = { top: 0, bottom: 0, left: 0, right: 0 };
241+
}
242+
243+
return offsets;
244+
}

test/bodyoffset-abs.html

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<!DOCTYPE html>
2+
<html lang="en-US">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>PowerTip Test Suite</title>
6+
7+
<!-- Library Resources -->
8+
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>
9+
10+
<!-- PowerTip Core Resources -->
11+
<script type="text/javascript" src="../src/core.js"></script>
12+
<script type="text/javascript" src="../src/csscoordinates.js"></script>
13+
<script type="text/javascript" src="../src/displaycontroller.js"></script>
14+
<script type="text/javascript" src="../src/placementcalculator.js"></script>
15+
<script type="text/javascript" src="../src/tooltipcontroller.js"></script>
16+
<script type="text/javascript" src="../src/utility.js"></script>
17+
<link rel="stylesheet" type="text/css" href="../css/jquery.powertip.css" />
18+
19+
<!-- Unit Test Scripts -->
20+
<script type="text/javascript" src="tests-bodyoffset.js"></script>
21+
22+
<!-- Custom Styles For Test Cases -->
23+
<style type="text/css">
24+
header, section { margin-bottom: 20px; }
25+
section { border: 1px solid #CCC; margin: 20px; padding: 20px; }
26+
#powerTip { white-space: normal; }
27+
#huge-text div, #huge-text-smart div { text-align: center; }
28+
#huge-text input, #huge-text-smart input { margin: 10px; padding: 10px; }
29+
#huge-text .east, #huge-text-smart .east { margin-left: 450px; }
30+
#session { position: fixed; right: 10px; top: 10px; font-size: 10px; width: 160px; background-color: #fff; border: 1px solid #ccc; padding: 10px; overflow: hidden; }
31+
#session pre { margin: 0; }
32+
</style>
33+
</head>
34+
<body style="position:absolute; left:50px; right:100px; top:25px; bottom:75px;">
35+
<section id="huge-text">
36+
<h2>Huge Text</h2>
37+
<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>
38+
<div>
39+
<input type="button" class="north-west-alt" value="North West Alt" />
40+
<input type="button" class="north-west" value="North West" />
41+
<input type="button" class="north" value="North" />
42+
<input type="button" class="north-east" value="North East" />
43+
<input type="button" class="north-east-alt" value="North East Alt" /><br />
44+
<input type="button" class="west" value="West" />
45+
<input type="button" class="east" value="East" /><br />
46+
<input type="button" class="south-west-alt" value="South West Alt" />
47+
<input type="button" class="south-west" value="South West" />
48+
<input type="button" class="south" value="South" />
49+
<input type="button" class="south-east" value="South East" />
50+
<input type="button" class="south-east-alt" value="South East Alt" />
51+
</div>
52+
</section>
53+
<section id="huge-text-smart">
54+
<h2>Huge Text with Smart Placement</h2>
55+
<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>
56+
<div>
57+
<input type="button" class="north-west-alt" value="North West Alt" />
58+
<input type="button" class="north-west" value="North West" />
59+
<input type="button" class="north" value="North" />
60+
<input type="button" class="north-east" value="North East" />
61+
<input type="button" class="north-east-alt" value="North East Alt" /><br />
62+
<input type="button" class="west" value="West" />
63+
<input type="button" class="east" value="East" /><br />
64+
<input type="button" class="south-west-alt" value="South West Alt" />
65+
<input type="button" class="south-west" value="South West" />
66+
<input type="button" class="south" value="South" />
67+
<input type="button" class="south-east" value="South East" />
68+
<input type="button" class="south-east-alt" value="South East Alt" />
69+
</div>
70+
</section>
71+
<section id="trapped-mousefollow" data-powertip="This is the tooltip text.&lt;br /&gt;It is tall so you can test the padding.">
72+
<h2>Trapped mouse following tooltip</h2>
73+
<p>This box has a mouse following tooltip.</p>
74+
<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>
75+
</section>
76+
<div id="session"><pre /></div>
77+
</body>
78+
</html>

test/bodyoffset-rel.html

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<!DOCTYPE html>
2+
<html lang="en-US">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>PowerTip Test Suite</title>
6+
7+
<!-- Library Resources -->
8+
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>
9+
10+
<!-- PowerTip Core Resources -->
11+
<script type="text/javascript" src="../src/core.js"></script>
12+
<script type="text/javascript" src="../src/csscoordinates.js"></script>
13+
<script type="text/javascript" src="../src/displaycontroller.js"></script>
14+
<script type="text/javascript" src="../src/placementcalculator.js"></script>
15+
<script type="text/javascript" src="../src/tooltipcontroller.js"></script>
16+
<script type="text/javascript" src="../src/utility.js"></script>
17+
<link rel="stylesheet" type="text/css" href="../css/jquery.powertip.css" />
18+
19+
<!-- Unit Test Scripts -->
20+
<script type="text/javascript" src="tests-bodyoffset.js"></script>
21+
22+
<!-- Custom Styles For Test Cases -->
23+
<style type="text/css">
24+
header, section { margin-bottom: 20px; }
25+
section { border: 1px solid #CCC; margin: 20px; padding: 20px; }
26+
#powerTip { white-space: normal; }
27+
#huge-text div, #huge-text-smart div { text-align: center; }
28+
#huge-text input, #huge-text-smart input { margin: 10px; padding: 10px; }
29+
#huge-text .east, #huge-text-smart .east { margin-left: 450px; }
30+
#session { position: fixed; right: 10px; top: 10px; font-size: 10px; width: 160px; background-color: #fff; border: 1px solid #ccc; padding: 10px; overflow: hidden; }
31+
#session pre { margin: 0; }
32+
</style>
33+
</head>
34+
<body style="position:relative">
35+
<section id="huge-text">
36+
<h2>Huge Text</h2>
37+
<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>
38+
<div>
39+
<input type="button" class="north-west-alt" value="North West Alt" />
40+
<input type="button" class="north-west" value="North West" />
41+
<input type="button" class="north" value="North" />
42+
<input type="button" class="north-east" value="North East" />
43+
<input type="button" class="north-east-alt" value="North East Alt" /><br />
44+
<input type="button" class="west" value="West" />
45+
<input type="button" class="east" value="East" /><br />
46+
<input type="button" class="south-west-alt" value="South West Alt" />
47+
<input type="button" class="south-west" value="South West" />
48+
<input type="button" class="south" value="South" />
49+
<input type="button" class="south-east" value="South East" />
50+
<input type="button" class="south-east-alt" value="South East Alt" />
51+
</div>
52+
</section>
53+
<section id="huge-text-smart">
54+
<h2>Huge Text with Smart Placement</h2>
55+
<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>
56+
<div>
57+
<input type="button" class="north-west-alt" value="North West Alt" />
58+
<input type="button" class="north-west" value="North West" />
59+
<input type="button" class="north" value="North" />
60+
<input type="button" class="north-east" value="North East" />
61+
<input type="button" class="north-east-alt" value="North East Alt" /><br />
62+
<input type="button" class="west" value="West" />
63+
<input type="button" class="east" value="East" /><br />
64+
<input type="button" class="south-west-alt" value="South West Alt" />
65+
<input type="button" class="south-west" value="South West" />
66+
<input type="button" class="south" value="South" />
67+
<input type="button" class="south-east" value="South East" />
68+
<input type="button" class="south-east-alt" value="South East Alt" />
69+
</div>
70+
</section>
71+
<section id="trapped-mousefollow" data-powertip="This is the tooltip text.&lt;br /&gt;It is tall so you can test the padding.">
72+
<h2>Trapped mouse following tooltip</h2>
73+
<p>This box has a mouse following tooltip.</p>
74+
<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>
75+
</section>
76+
<div id="session"><pre /></div>
77+
</body>
78+
</html>

0 commit comments

Comments
 (0)