Skip to content

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)