Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I am currently adding some bootstrap tooltips in my application.

All of the "normal" tooltips are ok, but when I want to use tooltip-html-unsafe, all I got is an empty tooltip.

My tooltip:

<a><span tooltip-placement="right" tooltip-html-safe="{{myHTMLText}}"> Help </span></a>

In the DOM, I have:

<div class="tooltip-inner" ng-bind-html-unsafe="content"></div>

The div's content seems empty, so there is nothing to show in the tooltip. I tried to put directly in the DOM some HTML text like:

<div class="tooltip-inner" ng-bind-html-unsafe="content"><b>test</b></div> and it works.

Do you have an idea?

share|improve this question

2 Answers 2

up vote 6 down vote accepted

The html-unsafe directive is designed to point at it's content. What about this:

<div data-ng-controller="SomeCtrl">
    <span data-tooltip-html-unsafe="{{yourContent}}" data-tooltip-placement="right">
        Help
    </span>
</div>

Then, in SomeCtrl, make a variable to hold the html:

$scope.yourContent = "<b>my html, yay</b>

IF you would like to modify bootstrap to take the content from a element, it can be done like this. First, you must change the tooltip template so that it calls a function to get the html:

angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) {
  $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html",
    "<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
    "  <div class=\"tooltip-arrow\"></div>\n" +
    "  <div class=\"tooltip-inner\" ng-bind-html-unsafe=\"getToolTipHtml()\"></div>\n" +
    "</div>\n" +
    "");
}]);

Then, make a link function for the tooltipHtmlUnsafePopup:

.directive( 'tooltipHtmlUnsafePopup', function () {
  return {
    restrict: 'E',
    replace: true,
    scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
    templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html',
    link: function(scope, element, attrs) {
        scope.getTooltipHtml = function() {
            var elemId = '#' + scope.content;
            var htmlContent = $rootElement.find(elemId).html();
            return htmlContent;
        };
    }
  };
})

EDIT: Later I extracted the customized code from ui-bootstrap, which is good since you do not have to modify ui-bootstrap to use it now. Here is the extracted code, in a module called "bootstrapx". This is just for popovers (as I was not really using tooltips) but I feel like this should be easily adaptable for tooltips too.

angular.module("bootstrapx", ["bootstrapx.tpls","bootstrapx.popover","bootstrapx.popover.dismisser"]);
angular.module("bootstrapx.tpls", ["template/popover/popover-html.html","template/popover/popover-html-unsafe.html","template/popover/popover-template.html"]);


angular.module( 'bootstrapx.popover', [ 'ui.bootstrap.tooltip' ] )
    .directive('popover', [ function() {
        return {
            restrict: 'EA',
            priority: -1000,
            link: function(scope, element) {
                element.addClass('popover-link');
            }
        };
    }])
    .directive('popoverHtml', [ function() {
        return {
            restrict: 'EA',
            priority: -1000,
            link: function(scope, element) {
                element.addClass('popover-link');
            }
        };
    }])
    .directive('popoverHtmlUnsafe', [ function() {
        return {
            restrict: 'EA',
            priority: -1000,
            link: function(scope, element) {
                element.addClass('popover-link');
            }
        };
    }])
    .directive('popoverTemplate', [ function() {
        return {
            restrict: 'EA',
            priority: -1000,
            link: function(scope, element) {
                element.addClass('popover-link');
            }
        };
    }])

    .directive( 'popoverHtmlPopup', [ function() {
        return {
            restrict: 'EA',
            replace: true,
            scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
            templateUrl: 'template/popover/popover-html.html'
        };
    }])
    .directive( 'popoverHtml', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) {
        return $tooltip( 'popoverHtml', 'popover', 'click' );
    }])

    .directive( 'popoverHtmlUnsafePopup', [ '$rootElement', function ( $rootElement ) {
        return {
            restrict: 'EA',
            replace: true,
            scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
            templateUrl: 'template/popover/popover-html-unsafe.html',
            link: function(scope, element) {
                var htmlContent = '';
                scope.$watch('content', function(value) {
                    if (!value) {
                        return;
                    }
                    var elemId = '#' + value;
                    htmlContent = $rootElement.find(elemId).html();
                });

                scope.getPopoverHtml = function() {
                    return htmlContent;
                };
            }
        };
    }])
    .directive( 'popoverHtmlUnsafe', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) {
        return $tooltip( 'popoverHtmlUnsafe', 'popover', 'click' );
    }])

    .directive( 'popoverTemplatePopup', [ '$http', '$templateCache', '$compile', '$parse', function ( $http, $templateCache, $compile, $parse) {
        return {
            restrict: 'EA',
            replace: true,
            scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
            templateUrl: 'template/popover/popover-template.html',
            link: function(scope, element, attrs) {
                scope.getPopoverTemplate = function() {
                    var templateName = scope.content + '.html';
                    return templateName;
                };
            }
        };
    }])
    .directive( 'popoverTemplate', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) {
        return $tooltip( 'popoverTemplate', 'popover', 'click' );
    }]);

    angular.module("template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) {
        $templateCache.put("template/popover/popover-html.html",
            "<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
            "  <div class=\"arrow\"></div>\n" +
            "\n" +
            "  <div class=\"popover-inner\">\n" +
            "      <h3 class=\"popover-title\" ng-bind=\"title\" ng-show=\"title\"></h3>\n" +
            "      <div class=\"popover-content\" ng-bind-html=\"content\"></div>\n" +
            "  </div>\n" +
            "</div>\n" +
            "");
        }]);

    angular.module("template/popover/popover-html-unsafe.html", []).run(["$templateCache", function($templateCache) {
        $templateCache.put("template/popover/popover-html-unsafe.html",
            "<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
            "  <div class=\"arrow\"></div>\n" +
            "\n" +
            "  <div class=\"popover-inner\">\n" +
            "      <h3 class=\"popover-title\" ng-bind=\"title\" ng-show=\"title\"></h3>\n" +
            "      <div class=\"popover-content\" ng-bind-html-unsafe=\"{{getPopoverHtml()}}\"></div>\n" +
            "  </div>\n" +
            "</div>\n" +
            "");
    }]);

    angular.module("template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) {
    $templateCache.put("template/popover/popover-template.html",
            "<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
            "  <div class=\"arrow\"></div>\n" +
            "\n" +
            "  <div class=\"popover-inner\">\n" +
            "      <h3 class=\"popover-title\" ng-bind=\"title\" ng-show=\"title\"></h3>\n" +
            "      <div class=\"popover-content\" ng-include=\"getPopoverTemplate()\"></div>\n" +
            "  </div>\n" +
            "</div>\n" +
            "");
    }]);


    angular.module('bootstrapx.popover.dismisser', [])
        .directive( 'dismissPopovers', [ '$http', '$templateCache', '$compile', '$parse', function ( $http, $templateCache, $compile, $parse) {
            return {
                restrict: 'A',
                link: function(scope, element, attrs) {
                    element.bind('mouseup', function(e) {
                        var clickedOutside = true;
                        $('.popover-link').each(function() {
                            if ($(this).is(e.target) || $(this).has(e.target).length) {
                                clickedOutside = false;
                                return false;
                            }
                        });
                        if ($('.popover').has(e.target).length) {
                            clickedOutside = false;
                        }
                        if (clickedOutside) {
                            $('.popover').prev().click();
                        }
                    });   
                }
            };
        }]);

I have the dismissPopovers directive on the body tag (this is likely applicable for tooltips too, you will just have to modify it to suit your needs):

<body data-ng-controller="AppController" data-dismiss-popovers>
share|improve this answer
    
According to angular-ui's wiki, this is the correct syntax. http://angular-ui.github.io/bootstrap/#/tooltip I tried yours, it didn't worked. –  Mencls Sep 26 '13 at 15:46
    
Oh crap! You are right, I forgot, I modified bootstrap to support that. Normally, it wants the actual content. –  aet Sep 26 '13 at 16:01
    
Thanks for your answer. Where do I have to put the link function ? –  Mencls Sep 26 '13 at 16:39
1  
Ok I found a quicker way to fix it here. But yours was good too ;) –  Mencls Sep 27 '13 at 8:17
1  
@simmi simmi I have edited the answer to include the popover dismisser that I use, hopefully that will help you! –  aet May 21 at 15:12

I have created custom directive which allows html tooltips for bootsrap in very simple way. No need to override any templates:

angular.module('vermouthApp.htmlTooltip', [
])
.directive('vaTooltip', ['$http', '$templateCache', '$compile', '$parse', '$timeout', function ($http, $templateCache, $compile, $parse, $timeout)
{
    //va-tooltip = path to template or pure tooltip string
    //tooltip-updater = scope item to watch for changes when template has to be reloaded [optional (only if template is dynamic)]
    //All other attributes can be added for standart boostrap tooltip behavior (ex. tooltip-placement)
    return {
        restrict: 'A',
        scope: true,
        compile: function (tElem, tAttrs)
        {
            //Add bootstrap directive
            if (!tElem.attr('tooltip-html-unsafe'))
            {
                tElem.attr('tooltip-html-unsafe', '{{tooltip}}');
            }
            return function (scope, element, attrs)
            {
                scope.tooltip = attrs.vaTooltip;
                var tplUrl = $parse(scope.tooltip)(scope);
                function loadTemplate()
                {
                    $http.get(tplUrl, { cache: $templateCache }).success(function (tplContent)
                    {
                        var container = $('<div/>');
                        container.html($compile(tplContent.trim())(scope));
                        $timeout(function ()
                        {
                            scope.tooltip = container.html();
                        });
                    });
                }
                //remove our direcive to avoid infinite loop
                element.removeAttr('va-tooltip');
                //compile element to attach tooltip binding
                $compile(element)(scope);

                if (angular.isDefined(attrs.tooltipUpdater))
                {
                    scope.$watch(attrs.tooltipUpdater, function ()
                    {
                        loadTemplate();
                    });
                } else
                {
                    loadTemplate();
                }
            };
        }
    };
}]);

Thats how you call it

 <a va-tooltip="'tooltipContent.html'" tooltip-updater="item" tooltip-placement="bottom">
                <b><i>{{item.properties.length - propertyShowLimit + ' properties more...'}}</i></b>
            </a>

And template can be like this:

<script id="tooltipContent.html" type="text/ng-template">
    <span ng-repeat="prop in item.properties>
        <b>{{prop.name}}</b>:
        <span ng-repeat="val in prop.values">{{val.value}}&nbsp;</span>
        <br />
    </span>
</script>
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.