Highlighter: A MooTools Search & Highlight Plugin
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!
pretty slick ;)
Really cool!
Great!!!
To get rid of the “unhighlight/repeat search” issue, you can just scrub out the highlight class, i.e.
at the start of a search.
i love jquery.
JQUERY IN
MOOTOOLS OUT :)
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?
rodreego: That’s a known shortcoming of the script…so far. I’ll be looking to improve it soon.
hey david
how can I combine this script with a smoothscroll? I’m an absolute beginner with mootools
What would you be trying to SmoothScroll to? The functionalities are different.
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)
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.
hi, i use the following cod to highlight / unhighlight searched word by clicking on a checkbox:
for some reason i get the following error
el.getParent()
is nullcan you tell me what’s wrong
regards, witold
this information most important for me.
thanks…
this seems like a great plugin, judging by the demo, but I just can’t get it working… if I use autoUnhighlight: true, when trying to search I always get the error
words is undefined
in
if (words.constructor === String) { words = [words]; }
which is in the unhighlight function…
in any case, it won’t even search and I don’t think I do anything wrong…
Is there any way for the code to unhighlight the old search automatically when a new search is done?
Hi David,
this can fix the problem when searching for a word with another word after unhighligting itself.