How to highlight text using javascript

后端 未结 13 2547
名媛妹妹
名媛妹妹 2020-11-22 02:32

Can someone help me with a javascript function that can highlight text on a web page. And the requirement is to - highlight only once, not like highlight all occurrences of

13条回答
  •  无人共我
    2020-11-22 03:13

    The solutions offered here are quite bad.

    1. You can't use regex, because that way, you search/highlight in the html tags.
    2. You can't use regex, because it doesn't work properly with UTF* (anything with non-latin/English characters).
    3. You can't just do an innerHTML.replace, because this doesn't work when the characters have a special HTML notation, e.g. & for &, < for <, > for >, ä for ä, ö for ö ü for ü ß for ß, etc.

    What you need to do:

    Loop through the HTML document, find all text nodes, get the textContent, get the position of the highlight-text with indexOf (with an optional toLowerCase if it should be case-insensitive), append everything before indexof as textNode, append the matched Text with a highlight span, and repeat for the rest of the textnode (the highlight string might occur multiple times in the textContent string).

    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's an example HTML document

    
    
        
            Example of Text Highlight
            
        
        
            
    This is a test This is another test äöüÄÖÜäöüÄÖÜ Test123äöüÄÖÜ

    By the way, if you search in a database with LIKE,
    e.g. WHERE textField LIKE CONCAT('%', @query, '%') [which you shouldn't do, you should use fulltext-search or Lucene], then you can escape every character with \ and add an SQL-escape-statement, that way you'll find special characters that are LIKE-expressions.

    e.g.

    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 every other RDBMS system that supports ESCAPE)


    A revised typescript-version:

    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);
                            } // End if (nodeVal)
    
                            parentNode.removeChild(node);
                            break;
                        } // End if (foundIndex < 0)
    
                        isFirst = false;
    
    
                        let begin: string = nodeVal.substring(0, foundIndex);
                        let matched: string = nodeVal.substr(foundIndex, textToHighlight.length);
    
                        if (begin)
                        {
                            textNode = document.createTextNode(begin);
                            parentNode.insertBefore(textNode, node);
                        } // End if (begin)
    
                        let span: HTMLSpanElement = document.createElement("span");
    
                        if (!span.classList.contains(highlightClassName))
                            span.classList.add(highlightClassName);
    
                        span.appendChild(document.createTextNode(matched));
                        parentNode.insertBefore(span, node);
    
                        nodeVal = nodeVal.substring(foundIndex + textToHighlight.length);
                    } // Whend
    
                } // Next i 
    
            } // End Sub checkAndReplace 
    
    
            protected iterator(p: Node)
            {
                if (p == null)
                    return;
    
                let children: Node[] = Array.prototype.slice.call(p.childNodes);
    
                if (children.length)
                {
                    for (let i = 0; i < children.length; i++)
                    {
                        let cur: Node = children[i];
    
                        // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
                        if (cur.nodeType === Node.TEXT_NODE) 
                        {
                            this.checkAndReplace(cur);
                        }
                        else if (cur.nodeType === Node.ELEMENT_NODE) 
                        {
                            this.iterator(cur);
                        }
                    } // Next i 
    
                } // End if (children.length) 
    
            } // End Sub iterator
    
    
            public highlightNode(n:Node)
            {
                this.iterator(n);
            } // End Sub highlight 
    
    
            public highlight()
            {
                this.iterator(this.m_container);
            } // End Sub highlight 
    
    
        } // End Class InstantSearch 
    
    
    } // End Namespace SearchTools 
    

    Usage:

    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
    

提交回复
热议问题