2

I am trying to change the color of multiple texts to a certain color by using this code:

var search = "bar";
$("div:contains('"+search+"')").each(function () {
    var regex = new RegExp(search,'gi');
    $(this).html($(this).text().replace(regex, "<span class='red'>"+search+"</span>"));
});

However, the code does not work a second time, and I am not sure why--it only changes the newest occurrence of the code.

Here is a JSFiddle using it twice where it is only changing the 2nd occurrence: http://jsfiddle.net/PELkt/189/

Could someone explain why it does not work on the 2nd occurrence?

  • May I say the important thing here is you have a loop/each that is unneeded, see my example below to see how to do it without that loop which is causing your issue – AlphaG33k Mar 17 '16 at 02:12

4 Answers4

2

Could someone explain why it does not work on the 2nd occurrence?

By calling .text() you are removing all the HTML markup, including the <span>s you just inserted.

This is the markup after the first replacement:

<div id="foo">this is a new <span class='red'>bar</span></div>

$(this).text() will return the string "this is a new bar", in which replace "new" with a <span> ("this is a <span class='red'>new</span> bar") and set it as new content of the element.

In order to do this right, you'd have to iterate over all text node descendants of the element instead, and process them individually. See Highlight a word with jQuery for an implementation.

Community
  • 1
  • 1
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
1

Use $(this).html() instead of $(this).text(), as $.fn.text() strips off all the html tags, so are the <span class="red">foo</span> stripped off to foo.

But let's say that you apply same highlight multiple times for foo, then I would suggest that you should create a class similar to this to do highlighting

var Highlighter = function ($el, initialArray) {
  this._array = initialArray || [];
  this.$el = $el;
  this.highlight = function (word) {
    if (this.array.indexOf(word) < 0) {
      this.array.push(word);
    }
    highlightArray();
  }
  function highlightArray() {
    var search;
    // first remove all highlighting
    this.$el.find("span[data-highlight]").each(function () {
      var html = this.innerHTML;
      this.outerHTML = html;
    });
    // highlight all here
    for (var i = 0; i < this._array.length; i += 1) {
      search = this._array[i];
      this.$el.find("div:contains('"+search+"')").each(function () {
        var regex = new RegExp(search,'gi');
        $(this).html($(this).html().replace(regex, "<span data-highlight='"+search+"' class='red'>"+search+"</span>"));
      });
    }
  }
}

var highlighter = new HighLighter();
highlighter.highlight("foo");
Ammar Hasan
  • 2,436
  • 16
  • 22
  • This could insert `` tags at invalid positions in the HTML. That's an unreliable solution. – Felix Kling Mar 17 '16 at 01:52
  • @FelixKling, true that, in that case he'll have to remove all such nodes first and then reapply this. I'll update my answer for that. – Ammar Hasan Mar 17 '16 at 01:53
  • How does `var html = this.innerHTML; $(this).html(html);` remove the highlighting? Also, this seems to have the same problems as the OPs initial code... did you test it? – Felix Kling Mar 17 '16 at 02:11
  • nopes didn't test that, was just giving a rough idea, but what you have pointed out is correct, that shoud be `var html = this.innerHTML; this.outerHTML = html;` instead. Thanks for pointing it out :) – Ammar Hasan Mar 17 '16 at 03:55
1

It was easy to fix your jsfiddle. Simply replace both .text() with .html() & you'll see that it highlights new & both bars in red.

jQuery's .text() method will strip all markup each time that it's used, but what you want to do is use .html() to simply change the markup which is already in the DOM.

$(document).ready(function () {
    var search = "bar";
    $("div:contains('"+search+"')").each(function () {
        var regex = new RegExp(search,'gi');
        $(this).html($(this).html().replace(regex, "<span class='red'>"+search+"</span>"));
    });

    search = "new";
    $("div:contains('"+search+"')").each(function () {
        var regex = new RegExp(search,'gi');
        $(this).html($(this).html().replace(regex, "<span class='red'>"+search+"</span>"));
    });
});
Clomp
  • 3,168
  • 2
  • 23
  • 36
  • 1
    Why did somebody downvoted this correct answer? I upvote to compensate. – cFreed Mar 17 '16 at 01:55
  • @cFreed: This answer fails to point the drawbacks of processing the raw HTML. – Felix Kling Mar 17 '16 at 01:57
  • @FelixKling I don't understand which drawbacks you point. An explanation seems to appear in your comment to the Ammar Hasan's answer, but I still don't understand: why could ``tags inserted at invalid positions? TIA for your clarification. – cFreed Mar 17 '16 at 02:06
  • 1
    @cFreed: Imagine the search word is "class" or "span" or "id" or "red" or any other HTML attribute name / attribute value. By applying the replace to the HTML string, it applies to both, the HTML markup and the data. – Felix Kling Mar 17 '16 at 02:07
  • @FelixKling Oh... I see, and I agree. Thanks. – cFreed Mar 17 '16 at 02:10
  • Yes it does. The HTML tags can be broken & appear on the page, when highlighting a search for "spa" instead of "hot tub". When you get to that level, you might want to consider using a variable that contains sanitized text, which will eventually be dropped onto the page. Like var results = {...jsonSearchResultsData...}; If span tags are injected into the data stream before rendering it to the DOM with 1 .html() call, then the problem of highlighting attributes would be eliminated. The JS code would no longer use the DOM as the model, but rather the view. – Clomp Mar 17 '16 at 17:01
  • It's also a really good issue to mention, but it it's not the original Q. It's a side tangent, which stems from the conversation that applied to highlighting multiple keywords in the search results. It is good to keep in mind, but it didn't deserve the original downvote. Thanks for the upvote cFreed! :) – Clomp Mar 17 '16 at 17:08
1

Here is another way of doing it that will allow you to continue using text if you wish

function formatWord(content, term, className){
        return content.replace(new RegExp(term, 'g'), '<span class="'+className+'">'+term+'</span>');
    }

$(document).ready(function () {

    var content = $('#foo').text();


  var change1 = formatWord(content, 'bar', 'red'),
        change2 = formatWord(change1, 'foo', 'red');

    alert(change2);

    $('body').html(change2);
});

http://codepen.io/nicholasabrams/pen/wGgzbR?editors=1010

AlphaG33k
  • 1,588
  • 1
  • 12
  • 24