Skip to content

Commit 2ca25a0

Browse files
committed
feat(tooltip): use expression to fix usage with $sce
Allows for trusted resource URLs through Strict Contextual Escaping ($sce). If the an interpolated expression is used instead, then the benefits of SCE is lost. Fixes angular-ui#3558
1 parent 82cb637 commit 2ca25a0

File tree

4 files changed

+45
-17
lines changed

4 files changed

+45
-17
lines changed

Diff for: src/tooltip/docs/demo.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<a href="#" tooltip-animation="false" tooltip="I don't fade. :-(">fading</a>
2121
at elementum eu, facilisis sed odio morbi quis commodo odio. In cursus
2222
<a href="#" tooltip-popup-delay='1000' tooltip='appears with delay'>delayed</a> turpis massa tincidunt dui ut.
23-
<a href="#" tooltip-template="myTooltipTemplate.html">Custom template</a>
23+
<a href="#" tooltip-template="'myTooltipTemplate.html'">Custom template</a>
2424
nunc sed velit dignissim sodales ut eu sem integer vitae. Turpis egestas
2525
</p>
2626

Diff for: src/tooltip/test/tooltip-template.spec.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe('tooltip template', function() {
1717

1818
beforeEach(inject(function($rootScope, $compile) {
1919
elmBody = angular.element(
20-
'<div><span tooltip-template="{{ templateUrl }}">Selector Text</span></div>'
20+
'<div><span tooltip-template="templateUrl">Selector Text</span></div>'
2121
);
2222

2323
scope = $rootScope;
@@ -61,5 +61,19 @@ describe('tooltip template', function() {
6161

6262
expect( elmBody.children().eq(1).text().trim() ).toBe( 'new text' );
6363
}));
64+
65+
it('should hide tooltip when template becomes empty', inject(function ($timeout) {
66+
elm.trigger( 'mouseenter' );
67+
expect( tooltipScope.isOpen ).toBe( true );
68+
69+
scope.templateUrl = '';
70+
scope.$digest();
71+
72+
expect( tooltipScope.isOpen ).toBe( false );
73+
74+
$timeout.flush();
75+
expect( elmBody.children().length ).toBe( 1 );
76+
}));
77+
6478
});
6579

Diff for: src/tooltip/tooltip.js

+28-14
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
1414
var defaultOptions = {
1515
placement: 'top',
1616
animation: true,
17-
popupDelay: 0
17+
popupDelay: 0,
18+
useContentExp: false
1819
};
1920

2021
// Default hide triggers for each show trigger
@@ -65,8 +66,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
6566
* TODO support multiple triggers
6667
*/
6768
this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $document, $position, $interpolate ) {
68-
return function $tooltip ( type, prefix, defaultTriggerShow ) {
69-
var options = angular.extend( {}, defaultOptions, globalOptions );
69+
return function $tooltip ( type, prefix, defaultTriggerShow, options ) {
70+
options = angular.extend( {}, defaultOptions, globalOptions, options );
7071

7172
/**
7273
* Returns an object of show and hide triggers.
@@ -98,8 +99,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
9899
var template =
99100
'<div '+ directiveName +'-popup '+
100101
'title="'+startSym+'title'+endSym+'" '+
101-
'content="'+startSym+'content'+endSym+'" '+
102-
'content-exp="contentExp()" '+
102+
(options.useContentExp ?
103+
'content-exp="contentExp()" ' :
104+
'content="'+startSym+'content'+endSym+'" ') +
103105
'placement="'+startSym+'placement'+endSym+'" '+
104106
'popup-class="'+startSym+'popupClass'+endSym+'" '+
105107
'animation="animation" '+
@@ -188,7 +190,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
188190
}
189191

190192
// Don't show empty tooltips.
191-
if ( ! ttScope.content ) {
193+
if ( !(options.useContentExp ? ttScope.contentExp() : ttScope.content) ) {
192194
return angular.noop;
193195
}
194196

@@ -247,6 +249,14 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
247249
tooltipLinkedScope.$watch(function () {
248250
$timeout(positionTooltip, 0, false);
249251
});
252+
253+
if (options.useContentExp) {
254+
tooltipLinkedScope.$watch('contentExp()', function (val) {
255+
if (!val && ttScope.isOpen ) {
256+
hide();
257+
}
258+
});
259+
}
250260
}
251261

252262
function removeTooltip() {
@@ -274,13 +284,15 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
274284
/**
275285
* Observe the relevant attributes.
276286
*/
277-
attrs.$observe( type, function ( val ) {
278-
ttScope.content = val;
287+
if (!options.useContentExp) {
288+
attrs.$observe( type, function ( val ) {
289+
ttScope.content = val;
279290

280-
if (!val && ttScope.isOpen ) {
281-
hide();
282-
}
283-
});
291+
if (!val && ttScope.isOpen ) {
292+
hide();
293+
}
294+
});
295+
}
284296

285297
attrs.$observe( 'disabled', function ( val ) {
286298
if (val && ttScope.isOpen ) {
@@ -466,14 +478,16 @@ function ($animate , $sce , $compile , $templateRequest) {
466478
return {
467479
restrict: 'EA',
468480
replace: true,
469-
scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&',
481+
scope: { title: '@', contentExp: '&', placement: '@', animation: '&', isOpen: '&',
470482
originScope: '&' },
471483
templateUrl: 'template/tooltip/tooltip-template-popup.html'
472484
};
473485
})
474486

475487
.directive( 'tooltipTemplate', [ '$tooltip', function ( $tooltip ) {
476-
return $tooltip( 'tooltipTemplate', 'tooltip', 'mouseenter' );
488+
return $tooltip('tooltipTemplate', 'tooltip', 'mouseenter', {
489+
useContentExp: true
490+
});
477491
}])
478492

479493
.directive( 'tooltipHtmlPopup', function () {

Diff for: template/tooltip/tooltip-template-popup.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
ng-class="{ in: isOpen() }">
55
<div class="tooltip-arrow"></div>
66
<div class="tooltip-inner"
7-
tooltip-template-transclude="content"
7+
tooltip-template-transclude="contentExp()"
88
tooltip-template-transclude-scope="originScope()"></div>
99
</div>

0 commit comments

Comments
 (0)