Developing PHP the Ajax way, Part 1: Getting started

A simple photo album

Asynchronous JavaScript and XML (Ajax), is arguably the most popular new Web technology. In this two-part "Developing PHP the Ajax way" series, you will create a simple photo album as an online Web application, entirely in PHP and the Simple Ajax Toolkit (Sajax). You'll begin by writing a simple photo album using the standard method of PHP development and later apply Sajax to make it an active Web application.

Share:

Sean Kelly (skelly@idsociety.com), Web Application Developer, ID Society

Sean Kelly graduated with a degree in mathematics from Reed College. He is currently a Web application developer for ID Society, a full-service Internet marketing agency in New York City. He is a supporter of open source content management systems, and contributes to Joomla! and the Wikipedia project.



30 May 2006

Also available in Chinese

Creating a simple photo album

In this article, you will create a simple photo album using two methods: a traditional Web application and a Sajax-powered application. You will write an album in PHP that reads the contents of a directory, showing the user a table of thumbnails. If the user clicks a thumbnail, the image will expand to full size. Since you will be writing a traditional application, every click will be a new HTTP request, with parameters being passed as part of the URL.

You will learn how to quickly apply the Sajax library to your album and find out why using Sajax will speed up application development.

Adding a pager table

A user visiting your album will need to have some way to quickly view your photos. Since many large photos cannot be easily viewed on one page, you need to create a pager -- a simple table that displays a few thumbnail images at a time. You will also write navigation to help the user mover forward and backward through the image list.

Figure 1. Pager will provide way to show user photos
Pager will provide way to show user photos

What is the OpenAjax Alliance?

In the hours before the 2006 JavaOne Conference in May, representatives from 29 companies met in conference rooms at Adobe Systems, trying to determine the future of Ajax in general, and their group, known as the OpenAjax Alliance, in particular.

The group made several decisions, including the most visible one, to give itself a name: the OpenAjax Alliance. It also decided not to make itself a formal standards body, or open source hosting organization, such as the Eclipse Foundation, and leave its structure informal for now. The group agreed to meet once a week or so in a teleconference.

The OpenAjax Alliance will focus on three areas: to decrease the risk of Ajax adoption by providing interoperability, to ensure that Ajax solutions adhere to open standards and use open source technology, and to preserve the open nature of the Web. The group's first task is to find ways to establish and maintain interoperability among Ajax tools.

The OpenAjax Alliance consists of 31 technology companies, including IBM®, Adobe Systems, the Eclipse Foundation, Google, Laszlo Systems Inc., Oracle, Red Hat Inc., and Zend Technologies Ltd.

Before you begin, collect at least 20 .jpg images and place them into a folder. You should also have one thumbnail per image, saved in a separate thumbnails folder. Though you could use the GD package to generate the thumbnails (see Resources), for this article, you are expected to have the thumbnails already prepared. Alternatively, you can use the photos and the thumbnails provided with this article (see Download).

For the remainder of this article, it is assumed that your photos are located in an /images subdirectory, with the thumbnails are located in /images/thumbnails. You can change these at the appropriate points in the code. Also, it is assumed that your thumbnails share the same name as their corresponding images.

Your pager will be passed two parameters: start, which will be the numeric index of the first photo to show alphabetically; and step, which will be the number of photos to show.

Listing 1. The beginnings of your album viewer
/*
 * Find a list of images in /images and provide thumbnails
 */
function get_table ( $limit_start = 0, $limit_step = 5 ) {
  $images = get_image_list('images');

  // Generate navigation for Previous and Next buttons
  // Code given below

  $output .= '<table class="image_table">';
  $columns        = 5;
  foreach ($images as $index => $image) {

    // Begin directory listing at item number $limit_start
    if ( $index < $limit_start ) continue;

    // End directory listing at item number $limit_end
    if ( $index >= $limit_start + $limit_step ) continue;

    // Begin column
    if ( $index - $limit_start % $columns == 0 ) {
      $output .= '<tr>';
    }

    // Generate link to blown up image (see below)
    $thumbnail = '<img src="thumbnails/' . $image . '" />';
    $output .= '<td>' . get_image_link($thumbnail, $index) 
. '</td>';
    
    // Close column
    if ( $index - $limit_start % $columns == $columns - 1 ) {
      $output .= '</tr>';
    }
  }
  
  $output .= '</table>';
  
  return $nav . $output;
}

This basic table simply loops through our list of images starting at index number $limit_start. It then lays out a thumbnail of every image until it reaches every fifth image and starts a new row. The loop then completes when $limit_start + $limit_step is reached.

Since the table is a visual representation of a directory listing, we need a function to list all the images in a directory. In Listing 1, get_file_list() is a function that returns a list of all pictures in the /images directory as an indexed array. A sample implementation is provided below.

Listing 2. get_file_list implementation
function get_image_list ( $image_dir ) {
  $d     = dir($image_dir);
  $files = array();
  if ( !$d ) return null;

  while (false !== ($file = $d->read())) {
    // getimagesize returns true only on valid images
    if ( @getimagesize( $image_dir . '/' . $file ) ) {
      $files[] = $file;
    }
  }
  $d->close();
  return $files;
}

Note: The get_file_list() function will be reused later in this article. It is important that whenever it is called, the array it returns does not change. Since the provided implementation is doing a directory search, you must simply ensure that the specific files in the directory do not change, and that you are sorting alphabetically each time.

Coding the navigation

Though your table lists a set number of images in a directory, the user needs some way to view any images that don't fit into the table. To actually navigate the pager, you need to include a standard set of links: beginning, previous, next, and last.

Listing 3. Pager navigation
  // Append navigation
  $output = '<h4>Showing items ' . $limit_start . '-' .
            min($limit_start + $limit_step - 1, count($images)) .
            ' of ' . count($images) . '<br />';

  $prev_start = max(0, $limit_start - $limit_step);
  if ( $limit_start > 0 ) {
    $output .= get_table_link('<<', 0, $limit_step);
    $output .= ' | ' . get_table_link('Prev',
               $prev_start, $limit_step);
  } else {
    $output .= '<< | Prev';
  }

  // Append next button
  $next_start = min($limit_start + $limit_step, count($images));
  if ( $limit_start + $limit_step < count($images) ) {
    $output .= ' | ' . get_table_link('Next',
               $next_start, $limit_step);
    $output .= ' | ' . get_table_link('>>',
               (count($images) - $limit_step), $limit_step);
  } else {
    $output .= ' | Next | >>';
  }

  $output .= '</h4>';

Finally, write the get_image_link() and the get_table_link() functions, which provide a way for the user to expand the thumbnail into a full photograph (see Listing 4). You should note that the script, index.php (and expand.php, which will be created later), is only referenced within these two functions. By doing this, it'll be easy to change the functionality of the links. In fact, when you integrate Sajax next, these two functions will be the only things that need to change.

Listing 4. get_image_link, get_table_link implementation
function get_table_link ( $title, $start, $step ) {
      $link = "index.php?start=$start&step=$step";
      return '<a href="' . $link . '">' . $title .'</a>';
}

function get_image_link ( $title, $index ) {
      $link = "expand.php?index=$index";
      return '<a href="' . $link . '">' . $title . '</a>';
}

Blowing up images

You should now have a functioning pager that provides the user a list of thumbnails. The second function of your album is allowing the user to click on a thumbnail and get a blown-up version of the photo. In the get_image_link() function, you're directing the user to the expand.php script, which you will now write. The script is passed the index of the file the user wants to expand, so it has to list the directory again and get the appropriate file name. It then simply creates an image tag and outputs it.

Listing 5. The get_image function
function get_image ( $index ) {
  $images = get_image_list ( 'images' );

  // Generate navigation  

  $output .= '<img src="images/' . $images[$index] . '" />';
  return $output;

}

Next, you must provide navigation to the user similar to what you used in the pager. Provide a previous link that navigates to image number $index-1, another link that navigates to image number $index+1, and an up link that returns to the pager.

Listing 6. The get_image navigation
  $output .= '<h4>Viewing image ' . $index .
             ' of ' . count($images) . '<br />';
  
  if ( $index > 0 ) {
    $output .= get_image_link('<<', 0);
    $output .= ' | ' . get_image_link('Prev', $index-1);
  } else {
    $output .= '<< | Prev';
  }
  
  $output .= ' | ' . get_table_link('Up', $index, 5);
  
  if ( $index < count($images) ) {
    $output .= ' | ' . get_image_link('Next', $index+1);
    $output .= ' | ' . get_image_link('>>', count($images));
  } else {
    $output .= ' | Next | >>';
  }
  
  $output .= '</h4>';

Finally, create a simple HTML container and name it expand.php.

Listing 7. The get_image navigation
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Creating a simple picture album viewer</title>

<style type="text/css">
body { text-align: center }
table.image_table { margin: 0 auto 0 auto; width: 700px;
padding:10px; border: 1px solid #ccc; background: #eee; }
table.image_table td { padding: 5px }
table.image_table a { display: block; }
table.image_table img { display: block; width: 120px;
padding: 2px; border: 1px solid #ccc; }
</style>

</head>
<body>

<h1>Creating a simple picture album viewer</h1>
<?php

$index = isset($_REQUEST['index']) ? $_REQUEST['index'] : 0;
echo get_image($index);

?>
</body>
</html>

You have now completed your photo album. The user gets full access to your photos and can navigate them easily. Naturally, the user can move forward and backward, and can even return to a favorite one by bookmarking it.

Figure 2. The completed album
The completed album

Attaching Sajax

Your photo album features a basic navigation, with photos indexed from a directory. Here, you will see how adding Sajax can improve the programming and user experiences.

It is assumed that you have a basic knowledge of Ajax. It is also recommended that you are familiar with the basics of Sajax (see Resources for a tutorial).

Sajax vs. Ajax vs. traditional Web applications

Up to this point, you've been developing your application using a standard model of Web development. Each of the two main functions -- the pager and the image viewer -- correspond to two separate PHP files. Parameters are passed as an HTTP GET request to the script, which returns a page directly to the Web client.

Figure 3. In a traditional Web application, three separate requests call two pages
In a traditional Web application, three separate requests call two pages

Ajax, as the Web development community well knows, allows you to make asynchronous auxiliary requests to the server and display the resulting output directly on the Web page (see Figure 4). Unfortunately, even implementing the simplest application in Ajax can be a large task. Since Ajax is not a standardized technology, it is implemented differently by Internet Explorer and alternate browsers, such as Firefox and Safari. Further, the programmer has to write at least three functions to implement a single feature: the initial JavaScript that makes the HTTP request; the PHP script that returns a response; and a second JavaScript function, which handles the response.

Figure 4. Ajax application takes care of all HTTP requests
Ajax application takes care of all HTTP requests

Sajax, above other Ajax libraries, rapidly speeds this process by applying a simple heuristic: Every PHP function that needs to be accessed by the Web client is "exported" via Sajax. If you have a PHP function named foo_bar(), Sajax exports the function to the JavaScript function x_foo_bar(). Any calls made by the client to x_foo_bar() are automatically redirected to foo_bar() on the server, and the output is passed to another JavaScript function. In Listing 8, you will see a short page that demonstrates this functionality. To run it, you need to download the Sajax library (see Resources).

Listing 8. Sajax in action
<?php
require("Sajax.php");

function foo_bar ( $param ) {
  return "You typed: $param";
}

$sajax_request_type = "GET";   // Set HTTP request type to GET
sajax_init();                  // Prepare Sajax
sajax_export("foo_bar");       // foo_bar can now be called by client
sajax_handle_client_request(); // Discussed below
?>
<html>
<head>
  <script language="javascript">
  <? sajax_show_javascript(); ?>
  </script>
</head>
<body>
  <form onSubmit="x_foo_bar(this.input.value, alert);return false;">
  <input type="text" name="input" />
  </form>
</body>
</html>

If you visit the page in Listing 8, type in the input box, and click Enter, your input will be echoed back to you in an alert box. However, behind the scenes of this seemingly basic page, the x_foo_bar() JavaScript function is remotely calling the foo_bar() function and passing the response to the built-in JavaScript alert() function. The final argument of every exported Sajax function is a response handler that handles the output of foo_bar().

This example also illustrates another feature of rapid development with Sajax: Instead of having separate files for each function, the page is actually calling itself, which makes it much easier to keep track of your functions (see Figure 5). The x_foo_bar() function makes an Ajax request directly back to the page, encoding the function name and parameters in the request. The key is the sajax_handle_client_request() function, which intercepts any Sajax calls and processes them automatically.

Figure 5. Using Sajax, a single file can give the client access to multiple functions on the server side
Using Sajax, a single file can give the client access to multiple functions on the server side

Connecting Sajax to your photo album

Using the code we just created, you will use Sajax to quickly convert your album from a multiple page application to an active Ajax application.

Since our photo album has two main functions, get_table() and get_image(), these are the only functions you need to export using Sajax. In fact, the functions barely have to change in order to be called via Sajax, and as you will see shortly, we only have to change the links that are generated.

Listing 9. Sajax photo album header
<?php
require("Sajax.php");

function get_image () { }        // Defined later
function get_thumbs_table () { } // Defined later

// Standard Sajax stuff.  Use Get, and export two
// main functions to javascript
$sajax_request_type = "GET";
sajax_init();
sajax_export("get_thumbs_table", "get_image");
sajax_handle_client_request();
?>

The body will be basic for the sake of this article. A single div with an id of window will be used for displaying output from the server.

Listing 10. Single div with id of window for displaying server output
<body>
<h1>Sajax photo album</h1>
<div id="window"></div>
</body>

Finally, you must write the JavaScript callback functions. In our example, since all output from the server is being output directly into our windowdiv tag, you can reuse the simple callback twice. Add in the Sajax function call, and you obtain the head.

Listing 11. Simple head
<head>
<title>Creating a Sajax photo album</title>
<style type="text/css">
body { text-align: center }
div#window { margin: 0 auto 0 auto; width: 700px;
  padding: 10px; border: 1px solid #ccc; background: #eee; }
table.image_table { margin: 0 auto 0 auto; }
table.image_table td { padding: 5px }
table.image_table a { display: block; }
table.image_table img { display: block; width: 120px
  padding: 2px; border: 1px solid #ccc; }
img.full { display: block; margin: 0 auto 0 auto;
  width: 300px; border: 1px solid #000 }
</style>

<script language="javascript">
<? sajax_show_javascript(); ?>

// Outputs directly to the "window" div
function to_window(output) {
     document.getElementById("window").innerHTML = output;
}

window.onload = function() {
      x get table to window);
};

</script>
</head>

The final step is to ensure that all links in the application are your custom Sajax calls. This simply involves taking your code from the previous section and making the following substitutions: href="index.php?start=0&step=5" becomes onclick="x_get_table(0, 5, to_window)", and href="expand.php?index=0" becomes onclick="x_get_image(0, to_window)".

You make these changes in the appropriate functions: get_image_link() and get_table_link(). Just like that, your conversion to Sajax is complete (see Figure 6). Every link is now a JavaScript call, corresponding to a remote PHP call, which in turn is output directly to the page using the JavaScript response handler to_window().

Our entire application is contained within a single Web page, while all the functionality (get_table(), get_image(), etc.) can be included in a separate library file inaccessible from the Web. In most Ajax applications, each request to the server needs to be handled by a separate script -- or at the very least, a single very large handler script needs to be written to redirect requests. Keeping all of these files together can be a pain. With Sajax, we never need more than one file, which only needs to define the functions we are using. Sajax acts as our handler script.

Figure 6. Our completed Sajax-powered photo gallery (thumbnail view)
Our completed Sajax-powered photo gallery

Notice that the URL remains constant, maintaining a much more pleasant user experience. The windowdiv is illustrated with a gray box, highlighting what's being generated via Sajax. Our script does not have to be aware of itself or its location on the server, since every link ends up being a JavaScript call directly to the page itself. This makes our code extremely modular. We only need to keep our JavaScript and PHP functions together on the same page, even though the page location may change.


Expanding your photo album

Making our photo album an active Web application was such a breeze with Sajax, let's take a moment to add some extra functionality to help illustrate how Sajax makes retrieving data from the server completely transparent. We will implement a metadata feature for our album, so users can add descriptions to their photos.


Metadata

No photo album is complete without a contextual description of where the photo was taken, by whom, etc. To implement this, we collect our images and create a simple XML file. The root node will be gallery, which contains any number of photo nodes. Each photo node is indexed by its file attribute. Within the photo node, any number of tags can describe the photograph, but in this example, we settle on date, locale, and comment.

Listing 12. A sample XML file containing metadata
<?xml version="1.0"?>
<gallery>
  <photo file="image01.jpg">
    <date>August 6, 2006</date>
    <locale>Los Angeles, CA</locale>
    <comment>Here's a photo of my favorite celebrity</comment>
  </photo>
  <photo file="image02.jpg">
    <date>August 7, 2006</date>
    <locale>San Francisco, CA</locale>
    <comment>In SF, we got to ride the street cars</comment>
  </photo>
    <photo file="image03.jpg">
    <date>August 8, 2006</date>
    <locale>Portland, OR</locale>
    <comment>Time to end our road trip!</comment>
  </photo>
</gallery>

Parsing the file is outside the scope of this article. We will assume that you have sufficient experience in one of the many methods of parsing XML in PHP. If you are not yet familiar with it, we recommend an article in Resources. Instead of spending time explaining how to convert this file into styled HTML, it is left as an exercise for you to see how this code takes the XML file and generates HTML. This code in Listing 13 uses the SimpleXML package built into PHP V5.

Listing 13. The metadata function
function get_meta_data ( $file ) {

  // Using getimagesize, the server calculates the dimensions
  list($width, $height) = @getimagesize("images/$file");
  $output = "<p>Width: {$width}px, Height: {$height}px</p>";

  // Use SimpleXML package in PHP_v5:
  // http://us3.php.net/manual/en/ref.simplexml.php
  $xml = simplexml_load_file("gallery.xml");
  
  foreach ( $xml as $photo ) {
    if ($photo['id'] == $file) {
      $output .= !empty($photo->date)    ? "<p>Date taken:
  {$photo->date}</p>"    : '';
      $output .= !empty($photo->locale)  ? "<p>Location:
   {$photo->locale}>/p>"  : '';
      $output .= !empty($photo->comment) ? "<p>Comment:
   {$photo->comment}</p>" : '';
    }
  }

  return $output;

Note that in the get_meta_data() function, you also used getimagesize() (a core PHP function; GD not required) to calculate the dimensions of the image.

We now return to the get_image() function, which has a list of file names generated by get_image_list(). To locate the metadata, simply pass this filename to your function.

Listing 14. Adding the metadata
function get_image ( $index ) {
      $images = get_image_list ( 'images' );

      // ...

      $output .= '<img src="images/' . $images[$index] . '" />';
      $output .= '<div id="meta_data">' .
                 get_meta_data( $images[$index] ) . '</div>';
      return $output;
}

Revisit the page, and you should see the result of the server requests. Figure 7 illustrates a blown-up photo with metadata.

Figure 7. Our photo album utilizes metadata
Our photo album utilizes metadata

Summary

By using Sajax, we have seen how we can eliminate the barrier between the client and server, and allow the programmer to seamlessly make remote function calls without worrying about the transportation layer, HTTP GET and POST requests. Instead, we spent most of our time writing the PHP scripts to provide data, and JavaScript for the presentation and control layers. In the example of our photo album, we gave the client a direct connection to a database of images. And by adding a simple metadata feature, we see how easy it can be to give the user direct access to information on the server, without worrying about protocol.

As with all Ajax applications, our photo album has a major fatal flaw: Our browser's history is not being used, and, thus, we have broken the Back button. In Part 2 of this "Developing PHP the Ajax way" series, we will solve this issue by implementing a history-caching and state-tracking mechanism.


Download

DescriptionNameSize
Part 1 source codeos-php-rad1.source.zip1.5MB

Resources

Learn

Get products and technologies

  • The Sajax Toolkit is maintained by modernmethod. Since the entire library is contained within a single file, you can get current version 0.12.
  • script.aculo.us is another simple Ajax toolkit, popular due to its ability to easily create attractive visual effects and could in theory be used alongside Sajax.
  • Flickr is a popular online photo album fully utilizing Ajax. Uploaded photos can be tagged with metadata, and photos can have notes attached to specific regions of the photo, all done completely within the application.
  • JavaScript Object Notation (JSON) is a method of transporting more complex information than simple string data between PHP and JavaScript. JSON, or a similar system, is requisite for any large Ajax application.
  • Get the alpha version of AJAX Toolkit Framework from IBM alphaWorks.
  • All photographs used in this article were found at Wikimedia Commons and were released to the public domain by their creators.
  • Innovate your next open source development project with IBM trial software, available for download or on DVD.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=111875
ArticleTitle=Developing PHP the Ajax way, Part 1: Getting started
publish-date=05302006