Creating an Ajax Search Widget

By Jeremy McPeak

The most widely used function of the Web is searching. It's not even an option; if you want to find any information, the search engines of the Web are the places you have to go to.

With the ever-expanding technology of the Web, conventional search engines are opening the doors to more unconventional means to get you to the content you desire. The first to jump onto the scene was Yahoo! with their Y!Q service (http://yq.search.yahoo.com/publisher/index.html). This new service enables you to search from any web page, provided the page's author includes it in their web page. It is a service to provide related search results to the content at hand, giving readers more information at their fingertips without leaving your page.

The Yahoo! Y!Q service is a great idea, but it hasn't surfaced without criticism. The main argument? It requires the use of Yahoo!'s JavaScript and you have to add a <form/> element, meeting the Yahoo! requirements, to perform the search. For many web site authors, it takes too much effort to provide the service. And after all the work, the search results are presented in the Yahoo! style, breaking the look and feel of your web site.

Thankfully, Yahoo! isn't the only search engine breaking into this "provide search results from your web site" service. MSN Search (http://search.msn.com) provides a similar service, except it enables the web developer to control the look, feel, and implementation. This ability comes from MSN Search providing RSS versions of its search results, making it possible to subscribe to a particular search or add the results to your page using Ajax methods.

Google has yet to throw its hat into this new spin on "search from your site" technique, although, at the time of this writing, Google released Google BlogSearch Beta (http://blogsearch.google.com), which provides results returned in either RSS or Atom formats.

The Server-Side Component

Perform a search with MSN Search, and you'll see an orange XML image at the bottom of the results page. Clicking this image takes you to a new page, giving you the URL to subscribe to the search:

http://search.msn.com/results.aspx?q=[SEARCHTERM]&format=rss

With this knowledge, you can write the server-side code to retrieve the remote feed. For the widget, you will use PHP to retrieve the search feed. The URL to request information from the server application looks like this:

websearch.php?search=[SEARCHTERM]

There's only one variable in the query string: search. Therefore, the application should look for this query item. On the server, you'll need to create a page to pull this data:

<?php
header("Content-Type: text/xml");
header("Cache-Control: no-cache");if ( isset($_GET["search"]) ) 
{    $searchTerm = urlencode( stripslashes($_GET["search"]) );
    
    $url = "http://search.msn.com/results.aspx?q=$searchTerm&format=rss";
    
    $xml = file_get_contents($url);
    
    echo $xml; 
}
?>

The first two lines set the required headers so that the browser will handle the data correctly (as XML and without caching the results). The next line of code uses the isset() function to determine whether the search key is present in the query string.

The search term should go through a variety of functions in order to send a proper request to the remote host. First, it is passed to the stripslashes() function. If magic quotes are enabled in the PHP configuration (which is the default), any quote that reaches the PHP engine is automatically escaped with a slash: \"search query\". The stripslashes() function removes these escape sequence, leaving only "search query". After the slashes' removal, it then goes to the urlencode() function, which properly encodes characters to be used in a query string. Spaces, quotes, ampersands, and so on are all encoded.

If the search term does not go through these processes, the MSN server will return a code 400: Bad Request.

When the search term is ready for transmission, it is included into the URL and stored in the $url variable. Finally, the file_get_contents() function opens the remote file, reads the contents, and returns it as a string to the $xml variable, which is printed to the page using the echo command.

The Client-Side Component

The client-side code from this widget is based on a static object called msnWebSearch, which is defined as an object literal without any properties (for now):

var msnWebSearch = {};

This object is used in the onclick event of an HTMLElement in order to perform a search:

<a href="#" 
onclick='msnWebSearch.search(event,"Professional Ajax"); return false;'>

    Professional Ajax
</a>

The msnWebSearch object exposes several methods to get the search results and to draw and position the HTML to contain the data. The first method is drawResultBox(), which draws the HTML. The HTML this method draws looks like this:

<div class="ajaxWebSearchBox">
    <div class="ajaxWebSearchHeading">MSN Search Results
        <a class="ajaxWebSearchCloseLink" href="#">X</a>

    </div>    <div class="ajaxWebSearchResults">
        <a class="ajaxWebSearchLink" target="_new" />
        <a class="ajaxWebSearchLink" target="_new" />
    </div>
</div>

The result box is divided into two parts: a heading and a results pane (see Figure 1). The heading tells the user that this new box contains results from an MSN search. It also contains an X, which will close the box. The results pane contains block-style links, which opens a new window when clicked.


Figure 1

Creating an Ajax Search Widget

Drawing the Results User Interface

The code to generate this HTML is rather lengthy because the elements are generated using DOM methods. The drawResultBox() method accepts one argument, an event object:

msnWebSearch.drawResultBox = function (e) {
    var divSearchBox= document.createElement("div");
    var divHeading = document.createElement("div");
    var divResultsPane = document.createElement("div");
    var aCloseLink = document.createElement("a");

These first lines create the HTML elements via the createElement() method. After the elements have been created, you can begin to assign their properties. The first two elements to finalize are aCloseLink and divHeading:

aCloseLink.href = "#";
aCloseLink.className = "ajaxWebSearchCloseLink";
aCloseLink.onclick = this.close;
aCloseLink.appendChild(document.createTextNode("X"));
    
divHeading.className = "ajaxWebSearchHeading";
divHeading.appendChild(document.createTextNode("MSN Search Results"));
divHeading.appendChild(aCloseLink);

The first four lines complete the link that closes the result box. A method, close(), becomes the handler for the link's onclick event. The next group of lines populate the heading <div/> with text and the closing link.

When this result box is drawn into the page, a response from the server application has not been received yet. To show the user that something is happening (other than a box popping out of nowhere), it would be nice for the user to see a message stating that data is loading (see Figure 2). To do this, create another element and append it to the divResultsPane element:

var divLoading = document.createElement("div");
divLoading.appendChild(document.createTextNode("Loading Search Feed"));
    
divResultsPane.className = "ajaxWebSearchResults";
divResultsPane.appendChild(divLoading);

This code creates the loading message and appends it to divResultsPane, while also assigning the class name to divResultsPane.


Figure 2

With these elements completed, all that remains is to add them to the divSearchBox element:

divSearchBox.className = "ajaxWebSearchBox";
divSearchBox.appendChild(divHeading);
divSearchBox.appendChild(divResultsPane);
document.body.appendChild(divSearchBox);

This code appends the divHeading and divResultsPane elements to the search box, and appends the search box to the page.

The final step in drawResultBox() is to position the newly drawn box and return divSearchBox to its caller:

msnWebSearch.drawResultBox = function (e) {
    var divSearchBox= document.createElement("div");
    var divHeading = document.createElement("div");
    var divResultsPane = document.createElement("div");
    var aCloseLink = document.createElement("a");
    
    aCloseLink.href = "#";
    aCloseLink.className = "ajaxWebSearchCloseLink";
    aCloseLink.onclick = this.close;
    aCloseLink.appendChild(document.createTextNode("X"));
    
    divHeading.className = "ajaxWebSearchHeading";
    divHeading.appendChild(document.createTextNode("MSN Search Results"));
    divHeading.appendChild(aCloseLink);
    
    var divLoading = document.createElement("div");
    divLoading.appendChild(document.createTextNode("Loading Search Feed"));
    
    divResultsPane.className = "ajaxWebSearchResults";
    divResultsPane.appendChild(divLoading);
    
    divSearchBox.className = "ajaxWebSearchBox";
    divSearchBox.appendChild(divHeading);
    divSearchBox.appendChild(divResultsPane);
    
    document.body.appendChild(divSearchBox);
    
    this.position(e, divSearchBox);
    
    return divSearchBox;
};

The way the msnWebSearch object is set up, divSearchBox must be returned to its caller for other operations. The position() method, as you may have guessed, positions the search box. It accepts two arguments: the event object passed to drawResultBox() and the divSearchBox element:

msnWebSearch.position = function (e, divSearchBox) {
    var x = e.clientX + document.documentElement.scrollLeft;
    var y = e.clientY + document.documentElement.scrollTop;
	divSearchBox.style.left = x + "px";
    divSearchBox.style.top = y + "px";
};

The first two lines get the left and top positions to place the search results box. Two pieces of information are required to perform this operation. First is the x and y coordinates of the mouse. This information is stored in the clientX and clientY properties.

These coordinates, however, are insufficient to properly position the results box because the clientX and clientY properties return the mouse position in relation to the client area in the browser window, not the actual coordinates in the page. To account for this, use the scrollLeft and scrollTop properties of the document element. With the final coordinates calculated, you can finally position the box where the user clicked the mouse.

Displaying the Results

The populateResults() method populates the result pane with the search results. It accepts two arguments: the element to contain the results and an XParser object (XParser is a JavaScript-based RSS reader freely available for download from www.wdonline.com/javascript/xparser/):

msnWebSearch.populateResults = function (divResultsPane,oParser) {
    var oFragment = document.createDocumentFragment();
    
    divResultsPane.removeChild(divResultsPane.firstChild);

This method generates <a/> elements programmatically with DOM methods, so these elements will be appended to a document fragment created in the first line. The next line removes the loading <div/> element appended in drawResultBox().

The next step is to create the links:

for (var i = 0; i < oParser.items.length; i++) {
    var oItem = oParser.items[i];
        
    var aResultLink = document.createElement("a");
    aResultLink.href = oItem.link.value;
    aResultLink.className = "ajaxWebSearchLink";
    aResultLink.target = "_new";
    aResultLink.appendChild(document.createTextNode(oItem.title.value));
        
    oFragment.appendChild(aResultLink);
}

This code cycles through the items of the feed and generates links from the data and appends the <a/> element to the document fragment.

When the loop exits, the document fragment is appended to divResultsPane to display the search results:

divResultsPane.appendChild(oFragment);

Creating an Ajax Search Widget

Closing the Results Box

To close the search results box, the msnWebSearch object provides the close() method. If you'll remember from the drawResultsBox() method, the close() method handles the onclick event of the link responsible for closing the box:

msnWebSearch.close = function () {
    var divSearchBox = this.parentNode.parentNode;
    document.body.removeChild(divSearchBox);
	   
    return false;
};

The search box isn't really closed; in fact, it is removed from the document. To do this, retrieve the divSearchBox element. The first line does this by retrieving the parent node of this element's parent. Because close() handles the onclick event, this references the link. The next line removes the divSearchBox element from the document. The last line, return false, forces the browser not to follow the default behavior of a link (going to the location noted in the href attribute).

Building the Search Interface

The last method of the msnWebSearch object is search(), which provides the interface to perform a search. You can call search() with the onclick event of an element. It accepts two methods, an event object and the search term:

msnWebSearch.search = function (e,sSearchTerm) {
    var divSearchBox = this.drawResultBox(e);
    var url = encodeURI("websearch.php?search=" + sSearchTerm);
	var oParser = new XParser(url);
    oParser.onload = function () {
        msnWebSearch.populateResults(divSearchBox.childNodes[1],oParser);
    };
};

The first line calls the drawResultBox() method and passes the event, e, to it. The next line encodes the URL for proper transmission. This URL is passed to the XParser constructor to create a new parser. The parser's onload event handler calls the populateResult() method when the search feed is finished loading to populate the search box with results.

Of course, one of the reasons for building this widget is to make it fit the look of your own site.

Customizing the Web Search Widget

Thanks to CSS, you can easily customize the widget for your existing site and any redesign you may have later down the road.

The first CSS class discussed is ajaxWebSearchBox, the class for the search box. Because the box is positioned, it must have a position of absolute:

.ajaxWebSearchBox
{
    position: absolute;
    background-color: #0d1e4a;
    width: 500px;
    padding: 1px;
}

The absolute position is the only requirement. All other properties are optional according to your tastes. In this example, the box has a darkish-blue background, a width of 500 pixels, and 1 pixel of padding on all four sides. This padding will give the box a 1-pixel border around the box's contents.

The next class is ajaxWebSearchHeading, which contains the box's heading text and the closing link. To position the closing link in the top-right corner, it is absolutely positioned. Because of this, it requires ajaxWebSearchHeading to have a position of relative:

.ajaxWebSearchHeading
{
    position: relative;
    background-color: #1162cc;
    font: bold 14px tahoma;
    height: 21px;
    color: white;
    padding: 3px 0px 0px 2px;
}

Once again, the only required property is the position property. The remaining properties help to give the element a heading look. The background color is a lighter blue with white, bold text 14 pixels high and in the Tahoma font. The element's height is 21 pixels and is padded on the top and left edges.

As stated previously, the closing link's position is absolute:

a.ajaxWebSearchCloseLink
{
    position: absolute;
    right: 5px;
    top: 3px;
    text-decoration: none;
    color: white;
}a:hover.ajaxWebSearchCloseLink
{
    color: red;
}

The element is positioned 5 pixels from the right and 3 pixels from the top, placing the element in the top-right corner. This link does not have any text-decoration and is colored white. When the user hovers over the link, the text color turns red.

Notice that no visited or active pseudo classes are used. This is because the window always ignores the href attribute of this link (it has return false in its event handler). Therefore, the link is never truly active or visited.

Next, the ajaxWebSearchResults class styles the results pane:

.ajaxWebSearchResults
{
    background-color: #d3e5fa;
    padding: 5px;
}

There are no required CSS properties for this element. The existing properties are merely to define the results pane and make it relatively easy to read. The background color is a light blue, and 5 pixels of padding surround the edges. You can also style the loading message:

.ajaxWebSearchResults div
{
    text-align: center;
    font: bold 14px tahoma;
    color: #0a246a;
}

This element does not have a class name, but you can still style it by using the parent child notation shown in the preceding example. This example places the text in the center of the <div/> element and gives it a bold, blue font 14 pixels high.

The last elements you need to style are the result links. These have a class name of ajaxWebSearchLink:

a.ajaxWebSearchLink
{
    font: 12px tahoma;
    padding: 2px;
    display: block;
    color: #0a246a;
}a:hover.ajaxWebSearchLink
{
    color: white;
    background-color: #316ac5;
}a:visited.ajaxWebSearchLink 
{
    color: purple;
}

The only required property is the display property, which is set to block. This gives every link its own line. The padding, two pixels worth, gives a bit of separation between the links, making them easier to read. The font-face is Tahoma and is 12 pixels high. Their color is a dark blue, giving a nice contrast to the light blue background of ajaxWebSearchResults.

When the user hovers over these links, the background color is set to blue, whereas the text color changes to white.

The visited pseudo class is set, in the last rule in the previous code. This is to provide users with user interface cues they are already used to. By having the visited pseudo class set to display a color of purple, users know they've already visited that link, which can save them time by not visiting a page they may not want to.

Now, let's take a look at how to implement the widget.

Creating an Ajax Search Widget

Implementing the Web Search Widget

Implementing this widget is simple. First, you must upload the websearch.php file to your web server (of course, PHP must be installed, too). Next, you need an HTML document to reference all the components. The msnWebSearch object relies on the XParser class, which in turn depends on the zXml library (available at www.nczonline.net/downloads/). You must reference these files:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xml:lang="en" lang="en"  xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Ajax WebSearch</title>
    <link rel="stylesheet" type="text/css" href="css/websearch.css" />

    <script type="text/javascript" src="js/zxml.js"></script>
    <script type="text/javascript" src="js/xparser.js"></script>
    <script type="text/javascript" src="js/websearch.js"></script>
</head><body>
</body>

</html>

To perform a search, set the msnWebSearch.search() method as the element's onclick handler:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xml:lang="en" lang="en"  xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

    <title>Ajax WebSearch</title>
    <link rel="stylesheet" type="text/css" href="css/websearch.css" />
    <script type="text/javascript" src="js/zxml.js"></script>
    <script type="text/javascript" src="js/xparser.js"></script>
    <script type="text/javascript" src="js/websearch.js"></script>

</head><body>    
 <a href="#" onclick='msnWebSearch.search(event,"\"Professional Ajax\""); 
 return false;'>Search for "Professional Ajax"</a>
 <br /><br /><br /><br />
 <a href="#" onclick='msnWebSearch.search(event,"Professional Ajax"); 
 return false;'>Search for Professional Ajax</a>

</body>
</html>

The first new link performs a search for the exact phrase Professional Ajax, whereas the second searches for all the words. Also note the return false in the onclick event. Once again, this forces the browser to ignore the href attribute and is required. Clicking these links will draw the search box at the mouse's cursor, and you'll have the search results just pixels away.

This article is adapted from Professional Ajax by Nicholas C. Zakas, Jeremy McPeak, and Joe Fawcett (Wrox, 2006, ISBN: 0-471-77778-1), from chapter 8 "Web Site Widgets," written by Jeremy McPeak. © Copyright 2006 by WROX Publishing, Inc. All rights reserved. Reproduced here by permission of the publisher.

# # #



About the Author

Jeremy McPeak

Jeremy McPeak works in the IT department of a school district and has experience developing web solutions with JavaScript, PHP, and C#. He is co-author of Professional Ajax (Wrox, 2006, ISBN: 0-471-77778-1).

Comments

  • There are no comments yet. Be the first to comment!

  • You must have javascript enabled in order to post comments.

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • This ESG study by Mark Peters evaluated a common industry-standard disk VTl deduplication system (with 15:1 reduction ratio) versus a tape library with LTO-5, drives with full nightly backups, over a five-year period.  The scenarios included replicated systems and offsite tape vaults.  In all circumstances, the TCO for VTL with deduplication ranged from about 2 to 4 times more expensive than the LTO-5 tape library TCO. The paper shares recent ESG research and lots more. 

  • Live Event Date: September 17, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Another day, another end-of-support deadline. You've heard enough about the hazards of not migrating to Windows Server 2008 or 2012. What you may not know is that there's plenty in it for you and your business, like increased automation and performance, time-saving technical features, and a lower total cost of ownership. Check out this upcoming eSeminar and join Rich Holmes, Pomeroy's practice director of virtualization, as he discusses the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds