1

This is my first Angular Directive.

I am trying to do a simple highlight on a html content based on the search terms used to find that content.

The problem is, that is working for the first term, but not for more. I want to all words get highlighted but I am doing something wrong when I replace the HTML content.

This is what the directive tries to do:

1. The directive should highlight one or more words. For example. If the search terms are "document legal" it should highlight both of them, even if they are not on this order.

So, a text like "legal something document" should get both highlighted, "legal" and "document".

2. If the word is less than 3 characters is not going to get highlighted.

3. If the word is not found, try removing the last character from it until its length is less than 3. You may search for "dimensions" and the search engine may return a text containing "dimension" or even "dime".

Just in case, the app is an Ionic App.

This is my code.

The angular directive:

angular.module('starter.directives', [])

.directive('highlightSearchTerms', function($compile) {
  return {
    restrict: 'A',
    scope: true,
    link: function($scope, element, attrs) {
      $scope.highlightTerm = function(term) {
        var html = element.html();

        var highlighted = html.replace(new RegExp(term, 'gi'),
          '<span class="highlightedText">$&</span>');

        if (highlighted == null) {
          return false;
        }

        // @see
        // I think that the problem is here, it works the 
        // first time, but the second time it gets here
        // the following exception is throwed
        // "Cannot read property 'replaceChild' of null"
        element.replaceWith(highlighted);

        return html != highlighted;
      };

      var searchTerms = $scope.searchTerms;

      if (searchTerms != undefined && searchTerms.length < 3) {
        return;
      }

      var terms = searchTerms.split(' ');

      // Try to highlight each term unless the word
      // is less than 3 characters
      for (var i = 0; i < terms.length; i++) {
        var term = terms[i];

        // // Try to highlight unless the word is less than 3 chars
        while (term.length > 2) {
          // If it got highlighted skip to next term
          // else remove a character from the term and try again
          if ($scope.highlightTerm(term)) {
            break;
          }

          term = term.substring(0, term.length - 1);
        }
      }
    }
  };
});

You can see some weird things. Like using $scope.highlightTerm instead of passing the highlightTerm var to the directive. I couldn't get it work.

How can I change the HTML of the element correctly?

This is the template that is using the directive:

<div ng-include src="tplName" highlight-search-terms></div>

I wish to do something like that but I couldn't get it working:

<div ng-include src="tplName" highlight-search-terms="something to highlight"></div>

Here is a Plunker: http://plnkr.co/edit/BUDzFaTnxTdKqK5JfH0U?p=preview

3
  • Putting this into a jsFiddler or plunker may help. Also ive not done it myself so can't comment with certainty but be careful editing the HTML without using angularjs $compile service. I'm pretty certain you can break any bindings you may have in that HTML using just jquery. Commented Oct 16, 2015 at 9:23
  • Thank you for the plunker suggestion. And the $compile service, I am going to check it out. Commented Oct 16, 2015 at 9:35
  • ste2425 is right your problem is that var html = element.html(); always returns the same value since you're not recompiling after the replacement. Punov's solution works but it uses native Javascript to bypass angular, I don't know if we always want that. Commented Oct 16, 2015 at 10:26

3 Answers 3

1

I think your code is working, but the issue was that you are trying to replace the whole div that is using the directive. So what you can do is just replace element.replaceWith(highlighted); with element.html(highlighted); and it will work.

I wish to do something like that but I couldn't get it working: <div ng-include src="tplName" highlight-search-terms="something to highlight"></div>

You already there, just use attrs in the link function like so:

var terms = attrs.highlightSearchTerms;, and you will get what you passed in highlight-search-terms="something to highlight"

Sign up to request clarification or add additional context in comments.

1 Comment

Yes! I knew that it should be something like this. Thank you very much.
1

This should work for you, with using of 'compile' function:

angular.module('starter.directives', [])

.directive('highlightSearchTerms', function($compile) {
  return {
    restrict: 'A',
    scope: true,
    compile: function(elem, attrs) {
        // your code
        elem[0].innerHTML = '<span class="highlightedText">$&</span>';
        // your code
    }
  };
});

Documentation also could help.

Comments

1

Even tough punov's solution works, I think you shouldn't trigger multiple re-compiles for a single "line". I would suggest storing the html in a variable and recompile after every term was replaced.

Here is a working example - but it needs some polishing.

http://plnkr.co/edit/3zA54A0F2gmVhCComXAb?p=preview

link: function($scope, element, attrs) {

  var searchTerms = $scope.searchTerms;
  var terms = searchTerms.split(' ');
  $scope.highlightedHTML = element.html();

  if (searchTerms !== undefined && searchTerms.length < 3) {
    return;
  }

  $scope.highlightTerm = function(term) {
    console.log("html - ", term, html);
    var highlighted =  $scope.highlightedHTML.replace(new RegExp(term, 'gi'),
      '<span class="highlightedText">$&</span>');

    //element.replaceWith(highlighted);

    return highlighted;
  };

  function highlight(terms, compile) {
    // Try to highlight each term unless the word
    // is less than 3 characters
    // if the term is not highlighted remove one character
    // from it and try again
    for (var i = 0; i < terms.length; i++) {
      var term = terms[i];

      while (term.length > 2) {
        var current = $scope.highlightedHTML;
         $scope.highlightedHTML = $scope.highlightTerm(term);


        if (current !==  $scope.highlightedHTML) {
          break;
        }

        term = term.substring(0, term.length - 1);
      }
    }

    compile();
  }



  highlight(terms, function() {
     element.replaceWith( $scope.highlightedHTML);
  });
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.