Thursday, July 7, 2011

Extend jQuery.highlight to highlight regular expressions

The jquery.highlight is a succinct and easy-to-use jQuery plugin to highlight texts. The current version can only highlight literary words case insensitively, but I extended it to highlight regular expressions so that you can highlight things like "foo*bar" as well. jquery.myhighlight-3.js:
/*

highlight v3 - Modified by Marshal (beatgates@gmail.com) to add regexp highlight, 2011-6-24

Highlights arbitrary terms.

<http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html>

MIT license.

Johann Burkard
<http://johannburkard.de>
<mailto:jb@eaio.com>

*/

jQuery.fn.highlight = function(pattern) {
    var regex = typeof(pattern) === "string" ? new RegExp(pattern, "i") : pattern; // assume very LOOSELY pattern is regexp if not string
    function innerHighlight(node, pattern) {
        var skip = 0;
        if (node.nodeType === 3) { // 3 - Text node
            var pos = node.data.search(regex);
            if (pos >= 0 && node.data.length > 0) { // .* matching "" causes infinite loop
                var match = node.data.match(regex); // get the match(es), but we would only handle the 1st one, hence /g is not recommended
                var spanNode = document.createElement('span');
                spanNode.className = 'highlight'; // set css
                var middleBit = node.splitText(pos); // split to 2 nodes, node contains the pre-pos text, middleBit has the post-pos
                var endBit = middleBit.splitText(match[0].length); // similarly split middleBit to 2 nodes
                var middleClone = middleBit.cloneNode(true);
                spanNode.appendChild(middleClone);
                // parentNode ie. node, now has 3 nodes by 2 splitText()s, replace the middle with the highlighted spanNode:
                middleBit.parentNode.replaceChild(spanNode, middleBit); 
                skip = 1; // skip this middleBit, but still need to check endBit
            }
        } else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) { // 1 - Element node
            for (var i = 0; i < node.childNodes.length; i++) { // highlight all children
                i += innerHighlight(node.childNodes[i], pattern); // skip highlighted ones
            }
        }
        return skip;
    }
    
    return this.each(function() {
        innerHighlight(this, pattern);
    });
};

jQuery.fn.removeHighlight = function() {
    return this.find("span.highlight").each(function() {
        this.parentNode.firstChild.nodeName;
        with (this.parentNode) {
            replaceChild(this.firstChild, this);
            normalize();
        }
    }).end();
};
Here is a test file:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<script type="text/javascript" src="lib/jquery-1.6.js"></script>
<script type="text/javascript" src="lib/jquery.myhighlight-3.js"></script>
<style>
.highlight {
    background-color: yellow;
}
</style>
</head>
<body>

<div id="row">
    <div>
        <a class="fullname" href="#"><span class="lastname">Goog</span>, <span class="firstname">Billy</span></a>
        <div id="remove">ServIcE: (click me to remove all highlight)</div>
        <div><span class="section">Pig &amp; Sheep Serv</span>, <span class="department">Cow Administration &amp; Management</span></div>
        <div class="org">Trade Administration, Administration Services</div>
        <div id="email">
            <a class="email" href="mailto:a@abc.com">a@abc.com</a>
        </div>
    </div>
</div>

<script type="text/javascript">
    $(function() {
        $("#row").highlight("service");
        $(".lastname").highlight("o");
        $(".section").highlight(/g.*ep/i);
        $(".department").highlight(/.*/i);
        $(".org").highlight(/adm.*(?=, )/i);
        $("#email").highlight(/a.c\./);
        $("#remove").click(function() {$("#row").removeHighlight();});
    });
</script>

</body>
</html>
And the result:

Here are the links for the source code:
  1. jquery.myhighlight-3.js
  2. jquery.myhighlight-3.min.js

12 comments:

Anonymous said...

The search and highlight will not work if I have the word Sheep as Sheep [with the italics or any other html tag]

marshal said...

If you change one word Sheep to Sh<i>ee</i>p in my above test case, then the 1 text node in .section span was split into 3 text nodes, ie. Sh ee p

However the regexp was not designed to be applied to several discrete text nodes. So in that case you have to select and highlight them one by one.

Ashish Saini said...

In case if '.section' html tag contained a text like 'Sheep' and it was needed to search and highlight Sheep by this regexp '.*', will your function work? For me it is not working. Can you please suggest a way to get it working?

Ashish Saini said...

In case if '.section' html tag contained a text like '< b >Sheep< /b >' and it was needed to search and highlight Sheep by this regexp '< b >.*< /b >', will your function work? For me it is not working. Can you please suggest a way to get it working?

Anonymous said...

Thanks a lot!

Anonymous said...

. (dot) matches everything.

Manuel B. said...

To make it case sensitive, just change this var regex = typeof(pattern) === "string" ? new RegExp(pattern, "i") : pattern; to this var regex = typeof(pattern) === "string" ? new RegExp(pattern) : pattern;

marshal said...

Hi Bülent,

Thanks for your comment. I might not understand your requirements correctly, but this plugin is for developers to programatically/dynamically highlight some text in a web page. If what you need is just highlight some static text in your blog, you can simply define some CSS, for example,
<style>
.highlight {
background-color: yellow;
}
</style>

<span class="highlight">Your text to highlight</span>

As to A+ A-, I saw it's working fine. Not sure what you really want...

Cheers,

Marshal

Anonymous said...

Hi there,

Will this work if I want to highlight words with accents? For example highlight "thé" using "the" in input?

Unknown said...

Thanks for great tool! But it seems the removeHighlight() doesn't work correctly. It removes the highlighted text alongside with the highlight itself. And for some reason this happens only for the first occurance of this text within one dom element.

Anonymous said...

Can you please tell how to highlight if there is nested html in the text. I want to highlight the following phrase.

"My dog is a good dog."

but on the page it appears as

"My dog is a good dog"

If the string My dog wasn't bold then the whole phrase would be highlighted but since it's bold none of the phrase gets highlighted. please help

Julian said...

Why not just use jquery.mark?
https://github.com/julmot/jquery.mark