From 2a3f2e76e7c426318a48378a818631a8a02cc856 Mon Sep 17 00:00:00 2001 From: Joel Steres Date: Wed, 24 Aug 2016 20:39:38 -0700 Subject: [PATCH] Compensate for body tag with position absolute, relative or static Also opts for making all placement based on top rather than bottom. --- src/placementcalculator.js | 48 ++++++++++---------- src/tooltipcontroller.js | 16 +++---- src/utility.js | 46 ++++++++++++++++++- test/bodyoffset-abs.html | 78 ++++++++++++++++++++++++++++++++ test/bodyoffset-rel.html | 78 ++++++++++++++++++++++++++++++++ test/tests-bodyoffset.js | 69 ++++++++++++++++++++++++++++ test/unit/placementcalculator.js | 14 +----- 7 files changed, 303 insertions(+), 46 deletions(-) create mode 100644 test/bodyoffset-abs.html create mode 100644 test/bodyoffset-rel.html create mode 100644 test/tests-bodyoffset.js diff --git a/src/placementcalculator.js b/src/placementcalculator.js index a4329abd..48424234 100644 --- a/src/placementcalculator.js +++ b/src/placementcalculator.js @@ -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; } diff --git a/src/tooltipcontroller.js b/src/tooltipcontroller.js index 42cdc51e..3a0f4d63 100644 --- a/src/tooltipcontroller.js +++ b/src/tooltipcontroller.js @@ -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 @@ -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, @@ -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); } } diff --git a/src/utility.js b/src/utility.js index c0d92bc0..5cfa1a95 100644 --- a/src/utility.js +++ b/src/utility.js @@ -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); } /** @@ -68,6 +69,7 @@ function getViewportDimensions() { function trackResize() { session.windowWidth = $window.width(); session.windowHeight = $window.height(); + session.positionCompensation = computePositionCompensation(session.windowWidth, session.windowHeight); } /** @@ -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; @@ -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; +} diff --git a/test/bodyoffset-abs.html b/test/bodyoffset-abs.html new file mode 100644 index 00000000..62775272 --- /dev/null +++ b/test/bodyoffset-abs.html @@ -0,0 +1,78 @@ + + + + + PowerTip Test Suite + + + + + + + + + + + + + + + + + + + + +
+

Huge Text

+

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.

+
+ + + + +
+ +
+ + + + + +
+
+
+

Huge Text with Smart Placement

+

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.

+
+ + + + +
+ +
+ + + + + +
+
+
+

Trapped mouse following tooltip

+

This box has a mouse following tooltip.

+

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.

+
+
+ + diff --git a/test/bodyoffset-rel.html b/test/bodyoffset-rel.html new file mode 100644 index 00000000..47c58e94 --- /dev/null +++ b/test/bodyoffset-rel.html @@ -0,0 +1,78 @@ + + + + + PowerTip Test Suite + + + + + + + + + + + + + + + + + + + + +
+

Huge Text

+

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.

+
+ + + + +
+ +
+ + + + + +
+
+
+

Huge Text with Smart Placement

+

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.

+
+ + + + +
+ +
+ + + + + +
+
+
+

Trapped mouse following tooltip

+

This box has a mouse following tooltip.

+

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.

+
+
+ + diff --git a/test/tests-bodyoffset.js b/test/tests-bodyoffset.js new file mode 100644 index 00000000..34603564 --- /dev/null +++ b/test/tests-bodyoffset.js @@ -0,0 +1,69 @@ +$(function() { + 'use strict'; + + // setup huge text tooltips + var hugeText = [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent sed', + 'volutpat tellus. Fusce mollis iaculis est at sodales. Proin aliquam', + 'bibendum neque, nec blandit orci porttitor non. Cras lacinia varius', + 'felis vel ultricies. Nulla eu sapien arcu, dapibus tempor eros.', + 'Praesent aliquet hendrerit commodo. Pellentesque habitant morbi', + 'tristique senectus et netus et malesuada fames ac turpis egestas.', + 'Proin gravida justo faucibus urna dictum id egestas velit hendrerit.', + 'Praesent dapibus rutrum tempor. Sed ultrices varius purus, eu rhoncus', + 'tortor scelerisque sit amet. Sed vitae molestie diam. Pellentesque', + 'posuere euismod venenatis. Proin ut ligula vel urna lacinia accumsan.', + 'Quisque commodo ultrices orci ut cursus. Aliquam in dolor orci. Nunc', + 'pretium euismod odio.' + ].join(' '); + $.each( + [ + 'north', + 'east', + 'south', + 'west', + 'north-west', + 'north-east', + 'south-west', + 'south-east', + 'north-west-alt', + 'north-east-alt', + 'south-west-alt', + 'south-east-alt' + ], + function(i, val) { + $('.' + val).data('powertip', hugeText); + } + ); + + // Huge text + $('#huge-text .north').powerTip({ placement: 'n' }); + $('#huge-text .east').powerTip({ placement: 'e' }); + $('#huge-text .south').powerTip({ placement: 's' }); + $('#huge-text .west').powerTip({ placement: 'w' }); + $('#huge-text .north-west').powerTip({ placement: 'nw' }); + $('#huge-text .north-east').powerTip({ placement: 'ne' }); + $('#huge-text .south-west').powerTip({ placement: 'sw' }); + $('#huge-text .south-east').powerTip({ placement: 'se' }); + $('#huge-text .north-west-alt').powerTip({ placement: 'nw-alt' }); + $('#huge-text .north-east-alt').powerTip({ placement: 'ne-alt' }); + $('#huge-text .south-west-alt').powerTip({ placement: 'sw-alt' }); + $('#huge-text .south-east-alt').powerTip({ placement: 'se-alt' }); + + // Huge text with smart placement + $('#huge-text-smart .north').powerTip({ placement: 'n', smartPlacement: true }); + $('#huge-text-smart .east').powerTip({ placement: 'e', smartPlacement: true }); + $('#huge-text-smart .south').powerTip({ placement: 's', smartPlacement: true }); + $('#huge-text-smart .west').powerTip({ placement: 'w', smartPlacement: true }); + $('#huge-text-smart .north-west').powerTip({ placement: 'nw', smartPlacement: true }); + $('#huge-text-smart .north-east').powerTip({ placement: 'ne', smartPlacement: true }); + $('#huge-text-smart .south-west').powerTip({ placement: 'sw', smartPlacement: true }); + $('#huge-text-smart .south-east').powerTip({ placement: 'se', smartPlacement: true }); + $('#huge-text-smart .north-west-alt').powerTip({ placement: 'nw-alt', smartPlacement: true }); + $('#huge-text-smart .north-east-alt').powerTip({ placement: 'ne-alt', smartPlacement: true }); + $('#huge-text-smart .south-west-alt').powerTip({ placement: 'sw-alt', smartPlacement: true }); + $('#huge-text-smart .south-east-alt').powerTip({ placement: 'se-alt', smartPlacement: true }); + + // Trapped mouse following tooltip + $('#trapped-mousefollow').powerTip({ followMouse: true }); +}); diff --git a/test/unit/placementcalculator.js b/test/unit/placementcalculator.js index 699453bd..031d4a24 100644 --- a/test/unit/placementcalculator.js +++ b/test/unit/placementcalculator.js @@ -40,11 +40,6 @@ $(function() { case 'n': case 'ne': case 'nw-alt': - assert.strictEqual(coords.top, 'auto', key + ': top property is set to auto'); - assert.strictEqual($.isNumeric(coords.left), true, key + ': left property is set to a number'); - assert.strictEqual(coords.right, 'auto', key + ': right property is set to auto'); - assert.strictEqual($.isNumeric(coords.bottom), true, key + ': bottom property is set to a number'); - break; case 'e': case 's': case 'se': @@ -57,17 +52,12 @@ $(function() { case 'w': case 'sw': case 'se-alt': - assert.strictEqual($.isNumeric(coords.top), true, key + ': top property is set to a number'); - assert.strictEqual(coords.left, 'auto', key + ': left property is set to auto'); - assert.strictEqual($.isNumeric(coords.right), true, key + ': right property is set to a number'); - assert.strictEqual(coords.bottom, 'auto', key + ': bottom property is set to auto'); - break; case 'nw': case 'ne-alt': - assert.strictEqual(coords.top, 'auto', key + ': top property is set to auto'); + assert.strictEqual($.isNumeric(coords.top), true, key + ': top property is set to a number'); assert.strictEqual(coords.left, 'auto', key + ': left property is set to auto'); assert.strictEqual($.isNumeric(coords.right), true, key + ': right property is set to a number'); - assert.strictEqual($.isNumeric(coords.bottom), true, key + ': bottom property is set to a number'); + assert.strictEqual(coords.bottom, 'auto', key + ': bottom property is set to auto'); break; } });