diff --git a/src/tooltip/test/tooltip.spec.js b/src/tooltip/test/tooltip.spec.js
index 01f6423fe7..90be4f6907 100644
--- a/src/tooltip/test/tooltip.spec.js
+++ b/src/tooltip/test/tooltip.spec.js
@@ -98,7 +98,7 @@ describe('tooltip', function() {
scope.alt = "Alt Message";
elmBody = $compile( angular.element(
- '
Selector Text
'
+ 'Selector Text
'
) )( scope );
$compile( elmBody )( scope );
@@ -114,6 +114,13 @@ describe('tooltip', function() {
expect( ttScope.content ).toBe( scope.tooltipMsg );
elm.trigger( 'mouseleave' );
+
+ //Isolate scope contents should be the same after hiding and showing again (issue 1191)
+ elm.trigger( 'mouseenter' );
+
+ ttScope = angular.element( elmBody.children()[1] ).scope();
+ expect( ttScope.placement ).toBe( 'top' );
+ expect( ttScope.content ).toBe( scope.tooltipMsg );
}));
it('should not show tooltips if there is nothing to show - issue #129', inject(function ($compile) {
@@ -136,6 +143,24 @@ describe('tooltip', function() {
expect( elmBody.children().length ).toBe( 0 );
}));
+ it('issue 1191 - isolate scope on the popup should always be child of correct element scope', inject( function ( $compile ) {
+ var ttScope;
+ elm.trigger( 'mouseenter' );
+
+ ttScope = angular.element( elmBody.children()[1] ).scope();
+ expect( ttScope.$parent ).toBe( elmScope );
+
+ elm.trigger( 'mouseleave' );
+
+ // After leaving and coming back, the scope's parent should be the same
+ elm.trigger( 'mouseenter' );
+
+ ttScope = angular.element( elmBody.children()[1] ).scope();
+ expect( ttScope.$parent ).toBe( elmScope );
+
+ elm.trigger( 'mouseleave' );
+ }));
+
describe('with specified enable expression', function() {
beforeEach(inject(function ($compile) {
@@ -323,18 +348,12 @@ describe('tooltip', function() {
elm = elmBody.find('input');
elmScope = elm.scope();
+ elm.trigger('fooTrigger');
tooltipScope = elmScope.$$childTail;
}));
- it( 'should not contain a cached reference', function() {
- expect( inCache() ).toBeTruthy();
- elmScope.$destroy();
- expect( inCache() ).toBeFalsy();
- });
-
it( 'should not contain a cached reference when visible', inject( function( $timeout ) {
expect( inCache() ).toBeTruthy();
- elm.trigger('fooTrigger');
elmScope.$destroy();
expect( inCache() ).toBeFalsy();
}));
diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js
index f6d116647f..c6a610f8f3 100644
--- a/src/tooltip/tooltip.js
+++ b/src/tooltip/tooltip.js
@@ -108,224 +108,245 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
return {
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 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;
+ compile: function (tElem, tAttrs) {
+ var tooltipLinker = $compile( template );
+
+ return function link ( scope, element, attrs ) {
+ var tooltip;
+ 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 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;
+ }
+
+ ttPosition.top += 'px';
+ ttPosition.left += 'px';
+
+ // Now set the calculated positioning.
+ tooltip.css( ttPosition );
+
+ };
+
+ // By default, the tooltip is not open.
+ // TODO add ability to start tooltip opened
+ scope.tt_isOpen = false;
+
+ function toggleTooltipBind () {
+ if ( ! scope.tt_isOpen ) {
+ showTooltipBind();
+ } else {
+ hideTooltipBind();
+ }
}
- ttPosition.top += 'px';
- ttPosition.left += 'px';
+ // Show the tooltip with delay if specified, otherwise show it immediately
+ function showTooltipBind() {
+ if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
+ return;
+ }
+ if ( scope.tt_popupDelay ) {
+ popupTimeout = $timeout( show, scope.tt_popupDelay, false );
+ popupTimeout.then(function(reposition){reposition();});
+ } else {
+ show()();
+ }
+ }
- // Now set the calculated positioning.
- tooltip.css( ttPosition );
+ function hideTooltipBind () {
+ scope.$apply(function () {
+ hide();
+ });
+ }
- };
+ // Show the tooltip popup element.
+ function show() {
- // By default, the tooltip is not open.
- // TODO add ability to start tooltip opened
- scope.tt_isOpen = false;
- function toggleTooltipBind () {
- if ( ! scope.tt_isOpen ) {
- showTooltipBind();
- } else {
- hideTooltipBind();
- }
- }
-
- // Show the tooltip with delay if specified, otherwise show it immediately
- function showTooltipBind() {
- if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
- return;
- }
- if ( scope.tt_popupDelay ) {
- popupTimeout = $timeout( show, scope.tt_popupDelay );
- popupTimeout.then(function(reposition){reposition();});
- } else {
- scope.$apply( show )();
- }
- }
+ // Don't show empty tooltips.
+ if ( ! scope.tt_content ) {
+ return angular.noop;
+ }
- function hideTooltipBind () {
- scope.$apply(function () {
- hide();
- });
- }
-
- // Show the tooltip popup element.
- function show() {
+ createTooltip();
+ // If there is a pending remove transition, we must cancel it, lest the
+ // tooltip be mysteriously removed.
+ if ( transitionTimeout ) {
+ $timeout.cancel( transitionTimeout );
+ }
- // Don't show empty tooltips.
- if ( ! scope.tt_content ) {
- return angular.noop;
- }
+ // Set the initial positioning.
+ tooltip.css({ top: 0, left: 0, display: 'block' });
- // 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 );
+ // 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 );
+ }
+
+ positionTooltip();
+
+ // And show the tooltip.
+ scope.tt_isOpen = true;
+ scope.$digest(); // digest required as $apply is not called
+
+ // Return positioning function as promise callback for correct
+ // positioning after draw.
+ return positionTooltip;
}
- positionTooltip();
+ // Hide the tooltip popup element.
+ function hide() {
+ // First things first: we don't show it anymore.
+ scope.tt_isOpen = false;
+
+ //if tooltip is going to be shown after delay, we must cancel this
+ $timeout.cancel( popupTimeout );
+
+ // And now we remove it from the DOM. However, if we have animation, we
+ // need to wait for it to expire beforehand.
+ // FIXME: this is a placeholder for a port of the transitions library.
+ if ( scope.tt_animation ) {
+ transitionTimeout = $timeout(removeTooltip, 500);
+ } else {
+ removeTooltip();
+ }
+ }
- // And show the tooltip.
- scope.tt_isOpen = true;
+ function createTooltip() {
+ // There can only be one tooltip element per directive shown at once.
+ if (tooltip) {
+ removeTooltip();
+ }
+ tooltip = tooltipLinker(scope, function () {});
- // Return positioning function as promise callback for correct
- // positioning after draw.
- return positionTooltip;
- }
-
- // Hide the tooltip popup element.
- function hide() {
- // First things first: we don't show it anymore.
- scope.tt_isOpen = false;
+ // Get contents rendered into the tooltip
+ scope.$digest();
+ }
- //if tooltip is going to be shown after delay, we must cancel this
- $timeout.cancel( popupTimeout );
-
- // And now we remove it from the DOM. However, if we have animation, we
- // need to wait for it to expire beforehand.
- // FIXME: this is a placeholder for a port of the transitions library.
- if ( scope.tt_animation ) {
- transitionTimeout = $timeout(function () {
+ function removeTooltip() {
+ if (tooltip) {
tooltip.remove();
- }, 500);
- } else {
- tooltip.remove();
+ tooltip = null;
+ }
}
- }
- /**
- * Observe the relevant attributes.
- */
- attrs.$observe( type, function ( val ) {
- scope.tt_content = val;
+ /**
+ * Observe the relevant attributes.
+ */
+ attrs.$observe( type, function ( val ) {
+ scope.tt_content = val;
- if (!val && scope.tt_isOpen ) {
- hide();
- }
- });
+ if (!val && scope.tt_isOpen ) {
+ hide();
+ }
+ });
- attrs.$observe( prefix+'Title', function ( val ) {
- scope.tt_title = val;
- });
+ attrs.$observe( prefix+'Title', function ( val ) {
+ scope.tt_title = val;
+ });
- attrs.$observe( prefix+'Placement', function ( val ) {
- scope.tt_placement = angular.isDefined( val ) ? val : options.placement;
- });
+ attrs.$observe( prefix+'Placement', function ( val ) {
+ scope.tt_placement = angular.isDefined( val ) ? val : options.placement;
+ });
- attrs.$observe( prefix+'PopupDelay', function ( val ) {
- var delay = parseInt( val, 10 );
- scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
- });
+ attrs.$observe( prefix+'PopupDelay', function ( val ) {
+ var delay = parseInt( val, 10 );
+ scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
+ });
- var unregisterTriggers = function() {
- if (hasRegisteredTriggers) {
- element.unbind( triggers.show, showTooltipBind );
- element.unbind( triggers.hide, hideTooltipBind );
- }
- };
+ var unregisterTriggers = function() {
+ if (hasRegisteredTriggers) {
+ element.unbind( triggers.show, showTooltipBind );
+ element.unbind( triggers.hide, hideTooltipBind );
+ }
+ };
- attrs.$observe( prefix+'Trigger', function ( val ) {
- unregisterTriggers();
+ attrs.$observe( prefix+'Trigger', function ( val ) {
+ unregisterTriggers();
- triggers = getTriggers( val );
+ triggers = getTriggers( val );
- if ( triggers.show === triggers.hide ) {
- element.bind( triggers.show, toggleTooltipBind );
- } else {
- element.bind( triggers.show, showTooltipBind );
- element.bind( triggers.hide, hideTooltipBind );
- }
+ if ( triggers.show === triggers.hide ) {
+ element.bind( triggers.show, toggleTooltipBind );
+ } else {
+ element.bind( triggers.show, showTooltipBind );
+ element.bind( triggers.hide, hideTooltipBind );
+ }
- hasRegisteredTriggers = true;
- });
+ hasRegisteredTriggers = true;
+ });
- var animation = scope.$eval(attrs[prefix + 'Animation']);
- scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation;
+ var animation = scope.$eval(attrs[prefix + 'Animation']);
+ scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation;
- attrs.$observe( prefix+'AppendToBody', function ( val ) {
- appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody;
- });
+ attrs.$observe( prefix+'AppendToBody', function ( val ) {
+ appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody;
+ });
- // if a tooltip is attached to we need to remove it on
- // location change as its parent scope will probably not be destroyed
- // by the change.
- if ( appendToBody ) {
- scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
- if ( scope.tt_isOpen ) {
- hide();
+ // if a tooltip is attached to we need to remove it on
+ // location change as its parent scope will probably not be destroyed
+ // by the change.
+ if ( appendToBody ) {
+ scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
+ if ( scope.tt_isOpen ) {
+ hide();
+ }
+ });
}
- });
- }
-
- // Make sure tooltip is destroyed and removed.
- scope.$on('$destroy', function onDestroyTooltip() {
- $timeout.cancel( transitionTimeout );
- $timeout.cancel( popupTimeout );
- unregisterTriggers();
- tooltip.remove();
- tooltip.unbind();
- tooltip = null;
- });
+
+ // Make sure tooltip is destroyed and removed.
+ scope.$on('$destroy', function onDestroyTooltip() {
+ $timeout.cancel( transitionTimeout );
+ $timeout.cancel( popupTimeout );
+ unregisterTriggers();
+ removeTooltip();
+ });
+ };
}
};
};