RSS

Spotlight-Like Search As You Type With CSS, AJAX & JS

OS X has a lot of great effects. These are great building blocks for creating effects for the web. You might have seen my previous tutorial on creating a Dashboard-Like effect using JS and CSS. This tutorial will show you how to create a Spotlight-Like effect for your search boxes. Take a look at the picture to the right to see where we are headed:

You can preview the finished effect here. This example will search through 3 text files of lists of names of celebrities, athletes, and presidents. In most real world situations, you would be searching inside databases, but for this preview I used text files. I have also zipped up the necessary files and put them here. This zip contains all required images, CSS files, HTML files, and javascript files.

spotlight_search.png

What we need to do

  • Create the HTML markup
  • Style a bit with CSS
  • Write our Javascript code
  • Write the PHP code to return the search results
  • Finish up the CSS
  • Tie everything together

I should note here that we are going to use PHP, but any scripting language will do.

Create the HTML markup

We’re first going to start by laying out our HTML. This is a really simple step. Inside your head tag, put:

<script src="scriptaculous/lib/prototype.js" type="text/javascript"><!--mce:1--></script>
<script src="scriptaculous/src/scriptaculous.js?load=effects" type="text/javascript"><!--mce:2--></script>
<script src="search.js" type="text/javascript"><!--mce:3--></script>

You might notice that we are using script.aculo.us. We are including those libraries so we can get a few nice animation effects. If you do no want these effects (more info in the JS section), you don’t need to include lines 1 and 2. Note: On line 2, the source also specifies to only load the effects.js file.

Line 3 points to our javascript file. Line 4 points to our CSS file. Simple right? Next place the following code somewhere inside your body tag:

<form id="search_form" method="get">
<input id="q" onkeyup="doSearch();" name="q" type="text" />
<input id="search_image" onclick="doSearch();return false;" alt="Search" src="search_icon.png" type="image" />
</form>
<div id="searchResultsDiv">
	Start typing. Your results will show below instantly.</div>

This is the code that does a few things. It creates a search box, q, which has an event handler, onkeyup, that calls our doSearch() method. We then create an image that has the onclick method set to the same action. We need to return false here so that the form’s action is not called.

The next div is where the search results will come back. Right now I have some text in there that tells people to just type and see results. You could have also left it blank if you don’t want there to be anything before users type.

That’s it for our HTML. Very, very, very simple. Let’s move onto the CSS so we can style this HTML nicely.

Style a bit with CSS

I’ve broken the discussion of the CSS file up into two parts. We will talk about the second portion in just a bit. The full CSS file is available in the zip file. In this first part, we are just going to style the HTML we’ve written so far. Not any of the search results. This section contains only 3 things:

a img {
	border: 0px;
}
form#search_form input {
	vertical-align:middle;
	display:inline;
}
form#search_form #search_image {
	margin-left: -15px;
}

Like our HTML, this is very simple. The a img just makes sure we don’t draw a border around images inside links. Next, we make sure all of our form elements are lined up. We also make the search icon have a left margin of -15 pixels. This will make our search icon overlap with the text box.

We’re all done with the CSS for now. Let’s move onto the Javascript.

Write our Javascript code

Let’s analyze our JS code a bit. This first part creates a typical XMLHttpRequest and creates a few helper methods for it.

function createRequestObject() {
    var ro;
    var browser = navigator.appName;
    if(browser == "Microsoft Internet Explorer"){
        ro = new ActiveXObject("Microsoft.XMLHTTP");
    }else{
        ro = new XMLHttpRequest();
    }
    return ro;
}
var http = createRequestObject();
function sndReq(action) {
    http.open('get', action);
    http.onreadystatechange = handleResponse;
    http.send(null);
}
function handleResponse() {
    if(http.readyState == 4){
		document.getElementById('searchResultsDiv').innerHTML = http.responseText;
    }
}

I’m not going to spend time explaining the workings of these AJAX objects. There are tutorials better at that. I’ll just say that we will use the method sndReq to send asynchronous requests with action as the URL we want to request. The function handleResponse will be called when the AJAX request is done. When we receive the finished request, we set the innerHTML of our search results div with the response text. Next we have our doSearch and openWindow functions:

function doSearch() {
	var val = 'do_search.php?q=' + document.getElementById('q').value;
	sndReq(val);
	return false;
}
function openWindow(url) {
  window.open(url,'popupWindow','resizable=no, scrollbars=yes, toolbar=no, status=no, height=650, width=720');
}

doSearch() will send an AJAX request to do_search.php with the q variable set to the search box’s value. openWindow() will simply open a new window and go to a certain URL. This code is executed when an item from the search results is clicked. You can change this function to do whatever you wish when your search results are clicked. Finally, we have the following javascript code:

function expandOrClose(arrow_id, list_num, list_name)  {
	var a = document.getElementById(arrow_id);
	if(a.name=='d') {
		a.src='arrowRight.gif';
		a.name='r';
		hideSearchTypes(list_num,list_name);
	}
	else {
		a.src='arrowDown.gif';
		a.name='d';
		showSearchTypes(list_num,list_name);
	}
	return false;
}
function hideSearchTypes (i,j) {
	var theDiv = document.getElementById(j).getElementsByTagName('li');
	for(var d = 1; d &lt; theDiv.length; d++) {
		var theID = 'res-'+i+'-'+d;
		Effect.DropOut(theID);
	}
}
function showSearchTypes (i,j) {
	var theDiv = document.getElementById(j).getElementsByTagName('li');
	for(var d = 1; d &lt; theDiv.length; d++) {
		var theID = 'res-'+i+'-'+d;
		Effect.Appear(theID);
	}
}

expandOrClose() is called when a user clicks on a category heading (the blue sections in the image above). What we want to do is compress that section so it takes up no space. Our JS code simply changes the arrow direction and then calls one of the next two functions. The two functions go through each remaining row in that category and hide/show it. These two functions use the script.aculo.us effects. You could also just do something like:

function hideSearchTypes (i,j) {
	var theDiv = document.getElementById(j).getElementsByTagName('li');
	for(var d = 1; d &lt; theDiv.length; d++) {
		var theID = 'res-'+i+'-'+d;
		document.getElementById(theID).style.display = 'none';
	}
}
function showSearchTypes (i,j) {
	var theDiv = document.getElementById(j).getElementsByTagName('li');
	for(var d = 1; d &lt; theDiv.length; d++) {
		var theID = 'res-'+i+'-'+d;
			document.getElementById(theID).style.display = '';
	}
}

That code immediately hides or shows each row. It also does not require the two script.aculo.us libraries.

That looks about it for the javascript code. Let’s move onto our PHP code.

Write the PHP code to return the search results

I want to repeat that this does not require PHP. Any language will do. Perl, ASP, Python, etc. I use PHP, so that is what this tutorial will use, but the rest of the code is independent of what language you use.

We’ll start with the first bit of PHP code:

This will set the variable $q to the value from our request. Be sure to make any proper permission checks here. For instance, you may want to confirm that the user has logged in or escape the strings to prevent any hacking.

I am going to start out with what your PHP should print out. Here is an example:

<div id="search_results">
<ul id="first_list" class="search_results_list">
	<li class="searchHeader">
	<a onclick="expandOrClose('arrow1',1,'first_list'); return false;"><img id="arrow1" src="arrowDown.gif" alt="expand or close" /> People</a></li>
	<li id="res-1-1" class="searchResultRow">
	<a onclick="openWindow('http://apple.com');return false">Steve Jobs</a></li>
	<li id="res-1-2" class="searchResultRow">
	<a onclick="openWindow('http://microsoft.com');return false">Bill Gates</a></li>
</ul>
</div>

Surround your output in a div with id as search_results. Each category of results you make will be its own ul. Give it a unique ID and make its class search_results_list. Each ul should have one li with class=”searchHeader”. Inside that li, we make a link, which calls our javascript function expandOrClose(). We put the arrow image, and then we write the title of the category, in this case People.

The following li’s should be of class searchResultRow. Their id should take the form of res-#-#. The first number is the category number. The second is the row number in that category. Each category begins with row 1. Inside these li’s you can print out your results. I surround them in a link that opens a window pointing to their respective companies.

The process of writing a searching algorithm is very complex and requires a tutorial in itself. I still want to show you some examples of how to write one. This first one is just pseudocode, but it describes the process of writing a search for our search box.

get inputed search string
sql code that searches for that string in important column 1
find how many results there were
if results == 0
	print out nothing for this category
else
	print out the UL for this category
	loop through each result
		print out LI for this row
	close UL

Repeat for each category you wish

#Finally
Check if nothing was ever printed out
If nothing printed
	Print out no results

This pseudo-code should be pretty self explanatory. The following code is the actual php code I use for the example linked above. It searches through text files matching the input string to parts of people’s names. Many of you will want this to be written so it can query a MySQL database. The transformation of this code to MySQL code is not very difficult.

<div id="search_results">
<ul id="celeb_list" class="search_results_list">
	<li class="searchHeader">
		<a onclick="expandOrClose('arrow1',1,'celeb_list'); return false;"><img id="arrow1" src="arrowDown.gif" alt="expand or close" /> Celebrities</a></li>
	<li id="res-1-&lt;?php echo $l; ?&gt;" class="searchResultRow"></li>
</ul>
<ul id="athlete_list" class="search_results_list">
	<li class="searchHeader">
		<a onclick="expandOrClose('arrow2',2,'athlete_list'); return false;"><img id="arrow2" src="arrowDown.gif" alt="expand or close" /> Athletes</a></li>
	<li id="res-2-&lt;?php echo $l; ?&gt;" class="searchResultRow"></li>
</ul>
<ul id="president_list" class="search_results_list">
	<li class="searchHeader">
		<a onclick="expandOrClose('arrow3',3,'president_list'); return false;"><img id="arrow3" src="arrowDown.gif" alt="expand or close" /> Presidents</a></li>
	<li id="res-3-&lt;?php echo $l; ?&gt;" class="searchResultRow"></li>
</ul>
<ul id="first_list" class="search_results_list">
	<li class="searchHeader">
	<a onclick="expandOrClose('arrow1',1,'first_list'); return false;"><img id="arrow1" src="arrowDown.gif" alt="expand or close" /> Results</a></li>
	<li id="res-1-1" class="searchResultRow">
	No Results Found</li>
</ul>
</div>

If you know PHP, this should make sense. I basically loop through each file array and pattern match the inputed string to the first name, the last name and then the first name and the last name. You might notice the variable $possibleName. That is there if the person might have a middle name included. If the pattern matching returns true, we go inside and if it’s the first time in that category we print out the header. Then we print out the specific result, the person’s name that matched. We loop through that 3 times (once for each data file). At the end we do a special UL print out if no results were found. Hopefully, for those of you who understand PHP, that example will help you in writing your own search. You will need to spend some time writing this part, and unfortunately, I can’t help too much considering it’s based heavliy on which language you use and how your database is structured. If you need help on a specific implementation, please leave a comment here, and I will attemp to help out.

Moving on. After you’ve finished your PHP (or other language) let’s return to the CSS code for the search results.

Finish up the CSS

Let’s get right in:

#search_results {
	margin: -18px 0 0 -38px;
	width: 200px;
	font-family: 'Lucida Grande', Verdana, Arial, Sans-Serif;
	font-size: 13px;
}
ul.search_results_list {
	margin-bottom: 0px;
	margin-top: -1px;
}
ul.search_results_list li {
	list-style: none;
	display: block;
	padding: 2px;
}
li.searchHeader {
	background-color:#6699FF;
	color:#FFFFFF;
	border:#CCCCCC 1px solid;
	font-weight: bold;
	font-size: 15px;
}
li.searchHeader a {
	text-decoration:none;
	color:#FFFFFF;
}
li.searchHeader a:hover {
	text-decoration:none;
	color:#000000;
}
li.searchResultRow {
	background-color:#FFFFFF;
	color:#333333;
	border-left:#CCCCCC 1px solid;
	border-right:#CCCCCC 1px solid;
	border-bottom:#CCCCCC 1px solid;
}
li.searchResultRow a {
	text-decoration: none;
	color: #111;
	margin-left: 12px;
}
li.searchResultRow a:hover {
	text-decoration: underline;
}

There’s nothing out of the ordinary in this CSS. It just sets up all the colors and borders we want.

Tie everything together

It looks like we have some really good working code. There’s nothing else to do. Just give it a run and check it out. It’s pretty cool. We now have search as you type implemented completely in very little code using HTML, CSS, Javascript, AJAX and some PHP. Spotlight has now officially reached the web.

There’s not much else to do. Just put your code on your site and let your users enjoy Spotlight-Like searching right within your website. All code here is Public Domain. If you use it on your site, it’d be nice if you posted a comment here linking to your site, so I can see how people are using it. I love getting feedback, so if you have any comments, questions, examples, or complaints, please post a comment.

If you liked this tutorial, check out my tutorial on Dashboard-Like effects in CSS and also my tutorial on Flickr-like edit in place.