像专业人员一样开发 Ajax 应用程序,第 1 部分: 使用 Prototype JavaScript 库和 script.aculo.us

目前,Web 应用程序开发几乎等同于 Ajax 开发。Ajax 不再是在特殊情况下才添加到应用程序的补充物了。它现在已经成为 Web 开发不可或缺的一部分。对于某些人而言,用 Ajax 增强应用程序曾经是一项极具挑战的任务。处理跨浏览器限制、编写大量复杂的 JavaScript 以及熟悉其中的数字编码,这些只不过是 Ajax 开发人员所面临的挑战的一小部分。还好,目前已经有几种开源 JavaScript 库,使上述操作更加容易。在这个包含三个部分的系列文章的第 1 部分,您将使用 Prototype JavaScript 库和 script.aculo.us 创建一个用来管理歌曲的 Ajax 应用程序。

Michael Galpin (mike.sr@gmail.com), 软件架构师, eBay

Michael Galpin 从 90 年代末就开始开发 Web 应用程序,他获得了 California Institute of Technology 的数学学位,是加利福尼亚州圣何塞市 eBay 公司的架构师。



2008 年 6 月 10 日

本系列文章包含三个部分,使用两个独立的开源项目(Prototype JavaScript 库和 script.aculo.us)为 Web 2.0 站点创建优秀的 Ajax 应用程序。在此系列文章的第 1 部分,我们先介绍 Prototype JavaScript 库(参见 参考资料 获得有关链接)。本文使用的是 Prototype 当前的最新版本 1.6.0.2(参见 参考资料)。Ajax 涉及到动态数据,所以需要用到服务器端的技术。在本文中,我们将 PHP 5.2.1 与 Apache 2.0.59 和 MySQL 5.0.41 结合起来一起使用。(参见 参考资料)。当然,您也可以选择自己的编程语言、Web 服务器和数据库。

developerWorks Ajax 资源中心
查看 Ajax 参考资料中心, 您可以从这个一站式站点获得关于开发 Ajax 应用程序的免费工具、代码和信息。由 Ajax 专家 Jack Herrington 主持的 活动 Ajax 社区论坛 将提供与其他 Ajax 应用程序开发人员交流的机会,帮助您解决困难。

Prototype 简介

如果进行查找,可以找到很多 JavaScript 库。原因有两个:首先,JavaScript 是浏览器语言,因此也是软件开发的关键部分。许多人都在编写 JavaScript 代码,所以就有很多 JavaScript 库存在。其次,JavaScript 很复杂,不同浏览器间的差异常常使 JavaScript 开发多少有些痛苦。幸运的是,JavaScript 库通常都提供了各种抽象来减轻这种痛苦。Prototype 就是这样的 JavaScript 库。

Prototype 是一种相当宽泛的库,具有很多功能。它的功能可以简化普通任务,并侧重于 Ajax。Prototype 提供一种很酷的方式,实现了在 JavaScript 内继承 Java™ 和 C++ 风格、对 HTML DOM 元素的扩展以及用于 JSON 的实用工具。在本文中,您将重点学习 Prototype 能为 Ajax 做些什么,同时还会了解几个 Prototype 的其他功能。


使用 Prototype 的 Ajax 库

Prototype 具有很多为了帮助您学习 Ajax 开发而设计的功能。Prototype 如此受欢迎的原因之一就是它不限制您如何进行 Ajax 编程。比如,有两种常用的模式可以响应 XMLHttpRequest(Ajax 内的底层机制):一种方式是使用用来重绘部分屏幕的 HTML 进行响应;一种是用数据进行响应,而将解析数据和重绘留给其他的 JavaScript 代码处理。Prototype 支持这两种模式。让我们来看看它是如何启用第一种模式的,即用 HTML 响应。

处理 HTML

通过 Prototype,在 Ajax 响应上处理服务器生成的 HTML 十分容易。它有一个专门为此构建的 API,并允服务器端代码只生成 HTML 片断,而不要求任何特定于 Prototype 的格式。学习这个知识的最好途径是观察其实际运行。

在示例中,我们在构建一个用来管理歌曲的应用程序。该应用程序需要用到数据库。清单 1 给出了创建此数据库的脚本:

清单 1. 创建数据库的脚本
CREATE TABLE 'songs' (
  'id' int(11) NOT NULL auto_increment,
  'title' varchar(120) NOT NULL,
  'artist' varchar(120) NOT NULL,
  'genre' varchar(80) default NULL,
  'album' varchar(120) default NULL,
  'year' year(4) default '2008',
  PRIMARY KEY  ('id')
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=latin1

如前所述,我使用的是 MySQL,但您轻松地调整此脚本使它适用于另一个数据库。使用提供的脚本创建一些示例数据(参见 下载 部分)。

现在让我们创建一个纯 HTML 页面来查看音乐库,如清单 2 所示:

清单 2. index.html 页面
<html>
     <head>
          <title>MyTunes Library</title>
          <script type="text/javascript" src="prototype.js"></script>
          <script type="text/javascript">
               function loadTunes(){
                    new Ajax.Updater('tunesBox', 'list.php', { method: 'get' });
               }
          </script>
          <link  rel="stylesheet" href="tunes.css" type="text/css"/>
     </head>
     <body onload="loadTunes();">
          <div id="pageTitle">MyTunes</div>
          <div id="tunesBox">
               <img id="spinner" src="wait_spinner.gif" height="33" width="33"/>
          </div>
     </body>
</html>

接下来的事情将会很有意思。清单 2 所示的页面是静态的纯 HTML 和 JavaScript 代码。这意味着浏览器可以缓存这个页面从而减轻服务器的负荷。当加载该页面时,将调用 JavaScript 函数 loadTunes()。您将在此处使用 Prototype,尤其它的 Ajax.Updater 对象。Prototype 处理创建适合于浏览器的传输机制(用于发送 Ajax 请求)的任务。这个任务曾经是个大事情,但由于 Prototype 这样的库的流行,现在已经没有这种感觉了。

此对象(清单 2)接受页面上一个元素的 ID、Web 服务器的一个 URL 和一个表示选项的映射键值对。对于这种情况,我们设置的 ID 是 tunesBox。可以看到页面上有一个具有此 ID 的 div。我们设置的 URL 是 list.php。Prototype 将会调用这个 URL、接受其响应并将其转储到 tunesBox div。最后,我们指定惟一选项是使用 HTTP GET 而不是(默认)的 POST。使用 POST 也可以,但在这里我们遵循 REST 标准,使用 GET,因为我们只读数据。下面是 Prototype 已经调用的 PHP 文件(参见清单 3):

清单 3. list.php 文件
<?php
  $message = "";
  $sql = "select * from songs";
  try{
     require_once(dirname(__FILE__)."/db.php");
     $result = mysql_query($sql,$conn);
     $list = array();
       if (!$result) {
         $message = "Could not successfully run query ($sql) from DB: " . mysql_error();
       } else if (mysql_num_rows($result) == 0) {
         $message = "Your library is empty!";
       } else {
         while ($row = mysql_fetch_assoc($result)){
               array_push($list, $row);
         }
       }
       mysql_free_result($result);
   } catch (Exception  $e) {
       $message = "Sorry there was an error: " . mysql_error();
   }
?>
<?php if ($message): ?>
     <div><span class="error"><?= $message ?></span></div>
<?php else: ?>
<table border="1" width="100%" cellpadding="8">
     <thead>
          <tr>
               <td>Name</td>
               <td>Artist</td>
               <td>Album</td>
               <td>Genre</td>
               <td>Year</td>
          </tr>
     </thead>
     <tbody>
     <?php foreach($list as $song): ?>
          <tr>
               <td><a href="edit.html?id=<?= $song["id"] ?>"><?= 
			                                 $song["title"] ?></a></td>
               <td><?= $song["artist"] ?></td>
               <td><?= $song["album"] ?></td>
               <td><?= $song["genre"] ?></td>
               <td><?= $song["year"] ?></td>
          </tr>
     <?php endforeach; ?>
     </tbody>
</table>
<?php endif; ?>

这是一段很简单的 PHP 脚本。它先查询之前创建的表,然后在结果集上进行迭代,创建一个 HTML 表。这里没有什么特别要说明的。将 JavaScript、CSS 和其他 PHP 文件(能在下载代码中找到)部署到 PHP 服务器并在浏览器上显示结果,如图 1 所示:

图 1. MyTunes
图 1 MyTunes

该页面将加载并无缝地使用 Ajax 和 Prototype 从服务器获得歌曲表。想要更详细地了解其中的原理,可以使用流行的 Firefox 扩展 Firebug 等工具(参见 参考资料 获得相关链接)。图 2 显示了其输出:

图 2. 针对 MyTunes 的 Firebug 控制台
针对 MyTunes 的 Firebug 控制台

单击 Net>XHR 选项卡,Prototype 将创建一个 XMLHttpRequest。它还会为响应创建 JavaScript 处理函数。该处理函数实际上使用来自 PHP 脚本的响应动态地更改 HTML DOM。从清单 2 中可以看到所有这些只由一行代码实现。再也没有比这更简单的 Ajax 开发了。如前所述,这是 Ajax 的常见模式。另一种模式是只从服务器返回数据,而不返回标记(HTML)。让我们看看 Prototype 是如何为这种方法提供帮助的。

处理数据

让服务器为 Ajax 请求返回 HTML 固然很方便,但也有缺点。由于标记和数据都由服务器返回,这会占据一定的带宽。如果 HTML 需要 JavaScript (至少事件侦听程序)附加到其上,这种方法也会变得非常复杂,使您不得不在服务器上跟踪客户机的状态。只从服务器上只返回纯数据有时简单些。同样,Prototype 也让这个过程变得很简单。让我们通过示例加以说明。

您可能已经在前面的示例中注意到,每首歌的名称实际上都是链接到另一个 edit.html 页面,如清单 4 所示:

清单 4. edit.html 页面
<html>
     <head>
          <title>Edit a Song</title>
          <script type="text/javascript" src="prototype.js"></script>
          <script type="text/javascript" src="editor.js"></script>
          <link rel="stylesheet" href="tunes.css"/>
     </head>
     <body onload="loadSong();">
          <div id="pageTitle">Edit Song</div>
          <div id="tunesBox">
               <span id="spinner">
                    <img src="wait_spinner.gif" height="33" width="33"/>
               </span>
               <form id="songForm" onsubmit="catchSubmit();">
                    <input type="hidden" name="id" id="id"/>
                    <div id="name">
                         <span id="nameLbl">Name:</span>
                         <span id="title" onclick="edit(this)"?></span>
                    </div>
                    <div id="artistDiv">
                         <span id="artistLbl">Artist:</span>
                         <span id="artist" onclick="edit(this)"?></span>
                    </div>
                    <div id="albumDiv">
                         <span id="albumLbl">Album:</span>
                         <span id="album" onclick="edit(this)"?></span>
                    </div>          
                    <div id="genreDiv">
                         <span id="genreLbl">Genre:</span>
                         <span id="genre" onclick="edit(this)"?></span>
                    </div>
                    <div id="yearDiv">
                         <span id="yearLbl">Year:</span>
                         <span id="year" onclick="edit(this)"?></span>
                    </div>                                                  
               </form>
          </div>
          <div class="backLink">
               <a href="index.html">Back to MyTunes Library</a>
          </div>
     </body>
</html>

同样,这也是个静态页面,而且是纯 HTML。当页面加载时,将调用一个称为 loadSong() 的 JavaScript 函数。该函数在单独的文件 editor.js 内。让我们来看看这个 loadSong 函数(参见清单 5):

清单 5. loadSong() JavaScript 函数
function loadSong(){
     var params = window.location.search.parseQuery();
function loadSong(){
     var params = parseQueryString();
     var id = params["id"];
     
     // create handler that will be invoked
     // when response is received from server
     var handler = function(xhr){
          // use responseJSON property added by Prototype
          var json = xhr.responseJSON;
          // check for error
          if (json.error){
               // display the error
          }
          var song = json.song;
          // clear the spinner
          // use Prototype's $() shortcut notation
          $("spinner").innerHTML = "";
          // set the display data
          $("id").value = song.id;
          $("title").innerHTML = song.title;
          $("artist").innerHTML = song.artist;
          $("album").innerHTML = song.album;
          $("genre").innerHTML = song.genre;
          $("year").innerHTML = song.year;     
     };
     
     // create options for 
     var options = {
          method : "get",
          onSuccess : handler,
          parameters : { "id" : id }
     };
     
     // send the request
     new Ajax.Request("song.php", options);
}

表达式 window.location.search 使您可以访问页面的 URL 查询字符串。在这种情况下,该字符串类似于 “?id=2”。然后,会使用一个 parseQuery() 函数。这并不是 window.location.search 对象固有的特殊函数 — 该对象只是一个字符串。parseQuery() 函数是 Prototype 添加到 JavaScript 的字符串类的函数,所以可以在任何字符串上调用。parseQueryString() 函数所执行的功能正如其名所示。它将页面 URL 的查询字符串转变成名称/值对。这就让您能够获得由前一页传递过来的 ID 参数。然后您可以使用此参数调用 PHP 脚本 song.php。它是服务器端脚本,用来加载歌曲并将其返回给您。其代码如清单 6 所示:

清单 6. song.php 脚本
<?php
     header('Content-Type: application/json');
     $message = "";
     $resp = array();
     $sql = "select * from songs where id =" . $_REQUEST["id"];
     try{
          require_once(dirname(__FILE__)."/db.php");
          $result = mysql_query($sql,$conn);
          $song = array();
          if (!$result) {
              $message = "Could not successfully run query ($sql) from DB: " . 
			                                            mysql_error();
          } else if (mysql_num_rows($result) == 0) {
              $message = "No song found with that id!";
          } else {
               $song = mysql_fetch_assoc($result);
          }
          $resp["song"] = $song;
          mysql_free_result($result);
     } catch (Exception  $e) {
          $message = "Sorry there was an error: " . mysql_error();
     }
     $resp["error"] = $message;
     echo(json_encode($resp));
?>

清单 6 查询此数据库。注意,您发回的是 JSON 代码。虽然原本可以使用 XML 进行数据传递,但在客户机上,JSON 更容易处理,也更有效。要正确地发回 JSON,需要将内容类型设置为 application/json。这不仅对浏览器来说是正确的,而且也是 Prototype 的要求,它使 Prototype 将响应识别为 JSON,进而让处理过程更为容易。最后,要将数据作为 JSON 返回,必须使用 PHP 内置的 json_encode 函数(您所使用的编程语言都有类似的库函数)。

现在,我们回到 清单 5, 看看所创建的响应处理程序(var handler = function ......)。Prototype 自动地对所返回 JSON 进行 safe-eval;这通过传递给处理程序的对象上的 responseJSON 属性来提供。这使您能够轻松地地访问数据,比如 song.title 等。现在,让我们看看以 $("spinner") 开头的那一行代码。$() 符号是 Prototype 提供的快捷方式。它也是类似的 document.getElementById() 函数的快捷方式。所以 $("spinner") 基本上等同于 document.getElementById("spinner")。实际上,根据对象类型,Prototype 会向返回对象添加一些额外的方法(稍后介绍)。借助 Prototype,可以使用简单的语法,比如 $("title").innerHTML = song.title,来初始化显示。在浏览器中,初始化后的页面如图 3 所示:

图 3. Edit 页面:初始化后
Edit 页面:初始化后

同样,您可以使用 Firebug 查看从服务器获取的数据,如图 4 所示:

图 4. Firebug 显示加载的歌曲数据
Firebug 显示加载的歌曲数据

现在,标题显示的是 “Edit Song”,但此页面看上去是只读的。单击任何一个属性值,就可以对它进行编辑,如图 5 所示:

图 5. 编辑歌曲
编辑歌曲

如果关闭此选项卡或按 Enter,数据就会发送给服务器并在数据库中更新。将数据发送给服务器的代码也来自 editor.js 文件,如清单 7 所示:

清单 7. 用于更新歌曲的 JavaScript 代码
function makeText(input){
     // save record
     var formData = $("songForm").serialize(true);
     saveData(formData);
     // go back to display
     input.parentNode.innerHTML = input.value;     
}

function saveData(song){
     var handler = function(xhr){
          var json = xhr.responseJSON;
          if (json.error){
               // display the error
          }
     };
     var options = {
          method : "post",
          onSuccess : handler,
          parameters : song
     };
     new Ajax.Request("update.php", options);                    
}

代码开始处是 makeText() 函数。现在,当使用 Prototype shortcut $("songForm") 时,就会得到其增强了的 Form 对象。这会带来一个新的可调用的函数 serialize,该函数获取表单内的所有输入字段并创建这些字段的名称和值的哈希表。您可以将这个哈希表传递给 saveData() 函数。Prototype 再次被用来向服务器发送一个 Ajax 请求。注意,这次使用了一个 POST,原因是正在更改数据 和调用 update.php,参见清单 8:

清单 8. update.php 脚本
<?php
     header('Content-Type: application/json');
     $message = "";
     $clause = "";
     foreach ($_POST as $key => $value){
          if ($key != "id"){
               $clause = $key . " = '" . $value . "' ";
          }
     }
     $sql = "update songs set " . $clause . " where id=" . $_POST["id"];
     try{
          require_once(dirname(__FILE__)."/db.php");
          $result = mysql_query($sql,$conn);
          if (!$result) {
              $message = "Could not successfully run query ($sql) from DB: " . 
			                                         mysql_error();
          }
          mysql_free_result($result);
     } catch (Exception  $e) {
          $message = "Sorry there was an error: " . mysql_error();
     }
     $resp["error"] = $message;
     echo(json_encode($resp));
?>

在这里您又会看到一个很简单的 PHP 处理。注意,Ajax 请求把歌曲的 ID 和被修改的字段发送回来,这是 Ajax 处理数据库修改高效性的一个很好的例子。您可以再次在 Firebug 中看到所有这些内容,如图 6 所示:

图 6. Firebug 显示歌曲更新
Firebug 显示歌曲更新

Prototype 不仅为发送 Ajax 请求提供了便利的方法,也为访问被发送的数据、访问响应数据及修改显示提供了便利。


结束语

Ajax 技术已经不是几年前的未知疆界了。聪明的开发人员可以利用大多数免费的开源库来减少 Ajax 编程偶尔会碰到的复杂性。Prototype 是一个以简化为目标的功能强大的库。除了全方位简化 Ajax 的开发外,它也能够与 Ajax 内容驱动(从服务器返回的 HTML)及数据驱动方法一起很好地工作。它是所有资深 Web 开发人员工具箱中的一个必备工具。


下载

描述名字大小
第 1 部分示例代码wa-aj-ajaxpro1.zip39KB

参考资料

学习

获得产品和技术

  • Prototype 是一个 JavaScript 库,引入了功能强大的函数来帮助简化 Ajax 编程。
  • 获得 Firefox 的扩展 Firebug
  • 获得 PHP
  • 获得 Apache
  • 获得 MySQL

条评论

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=Web development, Open source
ArticleID=311402
ArticleTitle=像专业人员一样开发 Ajax 应用程序,第 1 部分: 使用 Prototype JavaScript 库和 script.aculo.us
publish-date=06102008