Highlight search terms (select only leaf nodes)
Solution 1:
[See it in action]
// escape by Colin Snover
// Note: if you don't care for (), you can remove it..
RegExp.escape = function(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
function highlight(term, base) {
if (!term) return;
base = base || document.body;
var re = new RegExp("(" + RegExp.escape(term) + ")", "gi"); //... just use term
var replacement = "<span class='highlight'>" + term + "</span>";
$("*", base).contents().each( function(i, el) {
if (el.nodeType === 3) {
var data = el.data;
if (data = data.replace(re, replacement)) {
var wrapper = $("<span>").html(data);
$(el).before(wrapper.contents()).remove();
}
}
});
}
function dehighlight(term, base) {
var text = document.createTextNode(term);
$('span.highlight', base).each(function () {
this.parentNode.replaceChild(text.cloneNode(false), this);
});
}
Solution 2:
Use contents()
1, 2, 3 to get all nodes including text nodes, filter out the non-text nodes, and finally replace the nodeValue
of each remaining text node using regex. This would keep the html nodes intact, and only modify the text nodes. You have to use regex instead of simple string substitutions as unfortunately we cannot do global replacements when the search term is a string.
function highlight(term) {
var regex = new RegExp("(" + term + ")", "gi");
var localRegex = new RegExp("(" + term + ")", "i");
var replace = '<span class="highlight">$1</span>';
$('body *').contents().each(function() {
// skip all non-text nodes, and text nodes that don't contain term
if(this.nodeType != 3 || !localRegex.test(this.nodeValue)) {
return;
}
// replace text node with new node(s)
var wrapped = $('<div>').append(this.nodeValue.replace(regex, replace));
$(this).before(wrapped.contents()).remove();
});
}
We can't make it a one-liner and much shorter easily now, so I prefer it like this :)
See example here.
Solution 3:
I'd give the Highlight jQuery plugin a shot.
Solution 4:
I've made a pure JavaScript version of this, and packaged it into a Google Chrome plug-in, which I wish to be helpful to some people. The core function is shown below:
GitHub Page for In-page Highlighter
function highlight(term){
if(!term){
return false;
}
//use treeWalker to find all text nodes that match selection
//supported by Chrome(1.0+)
//see more at https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker
var treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
null,
false
);
var node = null;
var matches = [];
while(node = treeWalker.nextNode()){
if(node.nodeType === 3 && node.data.indexOf(term) !== -1){
matches.push(node);
}
}
//deal with those matched text nodes
for(var i=0; i<matches.length; i++){
node = matches[i];
//empty the parent node
var parent = node.parentNode;
if(!parent){
parent = node;
parent.nodeValue = '';
}
//prevent duplicate highlighting
else if(parent.className == "highlight"){
continue;
}
else{
while(parent && parent.firstChild){
parent.removeChild(parent.firstChild);
}
}
//find every occurance using split function
var parts = node.data.split(new RegExp('('+term+')'));
for(var j=0; j<parts.length; j++){
var part = parts[j];
//continue if it's empty
if(!part){
continue;
}
//create new element node to wrap selection
else if(part == term){
var newNode = document.createElement("span");
newNode.className = "highlight";
newNode.innerText = part;
parent.appendChild(newNode);
}
//create new text node to place remaining text
else{
var newTextNode = document.createTextNode(part);
parent.appendChild(newTextNode);
}
}
}
}