Highlighter: A MooTools Search & Highlight Plugin

Written by David Walsh on Monday, June 29, 2009


Searching within the page is a major browser functionality, but what if we could code a search box in javascript that would do the same thing? I set out to do that using MooTools and ended up with a pretty decent solution.

The MooTools Javascript Class

var Highlighter = new Class({
			
	/* implements */
	Implements: [Options],

	/* options */
	options: {
		autoUnhighlight: true,
		caseSensitive: false,
		elements: '*',
		className: '',
		onlyWords: false,
		tag: 'span'
	},
	
	/* initialization */
	initialize: function(options) {
		/* set options */
		this.setOptions(options);
		this.elements = $$(this.options.elements);
		this.words = [];
	},
	
	/* directs the plugin to highlight elements */
	highlight: function(words,elements,className) {
		
		/* figure out what we need to use as element(s) */
		var elements = $$(elements || this.elements);
		var klass = className || this.options.className;
		if (words.constructor === String) { words = [words]; }
		
		/* auto unhighlight old words? */
		if(this.options.autoUnhighlight) { this.unhighlight(); }
		
		/* set the pattern and regex */
		var pattern = '(' + words.join('|') + ')';
		pattern = this.options.onlyWords ? '\\b' + pattern + '\\b' : pattern;
		var regex = new RegExp(pattern, this.options.caseSensitive ? '' : 'i');
		
		/* run it for each element! */
		elements.each(function(el) { this.recurse(el,regex,klass); },this);
		
		/* make me chainable! */
		return this;
	}, 
	
	/* unhighlights items */
	unhighlight: function(words) {
		//var selector = this.options.tag + (word ? '[rel=' + word + ']' : '');
		if (words.constructor === String) { words = [words]; }
		words.each(function(word) {
			word = (this.options.caseSensitive ? word : word.toUpperCase());
			if(this.words[word]) {
				var elements = $$(this.words[word]);
				elements.set('class','');
				elements.each(function(el) {
					var tn = document.createTextNode(el.get('text'));
					el.getParent().replaceChild(tn,el);
				});
			}
		},this);
		return this;
	},
	
	/* recursed function */
	recurse: function(node,regex,klass) {
			if (node.nodeType === 3) {
				var match = node.data.match(regex);
				if (match) {
					/* new element */
					var highlight = new Element(this.options.tag);
					highlight.addClass(klass);
					var wordNode = node.splitText(match.index);
					wordNode.splitText(match[0].length);
					var wordClone = wordNode.cloneNode(true);
					highlight.appendChild(wordClone);
					wordNode.parentNode.replaceChild(highlight, wordNode);
					highlight.set('rel',highlight.get('text'));
					var comparer = highlight.get('text');
					if(!this.options.caseSensitive) { comparer = highlight.get('text').toUpperCase(); }
					if(!this.words[comparer]) { this.words[comparer] = []; }
					this.words[comparer].push(highlight);
					return 1;
				}
			} else if ((node.nodeType === 1 && node.childNodes) && !/(script|style)/i.test(node.tagName) && !(node.tagName === this.options.tag.toUpperCase() && node.className === klass)) {
				for (var i = 0; i < node.childNodes.length; i++) {
					i += this.recurse(node.childNodes[i],regex,klass);
				}
			}
			return 0;
		}
	});

The class does provide a few options:

  • autoUnhighlight: (defaults to true) Defines whether or not to auto-unhighlight highlighted words when searched.
  • caseSensitive: (defaults to false) Defines whether the search should be case sensitive.
  • elements: (defaults to '*') Defines which elements are searchable.
  • className: (defaults to '') The class name that will represent the highlighted word class. Gets applied to a span.
  • onlyWords: (defaults to false) Defines whether the class should only find words.
  • tag: (defaults to 'span') Defines the generated element type which will contain the highlighted text.

The class has two main methods:

  • highlight: Highlights the given text. Accepts the words, elements, and classname as parameters.
  • unhighlight: Unhighlights the given text. Accepts words as parameters.

The MooTools Usage

/* sample usage */
window.addEvent('domready',function() {
	
	/* instance */
	var highlighter = new Highlighter({
		elements: '#sample-content li',
		className: 'highlight',
		autoUnhighlight: false
	});
	
	/* submit listener */
	document.id('submit').addEvent('click',function() { if(document.id('search').value) { highlighter.highlight(document.id('search').value); } });
	document.id('submit3').addEvent('click',function() { if(document.id('search3').value) { highlighter.highlight(document.id('search3').value,'*','highlight1'); } });
	document.id('submit2').addEvent('click',function() { if(document.id('search2').value) { highlighter.unhighlight(document.id('search2').value); } });
	
	document.id('search').addEvent('keypress',function(e) { if(e.key == 'enter') { document.id('submit').fireEvent('click'); } });
	document.id('search3').addEvent('keypress',function(e) { if(e.key == 'enter') { document.id('submit3').fireEvent('click'); } });
	document.id('search2').addEvent('keypress',function(e) { if(e.key == 'enter') { document.id('submit2').fireEvent('click'); } });
	
});

What's great is that there are only two functions to use publicly for this class: highlight() and unhighlight().

It's important for me to mention that this class is not perfect! One glaring issue is that if you search for a word, then unhighlight the word, and then look for that word with the next word ("Lorem" => "Lorem ipsum"), the searcher doesn't find the second word due to the way the nodes are in place. If you have a solution to fix that, please let me know. This class was based on http://bartaz.github.com/sandbox.js/jquery.highlight.html.

Happy searching!


Epic Discussion

Commenter Avatar June 29 / #
rborn says:

pretty slick ;)

Commenter Avatar June 29 / #
tlx says:

Really cool!

Commenter Avatar July 03 / #
Alexander says:

Great!!!

Commenter Avatar July 09 / #
Jordan says:

To get rid of the “unhighlight/repeat search” issue, you can just scrub out the highlight class, i.e.

$$(’span’).removeClass(‘highlight1′);

at the start of a search.

Commenter Avatar July 24 / #
haberler says:

i love jquery.

JQUERY IN
MOOTOOLS OUT :)

Commenter Avatar July 30 / #
rodreego says:

Hi David,

your script is really fantastic, but there is an error on it. If the string don’t match in every letters the highlight doesn’t work. Eg.: Lorem Ipsum. If I write Lore Ipsun, or Ipsun, Loren, etc. Don’t return the letters matched. Right? The scripts was that way, or it is an error?

David Walsh July 30 / #

rodreego: That’s a known shortcoming of the script…so far. I’ll be looking to improve it soon.

Commenter Avatar August 12 / #
emse says:

hey david
how can I combine this script with a smoothscroll? I’m an absolute beginner with mootools

David Walsh August 12 / #

What would you be trying to SmoothScroll to? The functionalities are different.

Commenter Avatar August 12 / #
emse says:

i have a large page where every item is posted once. i want the user to type e.g. “Nightwish” and the script should find the word and scroll down to it (and highlight)

David Walsh August 12 / #

I see. You’ll need to implement Events on the class — I’d recommend an “onFind” event that gets fired when a match is found. Then you can direct the element to be scrolled to.

Commenter Avatar December 03 / #
Petar says:

Hi, really great work! :D
I’ve a little suggestion for fix the problem with unhighlithing.
I put in the page an additional form such as

and then something like in the code

if( $(’searched_word’).value )
search.unhighlight( $(‘testo_cercato’).value );
$(’searched_word’).value = words;
search.highlight(words);

where search is a highlighter method, words the searched words taken from an input box. Probably another way wuold be this of searching an element in the page with che highlighter class, taking it value (or text content) and in the same way clearing it from the all page.
Hope it will be useful. thanks again :D

Commenter Avatar December 03 / #
witold says:

hi, i use the following cod to highlight / unhighlight searched word by clicking on a checkbox:

$(‘colorme’).addEvent(‘change’,function() {

if($(‘colorme’).checked==true) {
highlighter.highlight(Cookie.read(’search_value’),’*',’highlight’); }
else { highlighter.unhighlight(Cookie.read(’search_value’)); }

});

for some reason i get the following error

el.getParent() is null

can you tell me what’s wrong

regards, witold

Commenter Avatar January 20 / #

this information most important for me.
thanks…

Be Heard!

I want to hear what you have to say! Share your comments and questions below.

Name*:
Email*:
Website:  


© David Walsh 2007-2010. Contact David Walsh. Powered by the remarkable MooTools javascript framework.