From 3704db9ab084dab0643bdd850932abf124d2a669 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Sat, 25 Jan 2014 20:22:54 +0100 Subject: [PATCH] feat(tooltip): support more positioning options Closes #1676 --- src/position/position.js | 72 ++++++++++++++++++++++ src/position/test/position.spec.js | 99 ++++++++++++++++++++++++++++++ src/tooltip/tooltip.js | 43 +------------ 3 files changed, 173 insertions(+), 41 deletions(-) create mode 100644 src/position/test/position.spec.js diff --git a/src/position/position.js b/src/position/position.js index 67ee4b422a..3444c33449 100644 --- a/src/position/position.js +++ b/src/position/position.js @@ -75,6 +75,78 @@ angular.module('ui.bootstrap.position', []) top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) }; + }, + + /** + * Provides coordinates for the targetEl in relation to hostEl + */ + positionElements: function (hostEl, targetEl, positionStr, appendToBody) { + + var positionStrParts = positionStr.split('-'); + var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; + + var hostElPos, + targetElWidth, + targetElHeight, + targetElPos; + + hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + + targetElWidth = targetEl.prop('offsetWidth'); + targetElHeight = targetEl.prop('offsetHeight'); + + var shiftWidth = { + center: function () { + return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; + }, + left: function () { + return hostElPos.left; + }, + right: function () { + return hostElPos.left + hostElPos.width; + } + }; + + var shiftHeight = { + center: function () { + return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; + }, + top: function () { + return hostElPos.top; + }, + bottom: function () { + return hostElPos.top + hostElPos.height; + } + }; + + switch (pos0) { + case 'right': + targetElPos = { + top: shiftHeight[pos1](), + left: shiftWidth[pos0]() + }; + break; + case 'left': + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left - targetElWidth + }; + break; + case 'bottom': + targetElPos = { + top: shiftHeight[pos0](), + left: shiftWidth[pos1]() + }; + break; + default: + targetElPos = { + top: hostElPos.top - targetElHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElPos; } }; }]); diff --git a/src/position/test/position.spec.js b/src/position/test/position.spec.js new file mode 100644 index 0000000000..bf615d3328 --- /dev/null +++ b/src/position/test/position.spec.js @@ -0,0 +1,99 @@ +describe('position elements', function () { + + var TargetElMock = function(width, height) { + this.width = width; + this.height = height; + + this.prop = function(propName) { + return propName === 'offsetWidth' ? width : height; + }; + }; + + var $position; + + beforeEach(module('ui.bootstrap.position')); + beforeEach(inject(function (_$position_) { + $position = _$position_; + })); + beforeEach(function () { + this.addMatchers({ + toBePositionedAt: function(top, left) { + this.message = function() { + return 'Expected "(' + this.actual.top + ', ' + this.actual.left + ')" to be positioned at (' + top + ', ' + left + ')'; + }; + + return this.actual.top == top && this.actual.left == left; + } + }); + }); + + + describe('append-to-body: false', function () { + + beforeEach(function () { + //mock position info normally queried from the DOM + $position.position = function() { + return { + width: 20, + height: 20, + top: 100, + left: 100 + }; + }; + }); + + it('should position element on top-center by default', function () { + expect($position.positionElements({}, new TargetElMock(10, 10), 'other')).toBePositionedAt(90, 105); + expect($position.positionElements({}, new TargetElMock(10, 10), 'top')).toBePositionedAt(90, 105); + expect($position.positionElements({}, new TargetElMock(10, 10), 'top-center')).toBePositionedAt(90, 105); + }); + + it('should position on top-left', function () { + expect($position.positionElements({}, new TargetElMock(10, 10), 'top-left')).toBePositionedAt(90, 100); + }); + + it('should position on top-right', function () { + expect($position.positionElements({}, new TargetElMock(10, 10), 'top-right')).toBePositionedAt(90, 120); + }); + + it('should position elements on bottom-center when "bottom" specified', function () { + expect($position.positionElements({}, new TargetElMock(10, 10), 'bottom')).toBePositionedAt(120, 105); + expect($position.positionElements({}, new TargetElMock(10, 10), 'bottom-center')).toBePositionedAt(120, 105); + }); + + it('should position elements on bottom-left', function () { + expect($position.positionElements({}, new TargetElMock(10, 10), 'bottom-left')).toBePositionedAt(120, 100); + }); + + it('should position elements on bottom-right', function () { + expect($position.positionElements({}, new TargetElMock(10, 10), 'bottom-right')).toBePositionedAt(120, 120); + }); + + it('should position elements on left-center when "left" specified', function () { + expect($position.positionElements({}, new TargetElMock(10, 10), 'left')).toBePositionedAt(105, 90); + expect($position.positionElements({}, new TargetElMock(10, 10), 'left-center')).toBePositionedAt(105, 90); + }); + + it('should position elements on left-top when "left-top" specified', function () { + expect($position.positionElements({}, new TargetElMock(10, 10), 'left-top')).toBePositionedAt(100, 90); + }); + + it('should position elements on left-bottom when "left-bottom" specified', function () { + expect($position.positionElements({}, new TargetElMock(10, 10), 'left-bottom')).toBePositionedAt(120, 90); + }); + + it('should position elements on right-center when "right" specified', function () { + expect($position.positionElements({}, new TargetElMock(10, 10), 'right')).toBePositionedAt(105, 120); + expect($position.positionElements({}, new TargetElMock(10, 10), 'right-center')).toBePositionedAt(105, 120); + }); + + it('should position elements on right-top when "right-top" specified', function () { + expect($position.positionElements({}, new TargetElMock(10, 10), 'right-top')).toBePositionedAt(100, 120); + }); + + it('should position elements on right-top when "right-top" specified', function () { + expect($position.positionElements({}, new TargetElMock(10, 10), 'right-bottom')).toBePositionedAt(120, 120); + }); + }); + +}); \ No newline at end of file diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index 57c9cf09ee..da1b356799 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -119,53 +119,14 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap var triggers = getTriggers( undefined ); var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); - var positionTooltip = function (){ - var position, - ttWidth, - ttHeight, - ttPosition; - // Get the position of the directive element. - position = appendToBody ? $position.offset( element ) : $position.position( element ); - - // Get the height and width of the tooltip so we can center it. - ttWidth = tooltip.prop( 'offsetWidth' ); - ttHeight = tooltip.prop( 'offsetHeight' ); - - // Calculate the tooltip's top and left coordinates to center it with - // this directive. - switch ( scope.tt_placement ) { - case 'right': - ttPosition = { - top: position.top + position.height / 2 - ttHeight / 2, - left: position.left + position.width - }; - break; - case 'bottom': - ttPosition = { - top: position.top + position.height, - left: position.left + position.width / 2 - ttWidth / 2 - }; - break; - case 'left': - ttPosition = { - top: position.top + position.height / 2 - ttHeight / 2, - left: position.left - ttWidth - }; - break; - default: - ttPosition = { - top: position.top - ttHeight, - left: position.left + position.width / 2 - ttWidth / 2 - }; - break; - } + var positionTooltip = function () { + var ttPosition = $position.positionElements(element, tooltip, scope.tt_placement, appendToBody); ttPosition.top += 'px'; ttPosition.left += 'px'; // Now set the calculated positioning. tooltip.css( ttPosition ); - }; // By default, the tooltip is not open.