结合 Ajax 进行 PHP 开发,第 1 部分: 入门

一个简单的相册

异步 JavaScript 和 XML(Asynchronous JavaScript and XML,Ajax)无疑是最流行的新 Web 技术。“结合 Ajax 进行 PHP 开发” 这个系列包括两部分,我们将完全使用 PHP 和 Simple Ajax Toolkit (Sajax) 创建一个简单的相册作为在线 Web 应用程序。我们首先用标准的 PHP 开发方法编写简单的相册,然后再用 Sajax 将其变成活动的 Web 应用程序。

Sean Kelly (skelly@idsociety.com), Web 应用程序开发人员, ID Society

Sean Kelly 毕业于 Reed College,并从那里获得了数学学位。他目前是 ID Society 的 Web 应用程序开发人员,ID Society 是一家位于纽约的提供全方位服务的 Internet 营销代理公司。他是一位开放源码内容管理系统的支持者,对 Joomla! 和 Wikipedia 项目作出了贡献。



2006 年 9 月 18 日

创建一个简单的相册

本文将使用两种方法创建一个简单的相册:传统的 Web 应用程序和基于 Sajax 的应用程序。我们将用 PHP 编写一个相册,读取某一目录中的内容,显示缩略图组成的表格。如果用户单击一个缩略图,就会完全展开该图像。因为编写的是传统应用程序,所以每次单击都会是一个新的 HTTP 请求,而参数则作为 URL 的一部分传递。

请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。

您将学习如何将 Sajax 库应用于相册,了解为何使用 Sajax 可以加快应用程序的开发。

添加一个分页器表

访问相册的用户需要某种快速查看照片的方法。因为很多大照片不容易在一页上显示,所以需要创建一个分页器 —— 每次显示少量缩略图的简单表格。还要编写导航,帮助用户在图像列表中来回移动。

图 1. 分页器提供了显示用户照片的一种方式
分页器提供了显示用户照片的一种方式

什么是 OpenAjax Alliance?

2006 年 5 月 JavaOne Conference 开始前,29 家公司的代表在 Adobe Systems 的会议室里碰头,准备大体上确定 Ajax 的未来,这个小组就称为 OpenAjax Alliance。

小组做了几项决定,其中最显著的就是给自己取了个名字:OpenAjax Alliance。它还决定不把自己组织成一个正式的标准团体,或者 Eclipse Foundation 那样的开放源码主导的组织,因此小组自身的形式暂时也是非正式的。小组同意大约每周召开一次电话会议。

OpenAjax Alliance 主要关注三个方面:通过提供互操作性降低采用 Ajax 的风险,保证 Ajax 解决方案坚持走开放标准路线和使用开放源码技术,保持 Web 的开放性。小组的第一项任务就是寻求建立和保持 Ajax 工具间互操作性的方法。

OpenAjax Alliance 包括 31 家技术公司,其中有 IBM®、Adobe Systems、Eclipse Foundation、Google、Laszlo Systems Inc.、Oracle、Red Hat Inc. 和 Zend Technologies Ltd.。

首先要收集至少 20 幅 .jpg 图片,并将它们放到一个文件夹中。每个图片还要有一个保存在单独缩略图文件夹中的缩略图。虽然可使用 GD 软件包生成缩略图(请参阅 参考资料),但本文假设已经准备好了缩略图。也可使用本文提供的照片和缩略图(请参阅 下载)。

为了完成本文的剩余部分,后面假设照片保存在 /images 子目录中,缩略图则放在 /images/thumbnails 中。可以在代码中做适当的修改。此外,我们还假定缩略图和对应的图像使用相同的名称。

分页器应该传递两个参数:start 是按照字母顺序显示的第一幅照片的索引号,step 是显示的照片数。

清单 1. 相册查看器
/*
 * 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;
}

这个表很简单,它从索引号 $limit_start 开始遍历图片列表。然后放上每个图片的缩略图,每五张图片作为一行。达到 $limit_start + $limit_step 的时候循环结束。

该表是目录列表的可视化表示,因此需要一个函数列出目录中的所有图像。清单 1 中的 get_file_list() 函数用索引数组返回 /images 目录中的所有图片列表。下面是一个示例实现。

清单 2. get_file_list 实现
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;
}

注意:本文后面还要使用 get_file_list() 函数。有一点很重要,无论何时调用该函数,返回的数组都是不变的。因为提供的实现要进行目录搜索,必须保证目录中的指定文件不会改变,每次都要按字母顺序排序。

导航的实现

虽然表格列出了目录中的一些图像,但用户还需要一种查看表格中未出现的图片的方法。要真正实现分页器的导行,则需要一套标准的链接:首页、上一页、下一页和尾页。

清单 3. 分页器导航
  // 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>';

最后还要编写 get_image_link()get_table_link() 函数,让用户将缩略图展开成完整的图像(参见清单 4)。注意,脚本 index.php(以及后面要创建的 expand.php)只在这两个函数中调用。这样就很容易改变链接的功能。事实上在下面与 Sajax 进行集成时,只有这两个函数需要修改。

清单 4. get_image_linkget_table_link 实现
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>';
}

放大图片

现在有了一个可用的分页器为用户提供一些缩略图。相册的第二项功能是允许用户单击缩略图来查看全图。get_image_link() 函数调用了 expand.php 脚本,我们现在就来编写它。该脚本传递用户希望展开的文件的索引,因此必须在此列出目录并获得适当的文件名。随后的操作就很简单了,只需创建病输出 image 标记即可。

清单 5. get_image 函数
function get_image ( $index ) {
  $images = get_image_list ( 'images' );

  // Generate navigation  

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

}

接下来还要提供与分页器类似的导航机制。“上一张” 导航到编号为 $index-1 的图像,“下一张” 导航到编号为 $index+1 的图像,“返回” 则回到分页器。

清单 6. get_image 导航
  $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>';

最后创建一个简单的 HTML 容器,将其命名为 expand.php。

清单 7. get_image 导航
<!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>

这样我们就完成了相册。用户可以看到所有的图片,而且很容易导航。自然,用户可以来回切换,甚至能通过书签功能返回喜欢的图片。

图 2. 完成的相册
完成的相册

添加 Sajax

现在相册提供了基本的导航功能,目录中的图像添加了索引。下面您将看到添加 Sajax 能够改进编程和用户体验。

这里假设您对 Ajax 有基本的了解,最好还熟悉 Sajax 的基础知识(请参阅 参考资料 中的教程)。

Sajax、Ajax 与传统 Web 应用程序

现在我们已经使用标准的 Web 开发模型开发了应用程序。两项主要功能是分页器和图像查看器,它们分别对应不同的 PHP 文件。参数作为 HTTP GET 请求传递给脚本,脚本直接向 Web 客户机返回页面。

图 3. 在传统的 Web 应用程序中,三个不同的请求调用了两个页面
传统的 Web 应用程序中三个不同的请求调用两个页面

Web 开发社区的人都知道,Ajax 允许向服务器发出异步的辅助请求,并直接在网页中显示结果(如图 4 所示)。不幸的是,即便最简单的 Ajax 应用程序实现起来也是一项大任务。因为 Ajax 不是标准化的技术,Internet Explorer 和其他浏览器(如 Firefox、Safari)的实现是不同的。此外,程序员至少要编写三个函数才能实现一个功能,这三个函数是:发送 HTTP 请求的初始 JavaScript,返回响应的 PHP 脚本,以及另一个处理这些响应的 JavaScript 函数。

图 4. Ajax 应用程序负责所有的 HTTP 请求
Ajax 应用程序负责所有的 HTTP 请求

建立在 Ajax 库之上的 Sajax 通过运用简单的启发式方法大大简化了这一过程:Web 客户机需要访问的每个 PHP 函数都由 Sajax “导出”。如果有一个名为 foo_bar() 的 PHP 函数,那么 Sajax 会把该函数导出为 JavaScript 函数 x_foo_bar()。客户机对 x_foo_bar() 的任何调用都会自动转发给服务器上的 foo_bar(),输出则传递给另一个 JavaScript 函数。清单 8 中的简短页面示范了这种功能。运行这个例子需要下载 Sajax 库(请参阅 参考资料)。

清单 8. Sajax 的应用
<?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>

如果打开清单 8 中的页面,在输入框中输入一些内容然后单击 Enter,那么输入内容就会在一个警告框中显示出来。但在这个看似简单的网页背后,x_foo_bar() JavaScript 函数将远程调用 foo_bar() 函数,并把响应传递给 JavaScript 内置函数 alert()。每个 Sajax 导出函数的最后一个参数都是一个响应处理程序,负责处理 foo_bar() 的输出。

这个例子还说明了 Sajax 快速开发的另一个重要特性:不需要每个函数都有一个单独的文件,页面实际上调用的是其自身,因此更便于跟踪函数的调用(如图 5 所示)。x_foo_bar() 函数直接向页面发回 Ajax 请求,在请求中包含函数名和参数。关键是 sajax_handle_client_request() 函数,它截获所有的 Sajax 调用并自动对它们进行处理。

图 5. 使用 Sajax 客户机可通过一个页面访问服务器端的多个函数
使用 Sajax 客户机可通过一个页面访问服务器端的多个函数

将 Sajax 连接到相册

利用刚刚创建的代码,我们将用 Sajax 迅速把相册从多页面应用程序转化成活动的 Ajax 应用程序。

因为相册主要有两个函数,get_table()get_image(),这也是需要用 Sajax 导出的全部函数。事实上,为了通过 Sajax 调用这些函数,这些函数本身基本上不需要修改,很快我们就会看到,我们只需要修改生成的链接即可。

清单 9. Sajax 相册的头部
<?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();
?>

对于本文而言,文档主体部分很简单。我们将使用 divwindowid 来显示服务器的输出。

清单 10. 显示服务器输出的 divwindowid
<body>
<h1>Sajax photo album</h1>
<div id="window"></div>
</body>

最后还要编写 JavaScript 回调函数。该例中,因为所有的服务器输出都直接输出到 windowdiv 标记,所以可以重复使用简单的回调函数。将回调函数添加到 Sajax 函数调用中,就可以得到头(head)。

清单 11. 简单的头
<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>

最后一步是保证应用程序中的所有链接都是自定义的 Sajax 调用。只需要取上一节中的代码并作如下替换:href="index.php?start=0&step=5" 变为 onclick="x_get_table(0, 5, to_window)"href="expand.php?index=0" 变为 onclick="x_get_image(0, to_window)"

并在相应的函数中做同样修改:get_image_link()get_table_link()。这样向 Sajax 的转化就完成了(如图 6 所示)。所有链接都变成了与远程 PHP 调用对应的 JavaScript 调用,PHP 使用 JavaScript 响应处理程序 to_window() 直接输出到页面。

整个应用程序都包含在一个页面中,还可以把其余功能(get_table()get_image() 等)放在不能从 Web 访问的单独的库文件中。在大多数 Ajax 应用程序中,每个发往服务器的请求都需要由单独的脚本处理,或至少需要编写一个非常庞大的处理程序脚本来重定向请求。将所有这些文件都集中到一起可能非常麻烦。使用 Sajax 永远只需要一个文件,在该文件中只需定义我们使用的函数即可。Sajax 代替了处理程序脚本。

图 6. 完成的基于 Sajax 的相册(缩略图)
完成的基于 Sajax 的相册

可以看到 URL 仍然保持不变,并带来了更多愉快的用户体验。windowdiv 显示在一个灰色的框中,通过 Sajax 生成的内容非常清楚。脚本不一定要知道自身或者它在服务器上的位置,因为所有的链接最终都成为直接对页面自身的 JavaScript 调用。因此我们的代码能够很好的模块化。我们只需要保持 JavaScript 和 PHP 函数在同一个页面上即可,即使页面位置发生了变化也没有关系。


扩展相册

使用 Sajax 把我们的相册变成活动的 Web 应用程序如此轻而易举,我们要再花点时间添加一些功能,进一步说明 Sajax 如何使从服务器检索数据变得完全透明。我们将为相册添加元数据功能,这样用户就能为他们的图片添加说明。


元数据

没有上下文说明的相册是不完整的,比如照片的来源、作者等。为此我们要将图像集中起来创建一个简单的 XML 文件。根节点是 gallery,它包含任意多个 photo 节点。每个 photo 节点都通过其 file 属性来编号。在 photo 节点中可以使用任意多个标记来描述照片,但本例中只使用了 datelocalecomment

清单 12. 包含元数据的 XML 文件
<?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>

文件的解析不在本文讨论范围之列。我们假设您能够熟练使用 PHP 中众多 XML 解析方法中的一种。如果不熟悉的话,建议阅读 参考资料 中的文章。我们不再浪费时间解释如何将该文件转化成 HTML,作为一个练习,读者可以自己了解下面的代码如何使用 XML 文件并生成 HTML。清单 13 中的代码使用了 PHP V5 中自带的 SimpleXML 包。

清单 13. 元数据函数
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;

要注意的是,get_meta_data() 函数中还使用 getimagesize()(一个核心 PHP 函数,不需要 GD)计算图像的大小。

再回到 get_image() 函数,它包含由 get_image_list() 生成的文件名的列表。查找元数据只需要将文件名传递给该函数即可。

清单 14. 添加元数据
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;
}

重新打开页面将看到服务器请求的结果。图 7 显示了带有元数据的放大的图像。

图 7. 使用元数据的相册
使用元数据的相册

结束语

我们看到,使用 Sajax 可以消除客户机和服务器之间的障碍,程序员能够进行无缝远程函数调用而不用担心传输层、HTTP GETPOST 请求。我们可以花更多时间编写提供数据的 PHP 脚本以及表示层和控制层的 JavaScript。在这个相册例子中,我们让客户机直接连接到图像数据库。通过添加简单的元数据,我们看到让用户直接访问服务器上的信息是多么简单,无需担心协议的问题。

与所有的 Ajax 应用程序一样,我们的相册也有一个致命的弱点:没有使用浏览器的 “访问历史”,因为破坏了后退按钮的功能。在 “结合 Ajax 进行 PHP 开发” 系列的 第 2 部分 中,我们将通过实现历史记录缓冲和状态跟踪机制来解决这个问题。


下载

描述名字大小
Part 1 source codeos-php-rad1.source.zip1.5MB

参考资料

学习

获得产品和技术

  • Sajax Toolkit 由 modernmethod 维护。整个库都包含在一个文件中,您可以下载 最新的 0.12 版
  • script.aculo.us 是另一种简单的 Ajax 工具包,以容易创建引人注目的视觉效果而闻名,理论上它能够与 Sajax 一起使用。
  • Flickr 是广受欢迎的在线图片库,它充分利用了 Ajax 的功能。上传图片可以使用元数据标记,图片还可以包含附加到特定图片区域的注释,所有功能都在该应用程序中完成。
  • JavaScript Object Notation (JSON) 是在 PHP 和 JavaScript 之间传输比字符串数据更复杂的信息的一种方法。JSON 或者类似的系统对于大型 Ajax 应用程序是不可或缺的。
  • 从 IBM alphaWorks 下载 AJAX Toolkit Framework 的测试版。
  • 本文中使用的所有图片都来自 Wikimedia Commons,其作者同意将这些图片发布到公共领域。
  • 使用 IBM 试用版软件 改进您的下一个开放源码开发项目,可通过下载或通过订阅免费的 IBM 软件试用版 DVD 获得这些软件。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Web development
ArticleID=160306
ArticleTitle=结合 Ajax 进行 PHP 开发,第 1 部分: 入门
publish-date=09182006