Skip to content

Commit 9f9b2fb

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 582ff4d commit 9f9b2fb

File tree

7 files changed

+303
-46
lines changed

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
@@ -60,6 +60,7 @@ function getViewportDimensions() {
6060
session.scrollTop = $window.scrollTop();
6161
session.windowWidth = $window.width();
6262
session.windowHeight = $window.height();
63+
session.positionCompensation = computePositionCompensation(session.windowWidth, session.windowHeight);
6364
}
6465

6566
/**
@@ -69,6 +70,7 @@ function getViewportDimensions() {
6970
function trackResize() {
7071
session.windowWidth = $window.width();
7172
session.windowHeight = $window.height();
73+
session.positionCompensation = computePositionCompensation(session.windowWidth, session.windowHeight);
7274
}
7375

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

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)