12

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?

madth3
  • 7,275
  • 12
  • 50
  • 74
Mencls
  • 329
  • 2
  • 6
  • 15

3 Answers3

23

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>
aet
  • 7,192
  • 3
  • 27
  • 25
  • According to angular-ui's wiki, this is the correct syntax. [http://angular-ui.github.io/bootstrap/#/tooltip](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
  • In the bootstrap code. Find their directive called 'tooltipHtmlUnsafePopup', after the templateUrl, add the link function. You could also duplicate the directive and template, change their names, and have both options available. I chose to modify their directive as I almost always just want to get html content from an element rather than putting html into my controllers. – aet Sep 26 '13 at 16:44
  • 2
    Ok I found a quicker way to fix it [here](https://github.com/angular-ui/bootstrap/commit/08d8b21d63234ccaebbabd98d442b0093c1bfe91). But yours was good too ;) – Mencls Sep 27 '13 at 08:17
  • Thanks Mencls this was the fix I was looking for. – Hcabnettek Nov 22 '13 at 22:14
  • @aet i am using it on page more than one , once i click on any div menu is shown up . again on next div click correspondinmg menu is shown . how can we make it to show only currentlt clicked div's menu . at a time only one pop over should be visible. http://jsfiddle.net/JNqqU/275/ sample – anam May 21 '14 at 09:35
  • 1
    @simmi simmi I have edited the answer to include the popover dismisser that I use, hopefully that will help you! – aet May 21 '14 at 15:12
  • Thanks, working demo http://jsfiddle.net/JNqqU/276/ , Also i tried adding Directive inside anothe so it still works http://jsfiddle.net/JNqqU/277/ great ... – anam May 22 '14 at 06:58
  • @aet i have made it into fiddle ... if u click same popover it will be shown on next click it blinks twice http://jsfiddle.net/JNqqU/278/ – anam May 22 '14 at 07:41
  • @simmi simmi If you notice there are some directives that override the built in ui-bootstrap ones (and all they do is add a class to the element that is triggering the popover). In your case, if you add class='popover-link' to your template with the tag, it should resolve that problem. Fork of your fiddle: http://jsfiddle.net/6TnfQ/ – aet May 22 '14 at 21:27
  • @aet each loop inside dismissPopovers is never executed . i tried checking popver and all . is there any other way ? – anam May 29 '14 at 12:59
6

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>
Adomas.NET
  • 126
  • 1
  • 4
4

There is now buit-in template functionality: https://angular-ui.github.io/bootstrap/#tooltip

<a href="#" uib-tooltip-template="'myTooltipTemplate.html'">Custom template</a>

<script type="text/ng-template" id="myTooltipTemplate.html">
  <span>Special Tooltip with <strong>markup</strong> and {{ dynamicTooltipText }}</span>
</script>
Parris Varney
  • 11,320
  • 12
  • 47
  • 76