I recently implemented a live search on the quotes page, so I figured I’d give a tutorial on how I did it. A “live” search is like a regular search feature, but if the user has Javascript enabled, the search will run as they type, instead of waiting for them to hit the ‘Submit’ button. There are a couple of advantages to this, one being that it’s a bit quicker than a traditional search because it starts working immediately; another advantage is that it might help you narrow down your search better (e.g. you initially want to search for ‘frogs’, but you get just ‘frog’ typed in and see results that interest you that wouldn’t have seen with a ‘frogs’ query). However, I think the main benefit from a live search is that it’s cool. :)

Things You’ll Need

  • PHP for the search backend;
  • The Prototype Javascript framework in order to easily make an AJAX call with the visitor’s search query.

Query-parsing Backend

We should get our search working without any fancy AJAX first to know that it works. My quotes are stored in a plain-text file and I use PHP to display them. We want a PHP script that we can pass a query to, either by a GET or a POST request (i.e. through a URL parameter or from a form), and that will then search through our quotes file for the given query. Here’s what I came up with, which I named quotes_search.php:

<?php
// Given a Regular Expression and an array,
// it will return an array of all keys for which
// the regular expression matches the array
// value at that key
function array_regex( $pattern, $haystack ) {
  $keys = array();

  // Go through the given array
  foreach ( $haystack as $i => $value ) {
    // Check to see if the current array value
    // matches the given pattern
    if ( preg_match( $pattern, $value ) )
      // Use array_unshift instead of array_push
      // so that the latest quote keys are added
      // to the beginning of the array and not the
      // end--want to display the quote search
      // results in reverse order
      array_unshift( $keys, $i );
  }

  return $keys;
}

// Get the query either from a GET or POST request
if ( array_key_exists('query', $_GET) )
  $query = $_GET['query'];
else if ( array_key_exists('query', $_POST) )
  $query = $_POST['query'];

// Where the quotes are stored; plain-text file
$data = '/path/to/quotes/file/quotes.txt';

// Turn that quotes file into an array of lines
$file = file( $data );

// If the query was blank...
if ( $query == '' ) {
  // Just display the entire quotes file
  $count = count( $file );
  echo '<ol class="padded">';

  // Display the quotes from the most recent to the earliest
  for ( $i=$count-1; $i>=0; $i-- ) {
    $file[$i] = trim( $file[$i] ); // Get rid of carriage return

    // For odd-numbered indices in the quotes array, put a
    // different CSS class on the li tag so that we can style
    // it differently for readability
    if ( $i % 2 == 0 )
      echo '<li>';
    else
      echo '<li class="alt">';

    // Place an anchor tag where the name attribute is set
    // to the quote's index in the quotes array, so that we
    // can link directly to this quote later if we like
    echo '<a name="' . $i . '"></a>';

    // Display the quote itself
    echo $file[$i];
    echo '</li>';
  }

  echo '</ol>';
} else {
  // Remove any slashes in the query so as not to mess with PHP's
  // RegEx support
  $query = str_replace( '/', '', $query );

  // Find the indices that have matching values to the given query,
  // case insensitive
  $keys = array_regex( "/$query/i", $file );

  // If there were any indices found with matching values...
  if ( count($keys) > 0 ) {
    echo '<ol class="padded">';

    // Go through the indices
    foreach ( $keys as $count => $key ) {
      // For odd-numbered indices in the quotes array, put a
      // different CSS class on the li tag so that we can style
      // it differently for readability
      if ( $count % 2 == 0 )
        echo '<li>';
      else
        echo '<li class="alt">';

      // Display the quote itself
      echo $file[$key] . "</li>\n";
    }

    echo '</ol>';
  } else {
    // No matching quotes were found
    echo '<p>No quotes match your search.</p>';
  }
}
?>

Displaying the Quotes

When no query is submitted, we want to see all the quotes. In my Wordpress Quotes page, I have the following:

<div id="quotes">
  <?php include '/path/to/searching/backend/quotes_search.php'; ?>
</div>

Since we’re including the search backend without any query passed to it, the PHP code shown above will just spit out the quotes in a nice ordered list. The div tag with its ID “quotes” is very important, as this will be used later by some Javascript to display the search results.

Making It Live

Now that we have our PHP backend, we can work on the Javascript side of things. We still want people with Javascript disabled to be able to use our search, though, so we set up the HTML form like any other form. We can, however, hide the submit button from those that have Javascript enabled, so as to make it clearer that submitting the form in the usual way is unnecessary. Here’s the HTML I used:

<form action="/wp-content/quotes_search.php" method="post">
<p><input type="text" name="query" size="20" id="query" /></p>
<span id="loading" style="display: none;"><em>Searching quotes...</em></span>
<span id="complete" style="display: none;"><em>Search complete!</em></span>
<script type="text/javascript">
  document.write('<p><em>Hint:</em> It searches as you type.</p>');
</script>
<noscript>
  <p><input type="submit" value="Search" /></p>
</noscript>
</form>

See the span tags that are set to display: none? Those will be used to notify the visitor that a search is taking place or has completed. They start out hidden because nothing initially happens with the search; we’ll use Javascript to display them and re-hide them as necessary. In the noscript area, which will be seen by users without Javascript, we include a submit button. For those with Javascript, we offer a helpful hint that no form submission is necessary.

This HTML by itself won’t do anything special. What we really need is the Javascript code to check when our search input field (with ID “query” above) has been updated and then initialize a search. Here’s that code:

<script type="text/javascript">
  //<![CDATA[
  new Form.Element.Observer(
    'query',
    0.5,
    function(element, value) {
      new Ajax.Updater(
        'quotes',
        '/url/to/search/backend/quotes_search.php?query=' + value,
        {
          asynchronous:true,
          evalScripts:true,
          onComplete:function(request){Element.hide('loading'); Element.show('complete');},
          onLoading:function(request){Element.hide('complete'); Element.show('loading');}
        },
        {parameters:value}
      )
    }
  )
  //]]>
</script>

That’s a hairy chunk of Javascript, I know. What it’s doing is creating an Observer (thank you, Prototype) for the element with the ID “query” (which is our search input field). The Observer checks the search field every 500 milliseconds for changes. When changes are detected, it executes the function that is the third parameter. The function contains an Updater, which will update the element with the ID “quotes”, which is our div container holding all the quotes in our page. The Updater uses AJAX to send a request to the URL '/url/to/search/backend/quotes_search.php?query=' + value, where value will be replaced with the value Javascript extracts from the search input field. The next Updater parameter is a hash of options, including two for showing and hiding our span tags when a query has completed or is in process. The final Updater parameter defines what parameters we want from the search input field, for use in our backend. You’ll want to stick this chunk of Javascript somewhere after your div containing the quotes and the search form, so that all the necessary ID’s the Javascript uses will be available to it.

Overall View

To sum up, here’s what we’ve got and what we’re doing with it:

  1. We have a plain-text file containing quotations, called quotes.txt;
  2. We have a Wordpress page in which we want to show these quotes, as well as offer a live search so that users can find specific quotes;
  3. We have a PHP backend in quotes_search.php that will search quotes.txt for a given query;
  4. We display the quotes in our Wordpress page and stick in the search form anywhere we please; after both of these items, we stick in the Javascript that will make our AJAX call;
  5. When a user with Javascript enabled types something in the search field, a Javascript Observer sees this and pokes the Javascript Updater;
  6. The Updater then gets off its butt and grabs the value from the search field, then throws it at quotes_search.php via GET request (that is, it passes the query as a URL parameter);
  7. If the user does not have Javascript enabled, they hit the submit button just like in any other form and the query is passed to quotes_search.php via a POST request (that is, it passes the query as a parameter in the form submission);
  8. The PHP backend takes the query and searches all the quotes in quotes.txt for all quotes that match; it then displays matching quotes in an ordered list. If Javascript is enabled, the matching quotes will be displayed inside the “quotes” div; if Javascript is disabled, the quotes will be displayed in a standalone page.