diff --git a/src/popover/test/popover.spec.js b/src/popover/test/popover.spec.js index 7e6f2b3970..7a23c75c87 100644 --- a/src/popover/test/popover.spec.js +++ b/src/popover/test/popover.spec.js @@ -2,7 +2,8 @@ describe('popover', function() { var elm, elmBody, scope, - elmScope; + elmScope, + tooltipScope; // load the popover code beforeEach(module('ui.bootstrap.popover')); @@ -20,10 +21,11 @@ describe('popover', function() { scope.$digest(); elm = elmBody.find('span'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; })); it('should not be open initially', inject(function() { - expect( elmScope.tt_isOpen ).toBe( false ); + expect( tooltipScope.isOpen ).toBe( false ); // We can only test *that* the popover-popup element wasn't created as the // implementation is templated and replaced. @@ -32,7 +34,7 @@ describe('popover', function() { it('should open on click', inject(function() { elm.trigger( 'click' ); - expect( elmScope.tt_isOpen ).toBe( true ); + expect( tooltipScope.isOpen ).toBe( true ); // We can only test *that* the popover-popup element was created as the // implementation is templated and replaced. @@ -42,7 +44,7 @@ describe('popover', function() { it('should close on second click', inject(function() { elm.trigger( 'click' ); elm.trigger( 'click' ); - expect( elmScope.tt_isOpen ).toBe( false ); + expect( tooltipScope.isOpen ).toBe( false ); })); it('should not unbind event handlers created by other directives - issue 456', inject( function( $compile ) { diff --git a/src/tooltip/test/tooltip.spec.js b/src/tooltip/test/tooltip.spec.js index 73d2fcba82..91ebb81f26 100644 --- a/src/tooltip/test/tooltip.spec.js +++ b/src/tooltip/test/tooltip.spec.js @@ -2,7 +2,8 @@ describe('tooltip', function() { var elm, elmBody, scope, - elmScope; + elmScope, + tooltipScope; // load the tooltip code beforeEach(module('ui.bootstrap.tooltip')); @@ -20,10 +21,11 @@ describe('tooltip', function() { scope.$digest(); elm = elmBody.find('span'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; })); it('should not be open initially', inject(function() { - expect( elmScope.tt_isOpen ).toBe( false ); + expect( tooltipScope.isOpen ).toBe( false ); // We can only test *that* the tooltip-popup element wasn't created as the // implementation is templated and replaced. @@ -32,7 +34,7 @@ describe('tooltip', function() { it('should open on mouseenter', inject(function() { elm.trigger( 'mouseenter' ); - expect( elmScope.tt_isOpen ).toBe( true ); + expect( tooltipScope.isOpen ).toBe( true ); // We can only test *that* the tooltip-popup element was created as the // implementation is templated and replaced. @@ -42,16 +44,16 @@ describe('tooltip', function() { it('should close on mouseleave', inject(function() { elm.trigger( 'mouseenter' ); elm.trigger( 'mouseleave' ); - expect( elmScope.tt_isOpen ).toBe( false ); + expect( tooltipScope.isOpen ).toBe( false ); })); it('should not animate on animation set to false', inject(function() { - expect( elmScope.tt_animation ).toBe( false ); + expect( tooltipScope.animation ).toBe( false ); })); it('should have default placement of "top"', inject(function() { elm.trigger( 'mouseenter' ); - expect( elmScope.tt_placement ).toBe( 'top' ); + expect( tooltipScope.placement ).toBe( 'top' ); })); it('should allow specification of placement', inject( function( $compile ) { @@ -60,9 +62,10 @@ describe('tooltip', function() { ) )( scope ); scope.$apply(); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; elm.trigger( 'mouseenter' ); - expect( elmScope.tt_placement ).toBe( 'bottom' ); + expect( tooltipScope.placement ).toBe( 'bottom' ); })); it('should work inside an ngRepeat', inject( function( $compile ) { @@ -86,7 +89,9 @@ describe('tooltip', function() { tt.trigger( 'mouseenter' ); expect( tt.text() ).toBe( scope.items[0].name ); - expect( tt.scope().tt_content ).toBe( scope.items[0].tooltip ); + + tooltipScope = tt.scope().$$childTail; + expect( tooltipScope.content ).toBe( scope.items[0].tooltip ); tt.trigger( 'mouseleave' ); })); @@ -136,27 +141,27 @@ describe('tooltip', function() { it( 'should close the tooltip when its trigger element is destroyed', inject( function() { elm.trigger( 'mouseenter' ); - expect( elmScope.tt_isOpen ).toBe( true ); + expect( tooltipScope.isOpen ).toBe( true ); elm.remove(); elmScope.$destroy(); expect( elmBody.children().length ).toBe( 0 ); })); - it('issue 1191 - isolate scope on the popup should always be child of correct element scope', function () { + it('issue 1191 - scope on the popup should always be child of correct element scope', function () { var ttScope; elm.trigger( 'mouseenter' ); - ttScope = angular.element( elmBody.children()[1] ).isolateScope(); - expect( ttScope.$parent ).toBe( elmScope ); + ttScope = angular.element( elmBody.children()[1] ).scope(); + expect( ttScope.$parent ).toBe( tooltipScope ); 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] ).isolateScope(); - expect( ttScope.$parent ).toBe( elmScope ); + ttScope = angular.element( elmBody.children()[1] ).scope(); + expect( ttScope.$parent ).toBe( tooltipScope ); elm.trigger( 'mouseleave' ); }); @@ -171,13 +176,14 @@ describe('tooltip', function() { scope.$digest(); elm = elmBody.find('span'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; })); it('should not open ', inject(function () { elm.trigger('mouseenter'); - expect(elmScope.tt_isOpen).toBeFalsy(); + expect(tooltipScope.isOpen).toBeFalsy(); expect(elmBody.children().length).toBe(1); })); @@ -187,7 +193,7 @@ describe('tooltip', function() { scope.enable = true; scope.$digest(); elm.trigger('mouseenter'); - expect(elmScope.tt_isOpen).toBeTruthy(); + expect(tooltipScope.isOpen).toBeTruthy(); expect(elmBody.children().length).toBe(2); })); @@ -201,33 +207,34 @@ describe('tooltip', function() { 'Selector Text' ))(scope); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; scope.$digest(); })); it('should open after timeout', inject(function ($timeout) { elm.trigger('mouseenter'); - expect(elmScope.tt_isOpen).toBe(false); + expect(tooltipScope.isOpen).toBe(false); $timeout.flush(); - expect(elmScope.tt_isOpen).toBe(true); + expect(tooltipScope.isOpen).toBe(true); })); it('should not open if mouseleave before timeout', inject(function ($timeout) { elm.trigger('mouseenter'); - expect(elmScope.tt_isOpen).toBe(false); + expect(tooltipScope.isOpen).toBe(false); elm.trigger('mouseleave'); $timeout.flush(); - expect(elmScope.tt_isOpen).toBe(false); + expect(tooltipScope.isOpen).toBe(false); })); it('should use default popup delay if specified delay is not a number', function(){ scope.delay='text1000'; scope.$digest(); elm.trigger('mouseenter'); - expect(elmScope.tt_isOpen).toBe(true); + expect(tooltipScope.isOpen).toBe(true); }); }); @@ -247,12 +254,13 @@ describe('tooltip', function() { scope.$apply(); elm = elmBody.find('input'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; - expect( elmScope.tt_isOpen ).toBeFalsy(); + expect( tooltipScope.isOpen ).toBeFalsy(); elm.trigger('focus'); - expect( elmScope.tt_isOpen ).toBeTruthy(); + expect( tooltipScope.isOpen ).toBeTruthy(); elm.trigger('blur'); - expect( elmScope.tt_isOpen ).toBeFalsy(); + expect( tooltipScope.isOpen ).toBeFalsy(); })); it( 'should use it as both the show and hide triggers for unmapped triggers', inject( function( $compile ) { @@ -263,12 +271,13 @@ describe('tooltip', function() { scope.$apply(); elm = elmBody.find('input'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; - expect( elmScope.tt_isOpen ).toBeFalsy(); + expect( tooltipScope.isOpen ).toBeFalsy(); elm.trigger('fakeTriggerAttr'); - expect( elmScope.tt_isOpen ).toBeTruthy(); + expect( tooltipScope.isOpen ).toBeTruthy(); elm.trigger('fakeTriggerAttr'); - expect( elmScope.tt_isOpen ).toBeFalsy(); + expect( tooltipScope.isOpen ).toBeFalsy(); })); it('should only set up triggers once', inject( function ($compile) { @@ -287,16 +296,17 @@ describe('tooltip', function() { var elm2 = elmBody.find('input').eq(1); var elmScope1 = elm1.scope(); var elmScope2 = elm2.scope(); + var tooltipScope2 = elmScope2.$$childTail; scope.$apply('test = false'); // click trigger isn't set elm2.click(); - expect( elmScope2.tt_isOpen ).toBeFalsy(); + expect( tooltipScope2.isOpen ).toBeFalsy(); // mouseenter trigger is still set elm2.trigger('mouseenter'); - expect( elmScope2.tt_isOpen ).toBeTruthy(); + expect( tooltipScope2.isOpen ).toBeTruthy(); })); }); @@ -321,11 +331,12 @@ describe('tooltip', function() { scope.$digest(); elm = elmBody.find('span'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; var bodyLength = $body.children().length; elm.trigger( 'mouseenter' ); - expect( elmScope.tt_isOpen ).toBe( true ); + expect( tooltipScope.isOpen ).toBe( true ); expect( elmBody.children().length ).toBe( 1 ); expect( $body.children().length ).toEqual( bodyLength + 1 ); })); @@ -338,7 +349,7 @@ describe('tooltip', function() { var match = false; angular.forEach(angular.element.cache, function (item) { - if (item.data && item.data.$isolateScope === tooltipScope) { + if (item.data && item.data.$scope === tooltipScope) { match = true; } }); @@ -355,10 +366,10 @@ describe('tooltip', function() { elm = elmBody.find('input'); elmScope = elm.scope(); elm.trigger('fooTrigger'); - tooltipScope = elmScope.$$childTail; + tooltipScope = elmScope.$$childTail.$$childTail; })); - it( 'should not contain a cached reference when visible', inject( function( $timeout ) { + it( 'should not contain a cached reference when not visible', inject( function( $timeout ) { expect( inCache() ).toBeTruthy(); elmScope.$destroy(); expect( inCache() ).toBeFalsy(); @@ -398,7 +409,7 @@ describe('tooltipWithDifferentSymbols', function() { }); describe( 'tooltipHtmlUnsafe', function() { - var elm, elmBody, elmScope, scope; + var elm, elmBody, elmScope, tooltipScope, scope; // load the tooltip code beforeEach(module('ui.bootstrap.tooltip', function ( $tooltipProvider ) { @@ -418,6 +429,7 @@ describe( 'tooltipHtmlUnsafe', function() { scope.$digest(); elm = elmBody.find('span'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; })); it( 'should render html properly', inject( function () { @@ -426,16 +438,16 @@ describe( 'tooltipHtmlUnsafe', function() { })); it( 'should show on mouseenter and hide on mouseleave', inject( function () { - expect( elmScope.tt_isOpen ).toBe( false ); + expect( tooltipScope.isOpen ).toBe( false ); elm.trigger( 'mouseenter' ); - expect( elmScope.tt_isOpen ).toBe( true ); + expect( tooltipScope.isOpen ).toBe( true ); expect( elmBody.children().length ).toBe( 2 ); - expect( elmScope.tt_content ).toEqual( scope.html ); + expect( tooltipScope.content ).toEqual( scope.html ); elm.trigger( 'mouseleave' ); - expect( elmScope.tt_isOpen ).toBe( false ); + expect( tooltipScope.isOpen ).toBe( false ); expect( elmBody.children().length ).toBe( 1 ); })); }); @@ -444,7 +456,8 @@ describe( '$tooltipProvider', function() { var elm, elmBody, scope, - elmScope; + elmScope, + tooltipScope; describe( 'popupDelay', function() { beforeEach(module('ui.bootstrap.tooltip', function($tooltipProvider){ @@ -464,15 +477,16 @@ describe( '$tooltipProvider', function() { scope.$digest(); elm = elmBody.find('span'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; })); it('should open after timeout', inject(function($timeout) { elm.trigger( 'mouseenter' ); - expect( elmScope.tt_isOpen ).toBe( false ); + expect( tooltipScope.isOpen ).toBe( false ); $timeout.flush(); - expect( elmScope.tt_isOpen ).toBe( true ); + expect( tooltipScope.isOpen ).toBe( true ); })); @@ -503,11 +517,12 @@ describe( '$tooltipProvider', function() { scope.$digest(); elm = elmBody.find('span'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; var bodyLength = $body.children().length; elm.trigger( 'mouseenter' ); - expect( elmScope.tt_isOpen ).toBe( true ); + expect( tooltipScope.isOpen ).toBe( true ); expect( elmBody.children().length ).toBe( 1 ); expect( $body.children().length ).toEqual( bodyLength + 1 ); })); @@ -523,13 +538,14 @@ describe( '$tooltipProvider', function() { scope.$digest(); elm = elmBody.find('span'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; elm.trigger( 'mouseenter' ); - expect( elmScope.tt_isOpen ).toBe( true ); + expect( tooltipScope.isOpen ).toBe( true ); scope.$broadcast('$locationChangeSuccess'); scope.$digest(); - expect( elmScope.tt_isOpen ).toBe( false ); + expect( tooltipScope.isOpen ).toBe( false ); })); }); @@ -552,12 +568,13 @@ describe( '$tooltipProvider', function() { scope.$digest(); elm = elmBody.find('input'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; - expect( elmScope.tt_isOpen ).toBeFalsy(); + expect( tooltipScope.isOpen ).toBeFalsy(); elm.trigger('focus'); - expect( elmScope.tt_isOpen ).toBeTruthy(); + expect( tooltipScope.isOpen ).toBeTruthy(); elm.trigger('blur'); - expect( elmScope.tt_isOpen ).toBeFalsy(); + expect( tooltipScope.isOpen ).toBeFalsy(); })); it( 'should override the show and hide triggers if there is an attribute', inject( function ( $rootScope, $compile ) { @@ -570,12 +587,13 @@ describe( '$tooltipProvider', function() { scope.$digest(); elm = elmBody.find('input'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; - expect( elmScope.tt_isOpen ).toBeFalsy(); + expect( tooltipScope.isOpen ).toBeFalsy(); elm.trigger('mouseenter'); - expect( elmScope.tt_isOpen ).toBeTruthy(); + expect( tooltipScope.isOpen ).toBeTruthy(); elm.trigger('mouseleave'); - expect( elmScope.tt_isOpen ).toBeFalsy(); + expect( tooltipScope.isOpen ).toBeFalsy(); })); }); @@ -598,12 +616,13 @@ describe( '$tooltipProvider', function() { scope.$digest(); elm = elmBody.find('input'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; - expect( elmScope.tt_isOpen ).toBeFalsy(); + expect( tooltipScope.isOpen ).toBeFalsy(); elm.trigger('customOpenTrigger'); - expect( elmScope.tt_isOpen ).toBeTruthy(); + expect( tooltipScope.isOpen ).toBeTruthy(); elm.trigger('customCloseTrigger'); - expect( elmScope.tt_isOpen ).toBeFalsy(); + expect( tooltipScope.isOpen ).toBeFalsy(); })); }); @@ -625,12 +644,13 @@ describe( '$tooltipProvider', function() { scope.$digest(); elm = elmBody.find('span'); elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; - expect( elmScope.tt_isOpen ).toBeFalsy(); + expect( tooltipScope.isOpen ).toBeFalsy(); elm.trigger('fakeTrigger'); - expect( elmScope.tt_isOpen ).toBeTruthy(); + expect( tooltipScope.isOpen ).toBeTruthy(); elm.trigger('fakeTrigger'); - expect( elmScope.tt_isOpen ).toBeFalsy(); + expect( tooltipScope.isOpen ).toBeFalsy(); })); }); }); diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index dfed38d87f..8cf1e98df5 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -97,31 +97,32 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap var endSym = $interpolate.endSymbol(); var template = '
'+ '
'; return { restrict: 'EA', - scope: true, compile: function (tElem, tAttrs) { var tooltipLinker = $compile( template ); return function link ( scope, element, attrs ) { var tooltip; + var tooltipLinkedScope; var transitionTimeout; var popupTimeout; var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; var triggers = getTriggers( undefined ); var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + var ttScope = scope.$new(true); var positionTooltip = function () { - var ttPosition = $position.positionElements(element, tooltip, scope.tt_placement, appendToBody); + var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); ttPosition.top += 'px'; ttPosition.left += 'px'; @@ -131,10 +132,10 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // By default, the tooltip is not open. // TODO add ability to start tooltip opened - scope.tt_isOpen = false; + ttScope.isOpen = false; function toggleTooltipBind () { - if ( ! scope.tt_isOpen ) { + if ( ! ttScope.isOpen ) { showTooltipBind(); } else { hideTooltipBind(); @@ -149,11 +150,11 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap prepareTooltip(); - if ( scope.tt_popupDelay ) { + if ( ttScope.popupDelay ) { // Do nothing if the tooltip was already scheduled to pop-up. // This happens if show is triggered multiple times before any hide is triggered. if (!popupTimeout) { - popupTimeout = $timeout( show, scope.tt_popupDelay, false ); + popupTimeout = $timeout( show, ttScope.popupDelay, false ); popupTimeout.then(function(reposition){reposition();}); } } else { @@ -180,7 +181,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap } // Don't show empty tooltips. - if ( ! scope.tt_content ) { + if ( ! ttScope.content ) { return angular.noop; } @@ -200,8 +201,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap positionTooltip(); // And show the tooltip. - scope.tt_isOpen = true; - scope.$digest(); // digest required as $apply is not called + ttScope.isOpen = true; + ttScope.$digest(); // digest required as $apply is not called // Return positioning function as promise callback for correct // positioning after draw. @@ -211,7 +212,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // Hide the tooltip popup element. function hide() { // First things first: we don't show it anymore. - scope.tt_isOpen = false; + ttScope.isOpen = false; //if tooltip is going to be shown after delay, we must cancel this $timeout.cancel( popupTimeout ); @@ -220,7 +221,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // 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 ) { + if ( ttScope.animation ) { if (!transitionTimeout) { transitionTimeout = $timeout(removeTooltip, 500); } @@ -234,7 +235,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap if (tooltip) { removeTooltip(); } - tooltip = tooltipLinker(scope); + tooltipLinkedScope = ttScope.$new(); + tooltip = tooltipLinker(tooltipLinkedScope); } function removeTooltip() { @@ -243,6 +245,10 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap tooltip.remove(); tooltip = null; } + if (tooltipLinkedScope) { + tooltipLinkedScope.$destroy(); + tooltipLinkedScope = null; + } } function prepareTooltip() { @@ -254,26 +260,26 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap * Observe the relevant attributes. */ attrs.$observe( type, function ( val ) { - scope.tt_content = val; + ttScope.content = val; - if (!val && scope.tt_isOpen ) { + if (!val && ttScope.isOpen ) { hide(); } }); attrs.$observe( prefix+'Title', function ( val ) { - scope.tt_title = val; + ttScope.title = val; }); function prepPlacement() { var val = attrs[ prefix + 'Placement' ]; - scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + ttScope.placement = angular.isDefined( val ) ? val : options.placement; } function prepPopupDelay() { var val = attrs[ prefix + 'PopupDelay' ]; var delay = parseInt( val, 10 ); - scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + ttScope.popupDelay = ! isNaN(delay) ? delay : options.popupDelay; } var unregisterTriggers = function () { @@ -297,7 +303,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap prepTriggers(); var animation = scope.$eval(attrs[prefix + 'Animation']); - scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; + ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation; attrs.$observe( prefix+'AppendToBody', function ( val ) { appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; @@ -308,7 +314,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // by the change. if ( appendToBody ) { scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { - if ( scope.tt_isOpen ) { + if ( ttScope.isOpen ) { hide(); } }); @@ -320,6 +326,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap $timeout.cancel( popupTimeout ); unregisterTriggers(); removeTooltip(); + ttScope = null; }); }; }