The solutions offered here are pretty bad.
- You cannot use regular expressions because in this way you are searching / highlighting in HTML tags.
- You cannot use regular expressions because it does not work properly with UTF * (anything with non-Latin / English characters).
- You cannot just create innerHTML.replace because it does not work when characters have special HTML notation, for example
& for &, < for <, > for>, ä for ä ö for ö ü for ü ß for ß, etc.
What you need to do:
textContent loop through an HTML document, find all text nodes, get textContent , get the position of the selected text using indexOf (with optional toLowerCase if it's not toLowerCase case), add everything before indexof as textNode , add the appropriate text with a selection range and repeat for the rest of textContent line can occur several times in the line textContent ).
Here is the code for this:
var InstantSearch = { "highlight": function (container, highlightText) { var internalHighlighter = function (options) { var id = { container: "container", tokens: "tokens", all: "all", token: "token", className: "className", sensitiveSearch: "sensitiveSearch" }, tokens = options[id.tokens], allClassName = options[id.all][id.className], allSensitiveSearch = options[id.all][id.sensitiveSearch]; function checkAndReplace(node, tokenArr, classNameAll, sensitiveSearchAll) { var nodeVal = node.nodeValue, parentNode = node.parentNode, i, j, curToken, myToken, myClassName, mySensitiveSearch, finalClassName, finalSensitiveSearch, foundIndex, begin, matched, end, textNode, span, isFirst; for (i = 0, j = tokenArr.length; i < j; i++) { curToken = tokenArr[i]; myToken = curToken[id.token]; myClassName = curToken[id.className]; mySensitiveSearch = curToken[id.sensitiveSearch]; finalClassName = (classNameAll ? myClassName + " " + classNameAll : myClassName); finalSensitiveSearch = (typeof sensitiveSearchAll !== "undefined" ? sensitiveSearchAll : mySensitiveSearch); isFirst = true; while (true) { if (finalSensitiveSearch) foundIndex = nodeVal.indexOf(myToken); else foundIndex = nodeVal.toLowerCase().indexOf(myToken.toLowerCase()); if (foundIndex < 0) { if (isFirst) break; if (nodeVal) { textNode = document.createTextNode(nodeVal); parentNode.insertBefore(textNode, node); } // End if (nodeVal) parentNode.removeChild(node); break; } // End if (foundIndex < 0) isFirst = false; begin = nodeVal.substring(0, foundIndex); matched = nodeVal.substr(foundIndex, myToken.length); if (begin) { textNode = document.createTextNode(begin); parentNode.insertBefore(textNode, node); } // End if (begin) span = document.createElement("span"); span.className += finalClassName; span.appendChild(document.createTextNode(matched)); parentNode.insertBefore(span, node); nodeVal = nodeVal.substring(foundIndex + myToken.length); } // Whend } // Next i }; // End Function checkAndReplace function iterator(p) { if (p === null) return; var children = Array.prototype.slice.call(p.childNodes), i, cur; if (children.length) { for (i = 0; i < children.length; i++) { cur = children[i]; if (cur.nodeType === 3) { checkAndReplace(cur, tokens, allClassName, allSensitiveSearch); } else if (cur.nodeType === 1) { iterator(cur); } } } }; // End Function iterator iterator(options[id.container]); } // End Function highlighter ; internalHighlighter( { container: container , all: { className: "highlighter" } , tokens: [ { token: highlightText , className: "highlight" , sensitiveSearch: false } ] } ); // End Call internalHighlighter } // End Function highlight };
Then you can use it like this:
function TestTextHighlighting(highlightText) { var container = document.getElementById("testDocument"); InstantSearch.highlight(container, highlightText); }
Here is an example HTML document
<!DOCTYPE html> <html> <head> <title>Example of Text Highlight</title> <style type="text/css" media="screen"> .highlight{ background: #D3E18A;} .light{ background-color: yellow;} </style> </head> <body> <div id="testDocument"> This is a test <span> This is another test</span> äöüÄÖÜäöüÄÖÜ <span>Test123äöüÄÖÜ</span> </div> </body> </html>
By the way, if you are looking in a database with LIKE ,
for example, WHERE textField LIKE CONCAT('%', @query, '%') [what you shouldn't do, you should use full-text search or Lucene], then you can escape each character with \ and add an SQL-escape statement that you will find special characters that are LIKE expressions.
eg
WHERE textField LIKE CONCAT('%', @query, '%') ESCAPE '\'
and the value of @query is not '%completed%' but '%\c\o\m\p\l\e\t\e\d%'
(tested, works with SQL server and PostgreSQL and any other RDBMS system that supports ESCAPE)
Revised typescript:
namespace SearchTools { export interface IToken { token: string; className: string; sensitiveSearch: boolean; } export class InstantSearch { protected m_container: Node; protected m_defaultClassName: string; protected m_defaultCaseSensitivity: boolean; protected m_highlightTokens: IToken[]; constructor(container: Node, tokens: IToken[], defaultClassName?: string, defaultCaseSensitivity?: boolean) { this.iterator = this.iterator.bind(this); this.checkAndReplace = this.checkAndReplace.bind(this); this.highlight = this.highlight.bind(this); this.highlightNode = this.highlightNode.bind(this); this.m_container = container; this.m_defaultClassName = defaultClassName || "highlight"; this.m_defaultCaseSensitivity = defaultCaseSensitivity || false; this.m_highlightTokens = tokens || [{ token: "test", className: this.m_defaultClassName, sensitiveSearch: this.m_defaultCaseSensitivity }]; } protected checkAndReplace(node: Node) { let nodeVal: string = node.nodeValue; let parentNode: Node = node.parentNode; let textNode: Text = null; for (let i = 0, j = this.m_highlightTokens.length; i < j; i++) { let curToken: IToken = this.m_highlightTokens[i]; let textToHighlight: string = curToken.token; let highlightClassName: string = curToken.className || this.m_defaultClassName; let caseSensitive: boolean = curToken.sensitiveSearch || this.m_defaultCaseSensitivity; let isFirst: boolean = true; while (true) { let foundIndex: number = caseSensitive ? nodeVal.indexOf(textToHighlight) : nodeVal.toLowerCase().indexOf(textToHighlight.toLowerCase()); if (foundIndex < 0) { if (isFirst) break; if (nodeVal) { textNode = document.createTextNode(nodeVal); parentNode.insertBefore(textNode, node); }
Using:
let searchText = document.getElementById("txtSearchText"); let searchContainer = document.body; // document.getElementById("someTable"); let highlighter = new SearchTools.InstantSearch(searchContainer, [ { token: "this is the text to highlight" // searchText.value, className: "highlight", // this is the individual highlight class sensitiveSearch: false } ]); // highlighter.highlight(); // this would highlight in the entire table // foreach tr - for each td2 highlighter.highlightNode(td2); // this highlights in the second column of table