40

I'm trying to write a "suggestion search box" and I cannot find a solution that allows to highlight a substring with javascript keeping the original case.

For example if I search for "ca" I search server side in a case insensitive mode and I have the following results:

Calculator

calendar

ESCAPE

I would like to view the search string in all the previous words, so the result should be:

Calculator

calendar

ESCAPE

I tried with the following code:

var reg = new RegExp(querystr, 'gi');
var final_str = 'foo ' + result.replace(reg, '<b>'+querystr+'</b>');
$('#'+id).html(final_str);

But obviously in this way I loose the original case!

Is there a way to solve this problem?

CroMagnon
  • 1,218
  • 7
  • 20
  • 32
Giovanni Di Milia
  • 13,480
  • 13
  • 55
  • 67

9 Answers9

78

Use a function for the second argument for .replace() that returns the actual matched string with the concatenated tags.

Try it out: http://jsfiddle.net/4sGLL/

reg = new RegExp(querystr, 'gi');
       // The str parameter references the matched string
       //    --------------------------------------v
final_str = 'foo ' + result.replace(reg, function(str) {return '<b>'+str+'</b>'});
$('#' + id).html(final_str);​

JSFiddle Example with Input: https://jsfiddle.net/pawmbude/

Mikhail
  • 11,067
  • 7
  • 28
  • 53
user113716
  • 318,772
  • 63
  • 451
  • 440
  • 13
    Best way to do it, although I would do it without a function: `result.replace(reg, '$&')`. Don't know about performance though, it just seems nicer. – Joost Jul 20 '10 at 21:49
  • I didn't either, but I always forget the `function` syntax so I went looking for some documentation. I found this by 'accident'. – Joost Jul 20 '10 at 21:57
  • Keep in mind that this approach won't work if you want to match the letters wherever they appear in the string, e.g., if the user types `js` and you want to highlight them separately in "**J**ava**S**cript". You'd have to check each letter individually in that case. – jdunning Jan 06 '16 at 20:30
  • 2
    This doesn't take care of proper escaping though. – rosenfeld Jun 20 '17 at 14:41
  • 2
    The above scenario not handle special character skip.[e.g] https://jsfiddle.net/40wfnx3z/ if you search the text "test(123)" it will throw error "Uncaught Syntax Error: Invalid regular expression: /test(/: Unterminated group" and highlight functionality also break.Please add those functionality then this solution will fulfilled answer. – VIJAY P Jul 26 '18 at 07:40
  • To escape special characters, refer to this https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex – rrebase Aug 16 '20 at 20:44
15

ES6 version

const highlight = (needle, haystack) =>
  haystack.replace(
      new RegExp(needle, 'gi'),
      (str) => `<strong>${str}</strong>`
  );
Robin Wieruch
  • 14,900
  • 10
  • 82
  • 107
serby
  • 4,186
  • 2
  • 24
  • 25
1

nice results with

function str_highlight_text(string, str_to_highlight){
   var reg = new RegExp(str_to_highlight, 'gi');
   return string.replace(reg, function(str) {return '<span style="background-color:#ffbf00;color:#fff;"><b>'+str+'</b></span>'});
}

and easier to remember... thx to user113716: https://stackoverflow.com/a/3294644/2065594

Community
  • 1
  • 1
Cyril Jacquart
  • 2,632
  • 3
  • 25
  • 24
1

While the other answers so far seem simple, they can't be really used in many real world cases as they don't handle proper text HTML escaping and RegExp escaping. If you want to highlight every possible snippet, while escaping the text properly, a function like that would return all elements you should add to your suggestions box:

function highlightLabel(label, term) {
  if (!term) return [ document.createTextNode(label) ]
  const regex = new RegExp(term.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'), 'gi')
  const result = []
  let left, match, right = label
  while (match = right.match(regex)) {
    const m = match[0], hl = document.createElement('b'), i = match.index
    hl.innerText = m
    left = right.slice(0, i)
    right = right.slice(i + m.length)
    result.push(document.createTextNode(left), hl)
    if (!right.length) return result
  }
  result.push(document.createTextNode(right))
  return result
}
rosenfeld
  • 1,730
  • 15
  • 19
  • you should at least check the code you are posting. createTextNode is undef (is it document.createTextNode?) b is undef, is it hl ? – gerasalus Aug 30 '19 at 06:29
0

string.replace fails in the general case. If you use .innerHTML, replace can replace matches in tags (like a tags). If you use .innerText or .textContent, it will remove any tags there were previously in the html. More than that, in both cases it damages your html if you want to remove the highlighting.

The true answer is mark.js (https://markjs.io/). I just found this - it is what I have been searching for for such a long time. It does just what you want it to.

ControlAltDel
  • 33,923
  • 10
  • 53
  • 80
-1

I do the exact same thing.

You need to make a copy.

I store in the db a copy of the real string, in all lower case.

Then I search using a lower case version of the query string or do a case insensitive regexp.

Then use the resulting found start index in the main string, plus the length of the query string, to highlight the query string within the result.

You can not use the query string in the result since its case is not determinate. You need to highlight a portion of the original string.

Larry K
  • 47,808
  • 15
  • 87
  • 140
-1

.match() performs case insensitive matching and returns an array of the matches with case intact.

var matches = str.match(queryString),
    startHere = 0,
    nextMatch,
    resultStr ='',
    qLength = queryString.length;

for (var match in matches) {
    nextMatch = str.substr(startHere).indexOf(match);
    resultStr = resultStr + str.substr(startHere, nextMatch) + '<b>' + match + '</b>';
    startHere = nextMatch + qLength;
}
jasongetsdown
  • 1,295
  • 1
  • 17
  • 22
-1

I have found a easiest way to achieve it. JavaScript regular expression remembers the string it matched. This feature can be used here.

I have modified the code a bit.

reg = new RegExp("("+querystr.trim()+")", 'gi');
final_str = 'foo ' + result.replace(reg, "<b>&1</b>");
$('#'+id).html(final_str);
  • can you add some description about the difference between you solution and the accepted answer ? and the utility of the ")" ? – Mimouni Jul 15 '16 at 14:05
-1

Highlight search term and anchoring to first occurence - Start

function highlightSearchText(searchText) {
    var innerHTML = document.documentElement.innerHTML;
    var replaceString = '<mark>'+searchText+'</mark>';
    var newInnerHtml = this.replaceAll(innerHTML, searchText, replaceString);
    document.documentElement.innerHTML = newInnerHtml;
    var elmnt = document.documentElement.getElementsByTagName('mark')[0]
    elmnt.scrollIntoView();
}

function replaceAll(str, querystr, replace) {
    var reg = new RegExp(querystr, 'gi');
    var final_str = str.replace(reg, function(str) {return '<mark>'+str+'</mark>'});
    return final_str
}

Highlight search term and anchoring to first occurence - End

PradeepKN
  • 617
  • 7
  • 10