Welcome!

AJAX & REA Authors: Chris Fleck, Marek Miesiac, Loraine Antrim, Liz McMillan, Yeshim Deniz

Related Topics: AJAX & REA

AJAX & REA: Article

Real-World AJAX Book Preview: Creating a Live Search Web Service with PHP

Real-World AJAX Book Preview: Creating a Live Search Web Service with PHP

This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs for the special pre-order price, click here for more information. Aimed at everyone from enterprise developers to self-taught scripters, Real-World AJAX: Secrets of the Masters is the perfect book for anyone who wants to start developing AJAX applications.

Creating a Live Search Web Service with PHP
To keep this simple we won't use a database. Instead we'll use an array of values.

Create a new file, StateList.php, with the following in it:

Listing 6.10

<?
$StateList = array(
     'ALABAMA' => 'AL', 'ALASKA' => 'AK', 'AMERICAN SAMOA' => 'AS',
     'ARIZONA' => 'AZ', 'ARKANSAS' => 'AR', 'CALIFORNIA' => 'CA',
     'COLORADO' => 'CO', 'CONNECTICUT' => 'CT', 'DELAWARE' => 'DE',
     'DISTRICT OF COLUMBIA' => 'DC', 'FEDERATED STATES OF MICRONESIA' => 'FM',
     'FLORIDA' => 'FL', 'GEORGIA' => 'GA', 'GUAM' => 'GU',
     'HAWAII' => 'HI', 'IDAHO' => 'ID', 'ILLINOIS' => 'IL',
     'INDIANA' => 'IN', 'IOWA' => 'IA', 'KANSAS' => 'KS',
     'KENTUCKY' => 'KY', 'LOUISIANA' => 'LA', 'MAINE' => 'ME',
     'MARSHALL ISLANDS' => 'MH', 'MARYLAND' => 'MD', 'MASSACHUSETTS' => 'MA',
     'MICHIGAN' => 'MI', 'MINNESOTA' => 'MN', 'MISSISSIPPI' => 'MS',
     'MISSOURI' => 'MO', 'MONTANA' => 'MT', 'NEBRASKA' => 'NE',
     'NEVADA' => 'NV', 'NEW HAMPSHIRE' => 'NH', 'NEW JERSEY' => 'NJ',
     'NEW MEXICO' => 'NM', 'NEW YORK' => 'NY', 'NORTH CAROLINA' => 'NC',
     'NORTH DAKOTA' => 'ND', 'NORTHERN MARIANA ISLANDS' => 'MP',
     'OHIO' => 'OH', 'OKLAHOMA' => 'OK', 'OREGON' => 'OR',
     'PALAU' => 'PW', 'PENNSYLVANIA' => 'PA', 'PUERTO RICO' => 'PR',
     'RHODE ISLAND' => 'RI', 'SOUTH CAROLINA' => 'SC', 'SOUTH DAKOTA' => 'SD',
     'TENNESSEE' => 'TN', 'TEXAS' => 'TX', 'UTAH' => 'UT',
     'VERMONT' => 'VT', 'VIRGIN ISLANDS' => 'VI', 'VIRGINIA' => 'VA',
     'WASHINGTON' => 'WA', 'WEST VIRGINIA' => 'WV', 'WISCONSIN' => 'WI',
     'WYOMING' => 'WY'
);
?>

This contains an associative array where the state names are the keys and the abbreviations are the values.

Next create state.php:

Listing 6.11

<?php
// StateList.php contains an associative array, $StateList in this format:
// $StateList["STATE NAME"] = "ABBREV";
require_once('StateList.php');

// Only proceed if we have a name to search for
if( isset($_GET['name']) ) {

   // Create an empty array to hold our matches
   $response = array();

   // Build a regular expression out of our name.
   // First we'll strip out any non-alpha characters since we won't use them
   // Just replace them with spaces, the next step will clean those up.
   $SearchString = trim(preg_replace( '/[^A-Za-z ]+/', ' ', $_GET['name'] ) );

   // Make sure we don't have an empty string
   // If it is empty, just return our empty array
   if( !empty($SearchString) ) {
     // Split on spaces, and use preg_replace so we can replace
     // multiple spaces with wildcard match.
     $SearchString = preg_replace( '/\s+/', '.*', $SearchString );

     // Loop through our array keys and do a case-insensitive search
     foreach( array_keys($StateList) as $State ) {
       if( preg_match( "/$SearchString/i", $State ) ) {
         $response[$State] = $StateList[$State];
       }
     }
   }

   // Check to see if an output format was requested, and if it was JSON
   // Return XML output by default
   if( isset($_GET['output']) && !strcasecmp($_GET['output'], 'json') ) {
     require_once('JSON.php');
     $json = new Services_JSON();
     echo $json->encode($response);
   } else {
     // Send our XML content-type header
     header('Content-type: text/xml');
     // And print out our formatted response
     echo xmlrpc_encode_request(null,$response);
   }
}
?>

This script checks for two values in the query string: name and output. The variable name contains the string to search for and output can be JSON or XML. Next we build our search string ($SearchString) by stripping any non-alpha characters out of the name. These aren't used in our indexes and can cause problems. To simulate a limited wildcard search, we're going to build a regular expression with our search string variable. We replace any spaces with a wildcard match (.*) and our search string is ready.

Without delving too deep into regular expressions, a dot will match any single character. Adding an asterisk says "match any single character, repeated zero or more times." This will allow a match for "District of Columbia" if someone types "Di Co." For a more detailed description of regular expressions visit http://regular-expressions.info/.

Once we have our regular expression we loop through the keys of the array, which if you remember is our list of state names. If our search string matches, we add the state and its abbreviation to an array of matches. Once the loop is finished we print out the array in the requested format.

Consuming the Live Search Web Service
Consuming the new Web service is fairly simple. We'll use the same structure as we did with the "add" Web service and extend it a bit. Create state_search.html:

Listing 6.12

<HTML>
<HEAD>
<SCRIPT>
function findState() {
     // Get the handle to our text field
     var stateName = getElem('StateName');

     // And the handle for our output div
     var o = getElem('output');

     // Let's set a minimum length of 3
     if( stateName.value.length < 3 ) {
       // If the string is too short, hide our output container
       o.style.display='none';
       return 0;
     }
       // Create a variable to hold our XMLHttpRequest
       var req = null;

       // Now create our request object
       // For Safari, Mozilla, Opera 7.60b+
       // and other browsers supporting XMLHttpRequest
       if( window.XMLHttpRequest ) {
         req = new XMLHttpRequest ();
     } else if( window.ActiveXObject ) { // IE
         req = new ActiveXObject('Microsoft.XMLHTTP');
     } else {
         // Unsupported browser
         // This should be handled more gracefully..
         alert('Your browser does not support XMLHTTP objects (Ajax).');
         return 0;
     }

         req.onreadystatechange = function() {
         if (req.readyState == 4 && req.status == 200) {
           var matches = eval( '(' + req.responseText + ')' );
           var formattedMatches = '';
           var count = 0; // Set a counter

           // Loop through the returned values, build our HTML
           for( var stateName in matches ) {
             formattedMatches += stateName + ' (' + matches[stateName] + ')
';
             count++;
           }
           if( count ) {
             // If we have matches, show our container
             // and print the matches
             o.innerHTML = formattedMatches;
             o.style.display='block';
           } else {
           o.style.display='none'; // No matches, hide the container.
         }
       }
     }

     // Open our request...
     req.open('GET', 'http://realworldajax/php/state.php?name=' + stateName.value +
'&output=json', true);
     req.send(null); // And send it.
   }

   // Provide an ID and this will return a handle to the object
   function getElem( szSrcID ) {
     return document.layers ? document.layers[szSrcID] :
     document.getElementById ? document.getElementById(szSrcID) :
     document.all[szSrcID];
   }

   </SCRIPT>
   </HEAD>

   <BODY>
     <h4>Begin typing a state name...</h4>
     <label for="StateName">State Name</label> <input type="text" value="" name="StateName" id="StateName" onkeyup="findState()" />
     <div id="output" style="border:1px solid black; width:400px; display:none;"></div>
   </BODY>
   </HTML>

How It Works
The HTML is basic. We have a text field and use onkeyup to call findState(). There's a div that will contain our output.

In the JavaScript we have the function findState() that creates our XMLHttpRequest object and the callback function and then calls our Web service with an HTTP GET request to the following URL:

http://realworldajax/php/state.php?name=STATE_NAME&output=json

Our output is returned as a JSON object, and the callback function loops through the object and prints it out into our div as illustrated in Figure 6.3.

Using a Client-Side Framework
While the live state search is impressive, it's a lot of code to parse through and there are many places where we need to add error checking. Thankfully there are a number of JavaScript frameworks available to simplify this process. Let's look at how using the Prototype framework can clean up our code.

Prototype can be downloaded from http://prototype.conio.net/.

Rather than use our custom getElem() function or the DOM function document.getElementById() to retrieve an element, we have $(). We can even pass multiple IDs and it will return an array of elements. Getting the handle on an XMLHttpRequest object is a breeze as well with the Ajax.Request object:

new Ajax.Request('/php/state.php',
         { parameters: 'output=json&name=' + $('StateName').value,
         onSuccess:handleSuccess,
         onFailure:handleFailure,
         method:'get'
         }
     );

That's all the code we need to create a cross-browser XMLHttpRequest and define the name of our two callback functions to handle success and failure. At success or failure, handleSuccess() and handleFailure() will be called and passed to the XMLHttpRequest object.

With Prototype, you can even take it a step further and handle a response in both XML and JSON. While you're returning more data to your client, it may help you eliminate an XML parsing routine. Alternatively you could return additional information - like an XML parsing routine - as JSON and use it to parse the XML response.

First we'll have to modify our Web service slightly:

   if( isset($_GET['output']) && !strcasecmp($_GET['output'], 'json') ) {
       require_once('JSON.php');
       $json = new Services_JSON();
       header("X-JSON: (" . $json->encode($response) . ')' );
       echo $json->encode($response);
   } elseif(isset($_GET['output']) && !strcasecmp($_GET['output'], 'combined')) {
       header('Content-type: text/xml');
       header("X-JSON: (" . $json->encode($response) . ')' );
       echo xmlrpc_encode_request(null,$response);
   } else {
       // Send our XML content-type header
       header('Content-type: text/xml');
       // And print out our formatted response
       echo xmlrpc_encode_request(null,$response);
   }

We added one more check to see if combined output is being requested. If it is, then we send our XML Content-type header and our X-JSON header with the JSON data followed by the XML-formatted output. We'll update the parameters in our Ajax.Request object to request output=combined and watch the traffic using the Firefox extension Live HTTP Headers. We'll make a request to our Web service with output=combined&name=Col to retrieve any matches for "Col."

Figure 6.4 shows our GET request with all of the standard HTTP headers. Prototype also adds two headers of its own: X-Requested-With and X-Prototype-Version header. The response begins with HTTP/1.x 200 OK. The X-JSON header is present and contains the JSON-formatted output.

We can dig a bit deeper using Venkman, a JavaScript debugger available for Mozilla browsers. In the Ajax.Request object we set our onSuccess callback to be handleSuccess(). handleSuccess() is defined as function handleSuccess(resp, json) { ... }, and we will set a breakpoint for it at the function declaration, which will let us see the contents of the variables json and resp.

Figure 6.5 shows variables at our breakpoint and you can see the content and type of the variables sent to our function by the Ajax.Request object.

As you can see, using a client-side framework like Prototype greatly reduces the complexity of the code you have to write while adding significantly more functionality at the same time.

AJAX with a Server-Side Framework
If you're designing a new AJAX-enabled application, you may find it easier to use a server-side framework. Just as a client-side framework simplifies your code, a server-side framework will provide one more layer of abstraction between you and the AJAX code. A server-side framework will typically generate all of the code necessary for an AJAX request. Some even dynamically create proxies for you to use to invoke remote Web services. There are many different types of frameworks available: some are standalone libraries that perform specific functions and others integrate with an existing library to extend it.

This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs, click here to order.

About Corey Gilmore

Corey Gilmore is the president of CFG Consulting, Inc., specializing in developing rich internet applications with ColdFusion, PHP and Ajax for the Federal government and Fortune 100 clients. He guiltily enjoys designing and implementing low-cost, high performance business continuity plans using VMware ESX server. As the former Director of Information Technology for the United States Senate Democratic Leadership, he designed and implemented a continuity of operations plan to ensure Senate business continuity in the event of a disaster. Corey can be reached at cfgci.com.

About Jason Blum

Jason Blum is principal engineer with the advanced technologies development team in the United States Senate, Office of the Sergeant at Arms. Formerly the lead administrator of the Senate’s shared Web hosting environment, Jason now designs and manages the implementation of schema and pattern-centric solutions for Senate offices in XML, ColdFusion, Flex, and .NET. He is a Certified Advanced ColdFusion developer with a BA in philosophy, Masters Degrees in philosophy of education and in IT, and an intermediate certification in Hungarian from itk.hu.

About Phil McCarthy

Philip McCarthy is a UK-based software development consultant
specializing in J2EE and Web technologies. An early adopter of rich
browser-based client development, he has several years' experience of integrating Ajax technologies into enterprise Java frameworks, gained on projects in the financial services, telecoms, and digital media sectors. Philip is also the author of the "Ajax for Java Developers" series for IBM developerWorks, and blogs about software development at chimpen.com.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.