diff --git a/src/popover/docs/readme.md b/src/popover/docs/readme.md index 87316ddeee..05b1ecb89d 100644 --- a/src/popover/docs/readme.md +++ b/src/popover/docs/readme.md @@ -3,3 +3,14 @@ directive supports multiple placements, optional transition animation, and more. Like the Twitter Bootstrap jQuery plugin, the popover **requires** the tooltip module. + +The popover directives provides several optional attributes to control how it +will display: + +- `popover-title`: A string to display as a fancy title. +- `popover-placement`: Where to place it? Defaults to "top", but also accepts + "bottom", "left", or "right". +- `popover-animation`: Should it fade in and out? Defaults to "true". +- `popover-popup-delay`: For how long should the user have to have the mouse + over the element before the popover shows (in milliseconds)? Defaults to 0. + diff --git a/src/popover/popover.js b/src/popover/popover.js index 1cee58f851..c38ff9461e 100644 --- a/src/popover/popover.js +++ b/src/popover/popover.js @@ -13,6 +13,6 @@ angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) }; }) .directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) { - return $tooltip( 'popover', 'click' ); + return $tooltip( 'popover', 'popover', 'click' ); }]); diff --git a/src/tooltip/docs/demo.html b/src/tooltip/docs/demo.html index 737d8d5f28..48047c559c 100644 --- a/src/tooltip/docs/demo.html +++ b/src/tooltip/docs/demo.html @@ -16,5 +16,9 @@ at elementum eu, facilisis sed odio morbi quis commodo odio. In cursus delayed turpis massa tincidunt dui ut.

+ +

+ I can even contain HTML. Check me out! +

diff --git a/src/tooltip/docs/demo.js b/src/tooltip/docs/demo.js index e31c64df07..f3a2c73d51 100644 --- a/src/tooltip/docs/demo.js +++ b/src/tooltip/docs/demo.js @@ -1,4 +1,5 @@ var TooltipDemoCtrl = function ($scope) { $scope.dynamicTooltip = "Hello, World!"; $scope.dynamicTooltipText = "dynamic"; + $scope.htmlTooltip = "I've been made bold!"; }; diff --git a/src/tooltip/docs/readme.md b/src/tooltip/docs/readme.md index abc53df636..306bd1f61f 100644 --- a/src/tooltip/docs/readme.md +++ b/src/tooltip/docs/readme.md @@ -1,2 +1,18 @@ A lightweight, extensible directive for fancy tooltip creation. The tooltip directive supports multiple placements, optional transition animation, and more. + +There are two versions of the tooltip: `tooltip` and `tooltip-html-unsafe`. The +former takes text only and will escape any HTML provided. The latter takes +whatever HTML is provided and displays it in a tooltip; it called "unsafe" +because the HTML is not sanitized. *The user is responsible for ensuring the +content is safe to put into the DOM!* + +The tooltip directives provide several optional attributes to control how they +will display: + +- `tooltip-placement`: Where to place it? Defaults to "top", but also accepts + "bottom", "left", or "right". +- `tooltip-animation`: Should it fade in and out? Defaults to "true". +- `tooltip-popup-delay`: For how long should the user have to have the mouse + over the element before the tooltip shows (in milliseconds)? Defaults to 0. + diff --git a/src/tooltip/test/tooltip.spec.js b/src/tooltip/test/tooltip.spec.js index 7539c717fa..fcda8b6997 100644 --- a/src/tooltip/test/tooltip.spec.js +++ b/src/tooltip/test/tooltip.spec.js @@ -163,6 +163,44 @@ describe('tooltip', function() { }); +describe( 'tooltipHtmlUnsafe', function() { + var elm, elmBody, scope; + + // load the tooltip code + beforeEach(module('ui.bootstrap.tooltip', function ( $tooltipProvider ) { + $tooltipProvider.options({ animation: false }); + })); + + // load the template + beforeEach(module('template/tooltip/tooltip-html-unsafe-popup.html')); + + beforeEach(inject(function($rootScope, $compile) { + scope = $rootScope; + scope.html = 'I say: Hello!'; + + elmBody = $compile( angular.element( + '
Selector Text
' + ))( scope ); + scope.$digest(); + elm = elmBody.find('span'); + elmScope = elm.scope(); + })); + + it( 'should show on mouseenter and hide on mouseleave', inject( function () { + expect( elmScope.tt_isOpen ).toBe( false ); + + elm.trigger( 'mouseenter' ); + expect( elmScope.tt_isOpen ).toBe( true ); + expect( elmBody.children().length ).toBe( 2 ); + + expect( elmScope.tt_content ).toEqual( scope.html ); + + elm.trigger( 'mouseleave' ); + expect( elmScope.tt_isOpen ).toBe( false ); + expect( elmBody.children().length ).toBe( 1 ); + })); +}); + describe( '$tooltipProvider', function() { describe( 'popupDelay', function() { diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index 4f36430e3f..1db5654fb7 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -33,23 +33,35 @@ angular.module( 'ui.bootstrap.tooltip', [] ) angular.extend( globalOptions, value ); }; + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name){ + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + /** * Returns the actual instance of the $tooltip service. * TODO support multiple triggers */ this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', function ( $window, $compile, $timeout, $parse, $document ) { - return function $tooltip ( type, defaultTriggerShow, defaultTriggerHide ) { + return function $tooltip ( type, prefix, defaultTriggerShow, defaultTriggerHide ) { var options = angular.extend( {}, defaultOptions, globalOptions ); + var directiveName = snake_case( type ); var template = - '<'+ type +'-popup '+ + '<'+ directiveName +'-popup '+ 'title="{{tt_title}}" '+ 'content="{{tt_content}}" '+ 'placement="{{tt_placement}}" '+ 'animation="tt_animation()" '+ 'is-open="tt_isOpen"'+ '>'+ - ''; + ''; // Calculate the current position and size of the directive element. function getPosition( element ) { @@ -75,19 +87,19 @@ angular.module( 'ui.bootstrap.tooltip', [] ) scope.tt_content = val; }); - attrs.$observe( type+'Title', function ( val ) { + attrs.$observe( prefix+'Title', function ( val ) { scope.tt_title = val; }); - attrs.$observe( type+'Placement', function ( val ) { + attrs.$observe( prefix+'Placement', function ( val ) { scope.tt_placement = angular.isDefined( val ) ? val : options.placement; }); - attrs.$observe( type+'Animation', function ( val ) { + attrs.$observe( prefix+'Animation', function ( val ) { scope.tt_animation = angular.isDefined( val ) ? $parse( val ) : function(){ return options.animation; }; }); - attrs.$observe( type+'PopupDelay', function ( val ) { + attrs.$observe( prefix+'PopupDelay', function ( val ) { var delay = parseInt( val, 10 ); scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; }); @@ -232,6 +244,21 @@ angular.module( 'ui.bootstrap.tooltip', [] ) }) .directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltip', 'mouseenter', 'mouseleave' ); -}]); + return $tooltip( 'tooltip', 'tooltip', 'mouseenter', 'mouseleave' ); +}]) + +.directive( 'tooltipHtmlUnsafePopup', function () { + return { + restrict: 'E', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + }; +}) + +.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter', 'mouseleave' ); +}]) + +; diff --git a/template/tooltip/tooltip-html-unsafe-popup.html b/template/tooltip/tooltip-html-unsafe-popup.html new file mode 100644 index 0000000000..09a5bd506a --- /dev/null +++ b/template/tooltip/tooltip-html-unsafe-popup.html @@ -0,0 +1,4 @@ +
+
+
+