Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add angularjs ui bootstrap tooltips dynamically to existing markup?

Relatively new to angularjs. Help me understand what's going on here!

What I'm ultimately trying to accomplish: Given a block of text in my html (say in a paragraph element), I want to dynamically add tooltips (bootstrap tooltips to be exact) to selected words in the text. So for example if a user types the world "hello" into a search box, all instances of "hello" in the paragraph will display a tooltip when hovered over, displaying some message like a definition or something.

NOTE: I don't think I was clear on this initially, but the block of text to which I want to add the tooltip is already in the html, and will not have any kind of directive-tag mark-up surrounding it. See my fiddle for an illustration.

I've done this in jQuery...now I'm trying to get it to work in angularjs!

My first attempt was to use a custom filter with a regex which will insert an "a" tag with the tooltip attributes into the paragraph at the appropriate locations. The new markup shows up, but doesn't seem to be "seen" by angularjs (not quite sure of the terminology yet but I think it's not getting "bound"??).

Here's the problem on illustrated on jsfiddle:

http://jsfiddle.net/petersg5/pF33a/2/

(1) The first line in the output has a working tooltip on "foo"...it just has the tooltip attributes directly in markup. Generated html:

<a href="#" tooltip-placement="top" tooltip="basic tooltip" class="ng-scope">foo</a>

(2) The second line uses ng-bind-html, and has the attributes, but not a working tooltip. Generated html:

<a href="#" tooltip-placement="top" tooltip="tooltip via ng-bind-html">foo</a>

(3) The third line uses the filter, and has the attributes, but not a working tooltip. Generated html:

<a href="#" tooltip-placement="top" tooltip="tooltip via filter">foo</a>

My main question is...how to solve the task I described above?

Secondary question is about understanding what's going on in each of the 3 examples above. I noticed that the direct output in (1) has an "ng-scope" class inserted by angular in the generated markup. The other two lack this, but do have an ng-binding class inserted in the parent p tag. Not sure what's going on here but I think it has something to do with my problem.

I have a feeling the solution may involve a directive, but I'm not sure how I would apply that directive to existing text (i.e., a p tag already in the markup).

Thanks!

EDIT: updated the jsfiddle to more accurately reflect the problem (fourth line in output)

like image 834
PeterG Avatar asked Jan 31 '14 21:01

PeterG


2 Answers

The proper way to handle HTML would be angular directive, lets make a directive (say dynamic-tooltip) that takes two parameter

  • tool-tip message
  • your search word

In HTML

  <p dynamic-tooltip="my message" tooltip-element="searchElement">
    Hello World check out my foo bar app
  </p>

The searchElement will be bind with any model like

  <input type="search" ng-model="search">
  <input type="button" value="Search" ng-click="searchElement = search">

Here when you click search button, the value you typed in search box will be set in searchElement

The directive definition is:

app.directive('dynamicTooltip', function($compile) {
    return {
      restrict: 'A',
      scope: {
        tooltipElement: '=',
        dynamicTooltip: '@'
      },
      link: function(scope, element, attrs) {
        var template = '<a href="#" tooltip-placement="top" tooltip="' + scope.dynamicTooltip + '">{{tooltipElement}}</a>';
        scope.$watch('tooltipElement', function(value) {
          var previousTooltip = element.find('a');
          angular.forEach(previousTooltip, function(item, i) {
            var el = angular.element(item);
            el.replaceWith(el.text());
          });
          var searchText = scope.tooltipElement;
          if (searchText) {
            replaced = element.html().replace(new RegExp(searchText, "g"), template);
            element.html(replaced);
          }
          $compile(element.contents())(scope);
        });
      }
    }
  })

The directive $watch tooltip-element, so when you change the value, first it try to remove previous tooltips then it try to match your search-word if found any then create the tooltip.

Check the Demo

like image 153
Tasnim Reza Avatar answered Oct 29 '22 09:10

Tasnim Reza


EDIT:

I think I misunderstood your requirement a bit. Especially after noticing that you use ui-boostrap angular module already.

Problem here, I would think, is that the new attributes you attach are not compiled by Angular, hence the tooltip directive is never run. You need to run the newly attached HTML/DOM through $compile(element)(scope).

Directive example. Please note that this isn't complete, treat it more as pseudocode than anything else, but it should serve as a guideline for how to do it. Basically, you can use whatever jQuery you want inside the directive, important thing to note is that the 'elem' you get in the link function is the same element which the directive is bound to. Also, you need to call $compile(your_element)(scope) on any HTML/DOM you create and attach yourself.

<p tooltip-tag="the_tag" text="My tooltip text">This is my tooltip the_tag test</p> 


myApp.directive('tooltipTag', ['$compile', function($compile) {
    return {
        scope: {
            text: '@' // Create isolate scope as this is a reusable component, we don't want to clutter parent scope
        },
        link: function(scope, elem, attrs) {
            // Elem is here the <p> element, as jqLite/jQuery element.
            // Put whatever logic you want here, feel free to use jQuery if you want
            // I guess you want to copy out the text, search for 'the_tag' (which you can find in 'attrs.tooltipTag') in the text inside the <p></p> and then replace the content.
            // Replace content elem.                
            elem.html(replaced_html);  // Contains text with the_tag replaced with <a href="whatever" tooltip="{{text}}" etc..>the_tag</a>
            var to_compile = elem.children('a');
            // Finally, compile and attach to scope with
            $compile(to_compile)(scope);
        }

    }

}])

This is probably a naive approach, but I don't fully know your requirements. But it should give you an idea of a possible way to solve this.


I think you are approaching the problem the wrong way. There is a general rule when using angular, which is: If you're modifying the DOM, you (almost always) need a directive.

So, what's going wrong here?

I'm not too familiar with Bootstrap's tooltip implementation, but I would assume it never picks up the newly added 'tooltip' attributes. I believe the reason it works in the first example is that tooltip related code search the DOM for tooltip attributes upon page load, but adding it after that is a no go (please correct me if I'm wrong).

So, how can you fix this?

I believe you want to add tooltips dynamically somehow? I think angular-ui bootstrap already have directives supporting this, e.g. ui-tooltip="{{variable.on.scope}}". See link further down.

Or, if you need something else, you have to create your own directive. Directives might be scary at first, but they're (in my opinion) the most important feature of Angular and pretty simple to use once you get the hang of it.

Essentially, when creating a new directive, you can pass in data defining the text you want in the tooltip. Inside the link(scope, elem, attrs) function in your directive, you can attach a tooltip (using Bootstrap's elem.tooltip() function) to the 'elem', being the element the directive applies to.

If you need to attach the <a> element as well, you can use transclusion, which is a bit more advanced.

If you're not familiar with directives, please read up on it and ask if you need further help. Or just check out angular-ui-bootstrap (there are versions for both Bootstrap 3 and 2): http://angular-ui.github.io/bootstrap/

like image 29
SveinT Avatar answered Oct 29 '22 09:10

SveinT