Share » Learn » eZ Publish » Clean Up Your Applications Using AJAX

Clean Up Your Applications Using AJAX

Thursday 12 April 2007 6:13:00 am

  • Currently 3 out of 5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

For a more advanced example, we will create a search box similar to Google Suggest for searching PEAR packages.

Providing users with word and phrase suggestions on how to complete a request in an input box makes your site more attractive and easier to use. Normally this is done with a combination of some text input and a dropdown list that are kept synchronized. While the user types into the input form, an initially invisible <div> element is created to contain the suggestions as they appear. The input field needs an event handler to monitor the text it contains in order to ensure that the suggestion list is always relevant.

Back in the browser, the callback function picks up the suggestions and applies a DOM manipulation so the user can view and select them. Each entry will have an event handler to update the input field when clicked.

Using HTML_AJAX, we can manage the client-server interaction with a few lines of code. The class for handling the functionality on the PHP side is pretty simple:

suggest.class.php:
class suggest {
   function suggest() {
      require_once 'pear_array.php';
      $this->strings = $aPear;
   }
   function getString($input='') {
      if ($input == '') return '';
      $input = strtolower($input);
      $suggestStrings=array();
      foreach ($this->strings as $string) {
         if (strpos(strtolower($string),$input) === 0) {
            $suggestStrings[] = $string;
         }
      }
      return $suggestStrings;
   }
}

In this example, we define an array of possible search terms. For normal use, you might get those values from a database. The only method needed for the search list is getString(). It expects a string and compares it with every possible entry of the array. If an element matches, it is copied to a result-set array and returned.

Next, we have to start up the server. For this example, we will use the AutoServer class:

auto_server.php:
session_start();
require_once 'HTML/AJAX/Server.php';
class AutoServer extends HTML_AJAX_Server {
   // this flag must be set for your init methods to be used
   var $initMethods = true;
   // init method for my suggest class
   function initSuggest() {
      require_once 'suggest.class.php';
         $suggest = new suggest();
         $this->registerClass($suggest);
   }
}
$server = new AutoServer();
$server->handleRequest();

By extending the server class and adding an init method for each class, you can manage several PHP classes being exported using only one server. You just have to set the variable $initMethods as "true" and name the init methods in the format: init[className]. In our example it is called initSuggest.

The AutoServer class is more powerful than necessary for a simple example like this, but is also a very interesting function of HTML_AJAX and shows its usefulness in bigger projects.

If our method does not produce any errors, we can now go further to implement the Suggest example on the client side:

<html>
<head>
  <title>HTML_AJAX Suggest</title>
  <script type='text/javascript'
    src='auto_server.php?client=all&stub=suggest'></script>
  <script>
   function do_suggest() {
      remoteSuggest.getstring(document.getElementById('string').value);
   }
   // create a javascript hash to hold our callback methods
   var suggestCallback = {
      getstring: function(result) {
         document.getElementById('suggestions').innerHTML = result;
      }
   }
   // create our remote object. Note the lowercase mapping. This is because in
   // php4, classes and functions have no case. in the server you can
   // register each function to add the case back
   var remoteSuggest = new suggest(suggestCallback);
  </script>
</head>
<body>
  <div>
    Type in a PEAR Package:
    <input type="text" name="string" id="string" size="20" onkeyup="
      do_suggest(); return false;">
    <input type="button" name="check" value="suggest..." onclick="do_suggest();
      return false;">
  </div>
  <div id="suggestions"> </div>
</body>
</html>

First, we include the JavaScript for the AutoServer class. Then, we create the JavaScript code for sending a request, do_suggest(), and the callback function for displaying the results. Finally, we create a new instance of our AJAX engine.

The rest of our first example is a simple form with a single input field and a <div> element for showing the results. After adding the event handler onkeyup="do_suggest(); return false;" to the input, the function do_suggest() is called after every keypress. The handler onkeypress would be too early.

How does the script work?

After changing the value of the input field, the function do_suggest() is called. This in turn calls the remoteSuggest.getstring() method of the HTML_AJAX JavaScript class. HTML_AJAX manages the conversation with the server. Next, the server replies with an array of possible suggestions. Then, HTML_AJAX calls the callback function, passing the data from the server.

Then the callback function does the DOM manipulation and outputs all suggestions in the <div> element.

Now we have a working example, but it is not ideal. The auto-complete function of the client's browser will interfere with the application's usability. We can switch it off easily by adding the attribute autocomplete="off" to the input field. Second, the suggestion list is not visually appealing. We will improve our callback function as shown below:

var suggestCallback = {
   getstring: function(resultSet) {
      var resultDiv = document.getElementById('suggestions');
      resultDiv.innerHTML = '';
      for(var f=0; f<resultSet.length; ++f){
         var result=document.createElement("span");
         result.innerHTML = resultSet[f];
         resultDiv.appendChild(result);
      }
   }
}

After emptying the <div> element for results (resultDiv), each result is wrapped in a <span> element for better formatting and added to the <div> element in a for loop. This is done using the JavaScript methods result=document.createElement("span") and appendChild().

For better layout we define some CSS:

* {
  padding: 0;
  margin: 0;
  font-family : Arial, sans-serif;
}
#suggestions {
  max-height: 200px;
  width : 306px;
  border: 1px solid #000;
  overflow : auto;
  margin-top : -1px;
  float : left;
}
#string {
  width : 300px;
  font-size : 13px;
  padding-left : 4px;
}
#suggestions span {
  display: block;
}

The last section is the most important, so that each suggestion span is shown one below the other.

The next step is to initially hide the <div> element for results by adding "display: none" in the CSS file, and to set it to visible when we receive results to display. Thus, we add the following callback method:

resultDiv.style.display='block';
if (!resultSet)
  resultDiv.style.display='none';

This will also hide our <div> element when the server does not know what to suggest.

How can we make the example more interactive? We can see the suggestions, but cannot choose from the list. To add more interactivity to the results, we can add event handlers to each span:

result.onmouseover = highlight;
result.onmouseout = unHighlight;
result.onmousedown = selectEntry;

This adds a JavaScript function to each event we want to define. highlight() and unHighlight() simply change the CSS class of the span:

function highlight (){
  this.className='highlight';
}

Corresponding CSS class:

.highlight {
  background-color: 0000ff;
  color: fff;
}

(We have not shown the unHighlight() function. It simply removes the styling applied by the highlight() function.)

At a minimum, we want to insert the suggestion automatically into the input field by clicking on it. Since the input field has the string id, we can set its value with the following function:

function selectEntry (){
  document.getElementById('string')
    .value = this.innerHTML;
}

This replaces the value of the input field with the content of the span, which is nothing more than one result served by our AJAX server.

The layout now looks like the screenshot below:

HTML_AJAX example: Suggest

The example works, but we have too many requests sent to the server. If the user types very fast, it is possible that Request 2 is sent before the result of Request 1 is back.

To prevent this, we will use a pattern called "submission throttling". Every 350 milliseconds, the browser checks if the value in the input field has changed and, if yes, a request is submitted. If a user types very fast, we save some bandwidth and do not disturb the user while he is typing. The server then produces an ordered list of suggestions. If the input field is empty, we do not send a request, but hide the resulting <div> element.

Here is the finished Suggest example:

<html>
<head>
  <title>HTML_AJAX Suggest</title>
  <link rel="StyleSheet" type="text/css" href="suggest3.css" />
  <script type='text/javascript'
    src='auto_server.php?client=all&stub=suggest'></script>
  <script>
   var string = '';
   var oldstring = '';
   var timeout= 1000; /*milliseconds to timeout; good value is 350*/
   function do_suggest() {
      string = document.getElementById('string').value;
      if (string != oldstring) {
         /* don't send request when input field is empty */
         if (string) {
            remoteSuggest.getstring(string);
         }
         /* hide div instead */
         else {
            document.getElementById('suggestions').style.display = 'none';
         }
         oldstring = string;
      }
      window.setTimeout('do_suggest()', timeout);
   }
   // create a javascript hash to hold the callback methods
   var suggestCallback = {
      getstring: function(resultSet) {
         var resultDiv = document.getElementById('suggestions');
         resultDiv.innerHTML = '';
         resultDiv.style.display = 'block';
         if (!resultSet) resultDiv.style.display = 'none';
         else{
            for(var f=0; f<resultSet.length; ++f){
               var result=document.createElement("span");
               result.innerHTML = resultSet[f];
               result.onmouseover = highlight;
               result.onmouseout = unHighlight;
               result.onmousedown = selectEntry;
               resultDiv.appendChild(result);
            }
         }
      }
   }
   // create our remote object
   var remoteSuggest = new suggest(suggestCallback);
   // functions for interactivity
   function highlight (){ this.className = 'highlight'; }
   function unHighlight () { this.className = ''; }
   function selectEntry () {
      document.getElementById('string').value = this.innerHTML;
   }
   </script>
</head>
<body onload="do_suggest()">
  <h1>HTML_AJAX Example: Suggest</h1>
  <p>Note: timeout is set to 1000ms for better demonstration. A good choice
    for daily use is 350ms.</p>
  <div id="error"></div>
  Type in a PEAR Package:
  <form method="get" id="suggest">
    <input type="text" name="string" id="string" size="20" autocomplete="off">
    <input type="button" name="check" value="suggest..." onkeyup="do_suggest();
      return false;">
    <div id="suggestions"> </div>
  </form>
</body>
</html>

As you can see, it is pretty simple to add interactivity to your forms and applications. This example can be extended by handling key requests, letting the user navigate through result lists with the cursor, or by adding caching functionality to save bandwidth.

36 542 Users on board!

Tutorial menu

Printable

Printer Friendly version of the full article on one page with plain styles

Author(s)