Skip to content

Commit

Permalink
feat(tooltip): use expression to fix usage with $sce
Browse files Browse the repository at this point in the history
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
  • Loading branch information
chrisirhc committed Apr 19, 2015
1 parent 82cb637 commit 2ca25a0
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/tooltip/docs/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<a href="#" tooltip-animation="false" tooltip="I don't fade. :-(">fading</a>
at elementum eu, facilisis sed odio morbi quis commodo odio. In cursus
<a href="#" tooltip-popup-delay='1000' tooltip='appears with delay'>delayed</a> turpis massa tincidunt dui ut.
<a href="#" tooltip-template="myTooltipTemplate.html">Custom template</a>
<a href="#" tooltip-template="'myTooltipTemplate.html'">Custom template</a>
nunc sed velit dignissim sodales ut eu sem integer vitae. Turpis egestas
</p>

Expand Down
16 changes: 15 additions & 1 deletion src/tooltip/test/tooltip-template.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('tooltip template', function() {

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

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

expect( elmBody.children().eq(1).text().trim() ).toBe( 'new text' );
}));

it('should hide tooltip when template becomes empty', inject(function ($timeout) {
elm.trigger( 'mouseenter' );
expect( tooltipScope.isOpen ).toBe( true );

scope.templateUrl = '';
scope.$digest();

expect( tooltipScope.isOpen ).toBe( false );

$timeout.flush();
expect( elmBody.children().length ).toBe( 1 );
}));

});

42 changes: 28 additions & 14 deletions src/tooltip/tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
var defaultOptions = {
placement: 'top',
animation: true,
popupDelay: 0
popupDelay: 0,
useContentExp: false
};

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

/**
* Returns an object of show and hide triggers.
Expand Down Expand Up @@ -98,8 +99,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
var template =
'<div '+ directiveName +'-popup '+
'title="'+startSym+'title'+endSym+'" '+
'content="'+startSym+'content'+endSym+'" '+
'content-exp="contentExp()" '+
(options.useContentExp ?
'content-exp="contentExp()" ' :
'content="'+startSym+'content'+endSym+'" ') +
'placement="'+startSym+'placement'+endSym+'" '+
'popup-class="'+startSym+'popupClass'+endSym+'" '+
'animation="animation" '+
Expand Down Expand Up @@ -188,7 +190,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
}

// Don't show empty tooltips.
if ( ! ttScope.content ) {
if ( !(options.useContentExp ? ttScope.contentExp() : ttScope.content) ) {
return angular.noop;
}

Expand Down Expand Up @@ -247,6 +249,14 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
tooltipLinkedScope.$watch(function () {
$timeout(positionTooltip, 0, false);
});

if (options.useContentExp) {
tooltipLinkedScope.$watch('contentExp()', function (val) {
if (!val && ttScope.isOpen ) {
hide();
}
});
}
}

function removeTooltip() {
Expand Down Expand Up @@ -274,13 +284,15 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
/**
* Observe the relevant attributes.
*/
attrs.$observe( type, function ( val ) {
ttScope.content = val;
if (!options.useContentExp) {
attrs.$observe( type, function ( val ) {
ttScope.content = val;

if (!val && ttScope.isOpen ) {
hide();
}
});
if (!val && ttScope.isOpen ) {
hide();
}
});
}

attrs.$observe( 'disabled', function ( val ) {
if (val && ttScope.isOpen ) {
Expand Down Expand Up @@ -466,14 +478,16 @@ function ($animate , $sce , $compile , $templateRequest) {
return {
restrict: 'EA',
replace: true,
scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&',
scope: { title: '@', contentExp: '&', placement: '@', animation: '&', isOpen: '&',
originScope: '&' },
templateUrl: 'template/tooltip/tooltip-template-popup.html'
};
})

.directive( 'tooltipTemplate', [ '$tooltip', function ( $tooltip ) {
return $tooltip( 'tooltipTemplate', 'tooltip', 'mouseenter' );
return $tooltip('tooltipTemplate', 'tooltip', 'mouseenter', {
useContentExp: true
});
}])

.directive( 'tooltipHtmlPopup', function () {
Expand Down
2 changes: 1 addition & 1 deletion template/tooltip/tooltip-template-popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
ng-class="{ in: isOpen() }">
<div class="tooltip-arrow"></div>
<div class="tooltip-inner"
tooltip-template-transclude="content"
tooltip-template-transclude="contentExp()"
tooltip-template-transclude-scope="originScope()"></div>
</div>

0 comments on commit 2ca25a0

Please sign in to comment.