Live search has been one of the hottest features. I decided to give it a try on my site, which is Drupal-powered. And since it's powered by Drupal, there are obviously some little problems that need to be sorted out.
This article is on Drupal 4.7. For live search on Drupal 5.0, see my live search module for Drupal 5.
Get Results Fast
The basic goal for me is that after the user types some text to search, a little box should appear shortly, containing the search result for their keyword. If their keyword returned no results, a message should inform them that.
It was obvious to implement it in the so-called "Ajax" fashion. As the user types, a request is sent in the background, and when it's completed, the client-side JavaScript injects the results directly on the current page.
There are also several points that needs to be achieved, including...
- falling back gracefully in case JavaScript isn't enabled
- the existing Search module should be reused and modifications should be minimized
- adjusts with the locale
- not to increase server burden
With these goals in mind, a vague idea of what's needed appears.
The Tools
- Prototype 1.5.0_rc0
- DOM Ready Extension for Prototype
- event:Selectors
- Cursor.js to indicate searching
Investigation
First of all, since we want to reuse the existing Search module for Drupal, I go to the Search Results page for "ajax" and examine the DOM structure.
Here are some simple facts about it:
- The URL for searching keyword is
http://www.kourge.net/search/node/keyword - The result is in a
<dl>definition list element with the classsearch-results - If no results were found then the
<dl>tag won't exist, as demonstrated in the Search Results page for "abracadabra" - If nothing's found, the message "your search yielded no results" is in an
<h2>element with the classtitle
Sum that all up, and you have a basic process:
- Fire an
XMLHttpRequestforhttp://www.kourge.net/search/node/keyword - When the request is complete, transverse through the
responseXMLobject of the request to look for all<dl>elements and choose only the one with the classsearch-results - If such an element is found, inject it to a prearranged
<div>element - If no such element exists, look for all
<h2>elements and choose only the one with the classtitle - If such an element is found, inject the content of it to the prearranged
<div>element to inform the user that nothing's found - If even that doesn't work, fallback to the default and hard-coded English message and inject it
The Problem
After writing experimental code, you'll notice that Mozilla-based browsers return null when you try to access the responseXML object of the completed request. Why?
The AJAX:Getting Started article on the Mozilla Developer Center states that:
Some versions of some Mozilla browsers won't work properly if the response from the server doesn't have an XML
mime-typeheader. To satisfy this, you can use an extra method call to override the header sent by the server, just in case it's nottext/xml.http_request = new XMLHttpRequest(); http_request.overrideMimeType('text/xml');AJAX:Getting Started - Step 1 – say "Please!" or How to Make an HTTP Request - MDC
The bottom line is that we are left with two options to deal with this.
- Utilize the
overrideMimeType()method in your client-side JavaScript code - Modify the Drupal Search module so that it will send back
Content-Type: text/xmlin the response header
The first way would require you to modify Prototype: not a very good idea. The second way is a bit easier, but requires a trick. You won't want to send back the header Content-Type: text/xml for every single search request, because that'll break the normal search feature for standard-aware browsers, causing them to display a parsed XML tree instead of rendering the page as (X)HTML.
A quick glimpse of the Prototype code will reveal this:
/* ... */
setRequestHeaders: function() {
var requestHeaders =
['X-Requested-With', 'XMLHttpRequest',
'X-Prototype-Version', Prototype.Version,
'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];
/* ... */
Prototype sends these three custom headers when initiating an XHR request, assuming my Prototype version is 1.5.0_rc0:
X-Requested-With: XMLHttpRequest X-Prototype-Version: 1.5.0_rc0 Accept: text/javascript, text/html, application/xml, text/xml, */*
Which header is the best for determining whether not to respond with the HTTP header Content-Type: text/xml?
Accept? Maybe some other browsers might send the exact same string. Not a good idea.X-Prototype-Version? What if you use a different version of Prototype? Not a good idea either.X-Requested-With? Right on spot, because every version of Prototype sends this header.
I opened up modules/search.module on my server and looked for the function search_view():
/**
* Menu callback; presents the search form and/or search results.
*/
function search_view() {
$type = arg(1);
// Search form submits with POST but redirects to GET. This way we can keep
// the search query URL clean as a whistle:
// search/type/keyword+keyword
if (!isset($_POST['edit']['form_id'])) {
if ($type == '') {
// Note: search/node can not be a default tab because it would take on the
// path of its parent (search). It would prevent remembering keywords when
// switching tabs. This is why we drupal_goto to it from the parent instead.
drupal_goto('search/node');
}
After the code:
if ($type == '') {
drupal_goto('search/node');
}
Add the following code:
if ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') header('Content-Type: text/xml');
That's one line of code for the server-side PHP script.
Now we need a slight modification for the theme template. My Drupal theme is Typography Paramount, powered by the PHPTemplate theme engine.
Again, I opened up themes/typography_paramount/search-box.tpl.php on my server, which is responsible for generating the markup for the existing search box on the right.
<input maxlength="128" name="edit[search_theme_form_keys]" id="search-box" size="15" value="<?php print t('Search') ?>" onclick="if(value=='<?php print t('Search') ?>')value=''" onblur="if(value=='')value='<?php print t('Search') ?>'" class="form-text" type="text" autocomplete="off" />
<input name="edit[form_id]" id="edit-form_id" value="search_theme_form" type="hidden" />
<input name="op" value="search" class="form-submit" type="hidden" alt="" />
<div id="search-result"></div>
<br /><br />
Notice that:
- The ID of the search box is
search-box <?php print t('Search') ?>is used to print out the localized string of "Search"onclickandonblurhandlers move the "Search" text out of the way when the user wants to search and grabs it back when the user leaves it empty- The prearranged
<div>element with the IDsearch-resultis for the injection of content
Then I modify themes/typeography_paramound/page.tpl.php to include the JavaScript files:
<script type="text/javascript" src="/js/prototype.js"></script> <script type="text/javascript" src="/js/eventselectors.js"></script> <script type="text/javascript" src="/js/domready.js"></script> <script type="text/javascript" src="/js/cursor.js"></script> <script type="text/javascript" src="/js/search.js"></script>
The last search.js is the custom code written. Search.js consists of several parts...
- $E() function for ease of creating elements
- The function
search()for getting the search box value and firing off the actual Ajax request. window.searchTimerfor delaying the Ajax request to avoid firing a request on every keystrokeEvent.onDOMReady()to immediately attach event listeners to the search box using event:Selector when the DOM is ready
It is also aware of several things.
- It encodes the search keyword
- If the user presses the Escape key the search box is immediately cleared
In conclusion, it fits with Drupal's search feature nicely. Try typing "ajax" in the search box and try it out.
This article is on Drupal 4.7. For live search on Drupal 5.0, see my live search module for Drupal 5.






Great article. Well written
Great article. Well written and very useful. Thanks alot!
Hi, Sorry, my last message
Hi,
Sorry, my last message was typed wrong. I meant hi elements in addition
to h2 elements in the response XML object parsing.
Thanks Again
Steve
Hi, I aplogize for this
Hi,
I aplogize for this edited reply. I was wondering why the h1 elements aren't
also searched for in the response XML object in addition to the h 2 elements.
This is a great insight into the trade offs involved in implementing this
search function. I surely couldn't have filled your shoes on this one!
Thanks Again
Steve
Great article. Well written
Great article. Well written and very useful. Thanks alot!
It would be nice if you have this module for Drupal 6 :d
Thanks
It's the lack of feedback
It's the lack of feedback that's holding me back. In fact, if I don't get any more feedback by the time summer breaks, I'll just go ahead and port it to Drupal 6, since it's already available for Drupal 5.
Hi, You were going to decide
Hi,
You were going to decide whether to port it to drupal 6. Have you decided yet?
Mike
This is good information on
This is good information on adding live search to drupal. Thanks for all the detail, it really helps.
Kandk
It's so great that sites
It's so great that sites like this exist so that we can learn how to add live search to our drupal sites. I would never have know before.
I hate it when Drupal
I hate it when Drupal upgrades because a lot of my stuff doesn't work, and I have to either wait until the next upgrade or go create my own scripts for my sites.
Post new comment