Video icon 64
Learning to code? Skill up faster with our practical video courses. Start your free trial today.
Advertisement

Easily Implement a Live Search Effect

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

The live search effect is no novelty in desktop apps. In Mac OS X or Windows Vista/7, just type a few letters in a search box, almost instantly you get the search result on the fly. Having to hit the submit button is becoming old school.

This tutorial will show you how to bring this cool effect to the web world with jQuery. The code used in this tutorial is modified based on John Resig's Approach.


Livesearch Effect in Windows 7


Livesearch Effect in OS X


Features


  • Presents search result on the fly
  • Fuzzy Search
  • Super Fast, No AJAX calls

Step 1: The HTML

To get started, we need a few html elements including:

  • A text input, in which we type our queries.
  • An unordered list, to show the search result

... and two javascript libraries:

  • jQuery: You'll need to at least know this to complete this tutorial.
  • Quicksilver Score: This wonderful script mimics the famous Quicksilver livesearch algorithm on Mac. It tells whether two text strings are relevant, and how relevant. It's like a mini google in one js file. Users all like fuzzy search, right? And they all like search results show up from the most relevant to the least. Thanks to Lachie Cox's great work!

Last but not least, we need some items to search from. In this tutorial, let's use the names of many web frameworks as dummy data, OK? (See the list of these frameworks)

The "search-from" data can be provided in arrays, JSONs or any form, as long as they can be looped through in javascript. Multi-dimensional arrays can be used too. But for ease of demonstration and understanding, data used in this tutorial is a simple one-dimensional array called "items".

And finally, here's the HTML. Pay attention to where the two JS libraries get included. Note: css is not the key part of this effect, so we won't spend any second on styling. They're minified. Hope this won't bother or distract you. Let's focus on HTML and JS.

Save the following code to an html file, and let's start!

 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> 
<html> 
	<head> 
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 
		<title>jQuery Livesearch Effect</title> 
		<style>  
			*{font-family:Tahoma,Geneva,sans-serif}body{background:#175082}h1{color:#069;font-size:40px;font-family:Georgia,"Times New Roman",Times,serif;line-height:50px;padding-bottom:10px;vertical-align:top;border-bottom:1px dashed#09F}h4{color:#000;font-size:20px;font-weight:normal}div#all{width:700px;position:relative;margin:40px auto;border:5px solid#09F;background:#DDF2FF;padding:0px 25px 50px 25px}input#q{width:300px;height:30px;line-height:30px;font-size:26px;border:2px solid#CCC;padding-left:5px}span#tip{font-size:12px;line-height:30px;margin-left:15px;color:#F30;font-weight:bold;margin-bottom:10px}ul#results li{color:#666;line-height:1.7em} 
		</style> 
	</head> 
	<body> 
		<div id="all"> 
			<h1>jQuery Livesearch Demo</h1> 
			<h4>Find Your Favourite Web Framework</h4> 
			<input id="q" /> 
			<span id="tip"><-- Type in here and get search results LIVE!</span> 
			<ul id="results"></ul> 
		</div> 
		 
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script> 
<script type="text/javascript" src="http://rails-oceania.googlecode.com/svn/lachiecox/qs_score/trunk/qs_score.js"></script> 
<script> 
//	A placeholder for the real code 
</script> 
<script> 
	var items = ["Agavi","AIDA Web","Akelos","Apache Click","Apache Cocoon","Apache Struts","Apache Wicket","AppFuse","Aranea","ASP.NET MVC","Axiom Stack","BFC","CakePHP","DooPHP","Camping","Catalyst","CherryPy","CodeIgniter","ColdSpring","Csla","CppCMS","CubicWeb","Django","Drupal","DotNetNuke","eZ Components","Flex","FUSE","Fusebox","Google Web Toolkit","Grok","Grails","Hamlets","Helix","Horde","Interchange","ItsNat","IT Mill Toolkit","JavaServer Faces","JBoss Seam","Kepler","Kohana","KumbiaPHP","Lift","LISA","ManyDesigns Portofino","Mason","Maypole","Mach-II","Merb","Midgard","Model-Glue","MonoRail","Morfik","Nitro","onTap","OpenACS","OpenLaszlo","OpenXava","Orbit","Orinoco","PEAR","PHP For Applications","PHP Work","Pyjamas","Pylons","Qcodo","RIFE","Ruby on Rails","Samstyle","Seaside","Shale","SilverStripe (Sapphire)","Simplicity","Sinatra","SmartClient","Sofia","SPIP","Spring","Stripes","Symfony","Tapestry","ThinWire","Tigermouse","Vaadin","TurboGears","Wavemaker","web2py","WebObjects","WebWork","Wigbi","Yii","Zend","ZK","Zoop","Zope 2","Zope 3","ztemplates"]; 
</script>		 
	</body> 
</html>

Step 2: Firebug is our best friend!

As a javascript guy, you can't live without Firebug today. It's definitely a must for anyone working with jQuery. You can complete this tutorial without firebug installed. But with firebug, we are able to see a lot of behind-the-scene stuff, which is essential to understand the search thing in this effect.

Remember the dummy items to search from? Yes, the framework names. Let's check if they're in the right place with Firebug:

  1. Open the HTML file with Firefox.
  2. Open Firebug, and switch to "Console" panel (ctrl+shift+c).
  3. Type "items"(without quotes) in the box on the right, and hit "Run".

You should see something shown in the image below. Firebug prints out the "items" variable which is an array. Yes, our framework names are right there in place. Let's go on!



Step 3: The Logic

Now, let's talk about the logic. To get this effect working, a sequence of things need to occur:

  1. User hits a key.
  2. We get the text of the search box. Let's called this "query".
  3. To each framework name, we check if the query is relevant, and how relevant it is.
  4. We filter out relevant framework names and sort them based on relevance.
  5. We present the result to the user.

Easy, hah? Except for one thing. If I search for a framework called "cake", the above logic runs, one cycle per letter, 4 cycles in total, right? But the first 3 cycles are completely unnecessary, only to reduce performance in a big way. To mitigate this, we'll wait until the user stops keying before firing our logic. And if a user stops keying for 0.2 seconds, we assume he finishes. To this point, our logic is delicate enough. It's time to get your hands dirty and starting coding!


Step 4: Event Listening

In real life, listening can be extremely important. This is also true in jQuery's world, where everything starts from listening to an event.

Search for "placeholder" in the HTML file. Put the following code into that place.

The following code listens to the keyup event. Throw them in between the <script> tag and we'll go through a few keypoints.

 
$(function(){ 
	var t = null; 
	$("#q").keyup(function(){ 
		if (t) { 
			clearTimeout(t); 
		} 
		t = setTimeout("filter()", 200); 
	}); 
}); 
 
function filter(){ 
//	Filter logic comes here 
}
  1. The code executes when document is ready.
  2. All our logics start from the keyup event.
  3. Remember we'll wait 0.2s to see if the user finishes typing? Here we use two functions to realize this feature: setTimeout() and clearTimeout(). What setTimeout() does is wait for 200ms (the second paramter) before executing a function (the first parameter). clearTimeout() just tells setTimeout(), hey, no more waiting, just cancel yourself. If you're not familiar with these functions, these tutorials are great. If this part doesn't instantly make sense to you, don't worry. Go through those tutorials and take some time here.
  4. The "filter()" function calculates and sorts the search result for us. We'll code it in the next step.

Note: setTimeout() is a native js function. Its first parameter must have the quotes wrapped around the function name, significantly different from jQuery's conventions.

So, the filter() function is fired when the user stops keying for 0.2 seconds. Now we're ready to go even further with the filter() function.


Step 5: Calculating Search Results

Now we've come to the core of this effect: search logic, which loops over all framework names and calculates the relevance.

Put the following code in the place of the filter() function. And we'll make some explanations.

 
function filter(){ 
	var q = $("#q").val().toLowerCase(); 
	 
	scores = []; 
	 
	if (q.length == 0) { 
		$("#results").html(""); 
	} else { 
		$.each(items, function(){ 
			var score = this.toLowerCase().score(q); 
			 
			if (score > 0) { 
				scores.push([score, this+""]); 
			} 
		}); 
	} 
}
  1. Line 2 shouldn't bother you too much. Just get the text in the search box and convert it to lowercase.
  2. Line 4 creates an empty array called "scores", which is used to store search result and relevance(or a score between the query and a framework name).
  3. Line 6-8 checks if the query is empty (length = 0). If the query's empty, we don't even need to calculate anything. Just clear the search result area.
  4. From Line 9, we see some real stuff. The array "items" carries all framework names. Those are what users search from.
  5. Line 9 loops "items" with $.each function. If you're a PHP guy, this is similar to foreach().
  6. Line 10 is in the loop. "score" (not "scores") stores the relevance between the query and the current item (a framework name). "this" here refers to the current item of the loop, in other words, a framework name. We turn it to lowercase as well. And look at the score() function. It is defined in the Quicksilver Score script and calculate the relevance for us.
  7. Line 12 checks if the score (relevance) is zero. A zero relevance means the two terms are not relevant at all. So we simply ignore them and keep only relevant results.
  8. Line 13 appends a framework name with its relevance score altogether to the "scores" array.

The Quicksilver Score algorithm is case-sensitive and doesn't seem to believe "A" has anything to do with "a". Make sure to convert texts to lowercase if that's what you want.

To this point, if you type in "php" in the search box and run the "scores" variable in Firebug, you'll get the result shown in this image.


Well, I know it's not the best looking format in the world, but let's try to read it. Look at the decimal numbers. Those are the relevance score which is followed by the item name.

Unbelievable, Hah? But there's still one problem. Look closer: CakePHP, scored 0.42 comes before DooPHP (0.5) and even PHP Work(0.93)! That's not the right order! We need to fix this in the next step.


Step 6: Sorting Search Results

Finally, here's the ultimate filter() function. Let's take a closer look.

 
function filter(){ 
	var q = $("#q").val().toLowerCase(); 
	 
	scores = []; 
	 
	if (q.length == 0) { 
		$("#results").html(""); 
	} else { 
		$.each(items, function(){ 
			var score = this.toLowerCase().score(q); 
			 
			if (score > 0) { 
				scores.push([score, this+""]); 
			} 
		}); 
		if (scores.length) { 
			$("#results").html(""); 
			$.each(scores.sort(function(a, b){return b[0] - a[0];}), function(i){ 
				var entry = "<li>" + this[1] +"</li>"; 
				$("#results").append(entry); 
			}); 
		} else { 
			$("#results").html(""); 
		} 
	} 
}

It's time to sort our search result and present it to our users!

  1. Line 16 checks if "scores", our search result container, is empty. If the result is empty, we just clear the results DOM in HTML, or if you like, print some sorries to the user.
  2. We're definitely happier if the search result is not empty. In this case, let's clear the results DOM in Line 17 to make some room for printing out the results.
  3. In Line 18, we see some old friend $.each, the iterator. And with the js native function sort(), we manage to sort search results by their revelance. If you're curious about how this works, check out this doc.
  4. Line 19 generates the HTML code to be appended in the results DOM. To make it simple, I just add an LI here. In real projects, feel free to write more complex HTML here.
  5. Finally, Line 20 brings what was behind the scene to the front

If you run the "scores" variable again in Firebug, you'll see that it's been sorted based on relevance score.



Conclusion

The code used in this tutorial is simplified for ease of demostration. To implement this in a real world project, consider the following:

  • Add initial and not-found markups for results DOM
  • Use a multi-dimensional array as the search pool
  • Implement more complicated HTML for search result output

The approach introduced in this tutorial is based on John Resig's Livesearch Plugin, which takes a set of HTML LIs as input. (We take arrays and JSONs as input, making the approach much more flexible.) And thanks to Lachie Cox's text relevance ranking algorithm as well.


Advertisement