diff --git a/src/tooltip/test/tooltip2.spec.js b/src/tooltip/test/tooltip2.spec.js index bb7e6ebba5..afdccef7c3 100644 --- a/src/tooltip/test/tooltip2.spec.js +++ b/src/tooltip/test/tooltip2.spec.js @@ -1,14 +1,15 @@ describe('tooltip directive', function () { - var $rootScope, $compile, $document, $timeout; + var $rootScope, $compile, $document, $timeout, $position; beforeEach(module('ui.bootstrap.tooltip')); beforeEach(module('template/tooltip/tooltip-popup.html')); - beforeEach(inject(function (_$rootScope_, _$compile_, _$document_, _$timeout_) { + beforeEach(inject(function (_$rootScope_, _$compile_, _$document_, _$timeout_, _$position_) { $rootScope = _$rootScope_; $compile = _$compile_; $document = _$document_; $timeout = _$timeout_; + $position = _$position_; })); beforeEach(function(){ @@ -102,4 +103,38 @@ describe('tooltip directive', function () { }); }); + + describe('position', function() { + + beforeEach(function() { + spyOn($position, 'position').andCallThrough(); + }); + + it('is called once on mouse enter and leave', function () { + var fragment = compileTooltip('Trigger here'); + + expect($position.position).not.toHaveBeenCalled(); + + fragment.find('span').trigger( 'mouseenter' ); + expect($position.position.calls.length).toEqual(1); + + closeTooltip(fragment.find('span')); + expect($position.position.calls.length).toEqual(1); + }); + + it('is called once for content updates when is open', function () { + $rootScope.content = 'some text'; + var fragment = compileTooltip('Trigger here'); + + expect($position.position).not.toHaveBeenCalled(); + + fragment.find('span').trigger( 'mouseenter' ); + expect($position.position.calls.length).toEqual(1); + + $rootScope.content = 'some new text'; + $rootScope.$digest(); + $timeout.flush(); + expect($position.position.calls.length).toEqual(1); + }); + }); }); \ No newline at end of file diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index 3176f96902..46e17f2f6f 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -109,13 +109,15 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap restrict: 'EA', scope: true, link: function link ( scope, element, attrs ) { - var tooltip = $compile( template )( scope ); - var transitionTimeout; - var popupTimeout; - var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; - var triggers = getTriggers( undefined ); - var hasRegisteredTriggers = false; - var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + var tooltip = $compile( template )( scope ), + transitionTimeout, + popupTimeout, + appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false, + triggers = getTriggers( undefined ), + hasRegisteredTriggers = false, + hasEnableExp = angular.isDefined(attrs[prefix+'Enable']), + position, + positionUpdateTimeout; // By default, the tooltip is not open. // TODO add ability to start tooltip opened @@ -147,77 +149,74 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap }); } - // Show the tooltip popup element. - function show() { - var position, - ttWidth, - ttHeight, + // Calculate tootip's position + function updatePosition() { + var ttWidth = tooltip.prop( 'offsetWidth' ), + ttHeight = tooltip.prop( 'offsetHeight' ), ttPosition; - - // Don't show empty tooltips. - if ( ! scope.tt_content ) { - return; - } - - // If there is a pending remove transition, we must cancel it, lest the - // tooltip be mysteriously removed. - if ( transitionTimeout ) { - $timeout.cancel( transitionTimeout ); - } - - // Set the initial positioning. - tooltip.css({ top: 0, left: 0, display: 'block' }); - - // Now we add it to the DOM because need some info about it. But it's not - // visible yet anyway. - if ( appendToBody ) { - $document.find( 'body' ).append( tooltip ); - } else { - element.after( tooltip ); - } - - // 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 + top: position.top + position.height / 2 - ttHeight / 2 + 'px', + left: position.left + position.width + 'px' }; break; case 'bottom': ttPosition = { - top: position.top + position.height, - left: position.left + position.width / 2 - ttWidth / 2 + top: position.top + position.height + 'px', + left: position.left + position.width / 2 - ttWidth / 2 + 'px' }; break; case 'left': ttPosition = { - top: position.top + position.height / 2 - ttHeight / 2, - left: position.left - ttWidth + top: position.top + position.height / 2 - ttHeight / 2 + 'px', + left: position.left - ttWidth + 'px' }; break; default: ttPosition = { - top: position.top - ttHeight, - left: position.left + position.width / 2 - ttWidth / 2 + top: position.top - ttHeight + 'px', + left: position.left + position.width / 2 - ttWidth / 2 + 'px' }; break; } - ttPosition.top += 'px'; - ttPosition.left += 'px'; - // Now set the calculated positioning. tooltip.css( ttPosition ); - + } + + // Show the tooltip popup element. + function show() { + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return; + } + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + } + + tooltip.css({ display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + if ( appendToBody ) { + $document.find( 'body' ).append( tooltip ); + } else { + element.after( tooltip ); + } + + // Get the position of the directive element. + position = appendToBody ? $position.offset( element ) : $position.position( element ); + + // Place tooltip + updatePosition(); + // And show the tooltip. scope.tt_isOpen = true; } @@ -248,8 +247,14 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap attrs.$observe( type, function ( val ) { scope.tt_content = val; - if (!val && scope.tt_isOpen ) { - hide(); + if (scope.tt_isOpen) { + if (!val) { + hide(); + } else { + // Wait for content to be set on the tooltip element and adjust position + // based on the new width/height and last known position of the directive element. + positionUpdateTimeout = $timeout(updatePosition, 0, false); + } } }); @@ -311,6 +316,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap scope.$on('$destroy', function onDestroyTooltip() { $timeout.cancel( transitionTimeout ); $timeout.cancel( popupTimeout ); + $timeout.cancel( positionUpdateTimeout ); unregisterTriggers(); tooltip.remove(); tooltip.unbind();