2006 年 6 月 26 日
探讨 Ajax 的三种数据传输机制(XMLHttp、脚本标记、框架或 iframes)及各自的优缺点。本教程将提供服务器端和客户端代码并作详细说明,介绍在任何地方有效使用 Ajax 控件所需的技术。
开始之前
了解本教程的内容以及如何从这些内容中获得最大收益。
关于本教程
到处都是 Asynchronous JavaScript and XML (Ajax) 的宣传,您可能认为有很多人知道它的工作原理。但那些工程师们似乎讨论的都是与服务器交换数据的 XMLHttp 方法。如果只知道这些方法,那么只能用 Ajax 做有限的工作。与服务器交换数据实际上有三种方式:XMLHttp 方法、<script> 标记方法和 frame 或 iframe 方法。只有全部了解这三种方式(及其相对优缺点),才能对 Ajax 有全面的认识。本教程将介绍您应该了解的 Ajax 之迷中至关重要的传输部分的所有知识,正是这部分将客户机和服务器联系在一起。
除了理解客户机如何从服务器请求数据之外,还要了解哪些类型的数据在网上传播的问题。多数 Ajax 文章都谈到了可扩展标记语言(XML),实际上您也能传输普通文本、超文本编辑语言(HTML)页面或者 JavaScript 代码。有大量的理由要求您跳出 XML 的窠臼。
本教程的学习内容包括:
- 为示例应用创建数据库后端。
- 构建用于访问数据库数据的一组服务器端页面。
- 以使用数据服务的
XMLHttp 为基础构建一组页面。
- 以使用数据服务的 iframes 为基础构建一组页面。
- 以使用数据服务的
<script> 标记构建一组页面。
目标
本教程将介绍基本的 Ajax 传输方法,以及它们在 PHP 和 JavaScript 代码中的应用。
前提条件
本教程假设读者对 XML、HTML 以及 JavaScript 和 PHP 编程语言有基本的了解。运行本教程中的例子需要:
- 能够访问 MySQL 服务器的 PHP 服务器。
- Web 浏览器。(建议使用 Mozilla Firefox 或 Microsoft® Internet Explorer V6。)
系统要求
要运行本教程中的示例,需要安装 Apache Web 服务器和 PHP。还需要使用 Web 浏览器,如 Mozilla Firefox。
准备工作
 |
下载示例代码
本教程中用到的所有 HTML、PHP 和 SQL 源代码都可以从 下载 部分下载。
|
|
Ajax 到底是什么,为何这么重要?Ajax 不过是即时更新网页而不需要向服务器请求完整的新页面的一种方法。按照顺序来看,客户机首先从服务器请求页面。服务器返回内嵌 JavaScript 代码的 HTML 页面。然后 JavaScript 代码从服务器请求更多的数据(通常使用 XML)并动态地更新页面。可以用定时器或者根据用户输入触发脚本。脚本如何从服务器获取数据就是本文要讲述的内容。
关于 Ajax 的大部分书籍和文章都提到了一种 Ajax 传输方法:XMLHttp 请求。这可能是因为两种原因:首先这种方法可能是最精巧的,其次它是最新的方法。不过并没有新到要担心浏览器的兼容性。如果浏览器不支持 XMLHttp,也就不大可能很好地支持动态 HTML(DHTML),因而没有必要支持这样的浏览器。
XMLHttp 对象非常灵活,但是由于安全问题必须限制能够检索数据的位置。这些限制看起来似乎无关紧要,但在完成把 Ajax 控件放置到其他服务器的 blog 页面上这类任务时,就会带来实际问题。因此必须考虑替代方法,即速度慢的 <script> 标记。不要忘记还有第三种选择:使用框架或在线框架(iframe)与服务器交换数据。这种方法不算新,但是值得考虑,在需要的时候要想到它。
本教程包含三部分不同的代码。最底层是数据库,包括模式和数据。数据库之上是服务器页面,以不同的形式公开数据库中的数据。再上面是一组 HTML 页面,通过不同的机制访问数据。
这一节首先建立数据库。然后创建访问数据的 PHP 页面。
建立数据库
Web 应用程序怎能没有数据库呢?本教程使用了一个简单的 MySQL 数据库,该数据库只有一个表,即 book。清单 1 显示了该数据库,并定义了记录格式。
清单 1. 数据库
CREATE TABLE books (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
author TEXT,
title TEXT,
PRIMARY KEY ( id ) );
INSERT INTO books VALUES (
null,
"Jack Herrington",
"Code Generation in Action" );
INSERT INTO books VALUES (
null,
"Jack Herrington",
"Podcasting Hacks" );
INSERT INTO books VALUES (
null,
"Jack Herrington",
"PHP Hacks" );
|
清单 1 没有什么复杂的地方:只是一个包含三个字段的表,这三个字段是惟一标识符、标题和作者。向客户机公开这个表需要几种不同的 Web 页面,以不同形式导出数据。下面几节详细说明每种格式,并给出了用给定格式从数据库中输出数据的 PHP 服务器代码。
构建文本服务
清单 2 中的 PHP 代码连接到数据库,并把数据库中的内容存成文本字符串,记录之间用回车换行分开。
清单 2. 文本格式的服务
<?php
require_once("DB.php");
header( "Content-type: text" );
$dsn = 'mysql://root:password@localhost/ajaxdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "SELECT author, title FROM books" );
while( $res->fetchInto( $row ) )
{
echo($row[0].' - '.$row[1]."\n");
}
?>
|
测试该服务可以使用 php 解释器在命令行中运行,如 清单 3 所示。
清单 3. 运行文本格式的服务
% php text.php
Jack Herrington - Code Generation in Action
Jack Herrington - Podcasting Hacks
Jack Herrington - PHP Hacks
%
|
这个例子可能看起来无关紧要,但要记住,在 Ajax 世界中,任何非 XML 或 XML 兼容流都被看作是文本。因此传输的 vCard 或者 base64 编码流都是简单的文本。您必须知道如何传递非 XML 数据。
构建 XML 服务
XML 是 Ajax 最常用的信息源。可以使用 清单 4 中所示代码创建该示例所使用的最简单的 XML 版本。
清单 4. Xml.php
<php
header( 'Content-type: text/xml' );
?>
<books>
<?php
require_once("DB.php");
$dsn = 'mysql://root:password@localhost/ajaxdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "SELECT author, title FROM books" );
while( $res->fetchInto( $row ) )
{
?>
<book><author><?php echo($row[0]) ?>
</author><title><?php echo($row[1]) ?>
</title></book>
<?php
}
?>
</books>
|
注意,脚本的开始部分将 Content-type 头字段设置为 text/xml MIME 类型。这种类型对于 Ajax 至关重要。除非正确设置 MIME 类型,否则就不会在 XMLHttp 请求的结尾部分返回 XML 文档。
清单 5 显示了 清单 4 的命令行输出。
清单 5. xml.php 的输出
% php xml.php
<books>
<book><author>Jack Herrington</author><title>Code Generation in Action</title></book>
<book><author>Jack Herrington</author><title>Podcasting Hacks</title></book>
<book><author>Jack Herrington</author><title>PHP Hacks</title></book>
</books>
%
|
如果使用 iframe 方案就不能直接发送 XML。相反,必须将 XML 编码成 HTML 文本。清单 6 显示了这种编码。
清单 6. Xml_text.php
<?php
header( 'Content-type: text/html' );
ob_start();
?>
<books>
<?php
require_once("DB.php");
$dsn = 'mysql://root:password@localhost/ajaxdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "SELECT author, title FROM books" );
while( $res->fetchInto( $row ) )
{
?>
<book><author><?php echo($row[0]) ?>
</author><title><?php echo($row[1]) ?>
</title></book>
<?php
}
?>
</books>
<?php
$xml = ob_get_clean();
$xml = preg_replace( '/\>/', '>', $xml );
$xml = preg_replace( '/\</', '<', $xml );
echo( $xml );
?>
|
在这种情况下,内容类型被设置为 html,所有的 XML 内容都是使用 ob_start() 和 ob_get_clean() 方法捕获的。然后 XML 通过将 < 和 > 符号改为 < 和 > 来转化成 HTML 字符串。可以在 清单 7 中看到该脚本的输出。
清单 7. xml_text.php 的输出
% php xml_text.php
<books>
<book><author>Jack Herrington</author>
<title>Code Generation in Action</title></book>
<book><author>Jack Herrington</author>
<title>Podcasting Hacks</title></book>
<book><author>Jack Herrington</author>
<title>PHP Hacks</title></book>
</books>
%
|
处理这种自定义 XML 格式仅仅是了解 XML 过程中的一部分。您的应用程序中也可能需要使用其他的 XML 标准。
构建 RSS 和 RDF 服务
丰富站点摘要(Rich Site Summary,RSS)和资源描述框架(Resource Description Framework,RDF)联合标准有两个突出的优点:它们是基于 XML 的,因此非常适合 Ajax,同时它们很容易创建。清单 8 显示了从数据库表创建 RSS 提要的代码。
清单 8. Rss.php
<?php
header( 'Content-type: text/xml' );
?>
<rss version="0.91">
<channel>
<title>Book List</title>
<description>My book list</description>
<?php
require_once("DB.php");
$dsn = 'mysql://root:password@localhost/ajaxdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "SELECT author, title, id FROM books" );
while( $res->fetchInto( $row ) )
{
?>
<item><description>A book by <?php echo($row[0]) ?>
</description><title><?php echo($row[1]) ?>
</title>
<link>http://myhost/book.php?id=<?php echo($row[2]) ?></link>
</item>
<?php
}
?>
</channel>
</rss>
|
在命令行中运行 rss.php 将看到 清单 9 所示的 RSS 输出。
清单 9. rss.php 的输出
% php rss.php
<rss version="0.91">
<channel>
<title>Book List</title>
<description>My book list</description>
<item><description>A book by Jack Herrington</description>
<title>Code Generation in Action</title>
<link>http://myhost/book.php?id=1</link>
</item>
<item><description>A book by Jack Herrington</description>
<title>Podcasting Hacks</title>
<link>http://myhost/book.php?id=2</link>
</item>
<item><description>A book by Jack Herrington</description>
<title>PHP Hacks</title>
<link>http://myhost/book.php?id=3</link>
</item>
</channel>
</rss>
%
|
在 Ajax 数据中使用 RSS 最重要的是可以用一个操作完成。JavaScript 代码不仅获得了数据,还创建了联合提要,用户可以使用 RSS 阅读器订阅这些提要。而 RSS 读者越来越多了。目前发布的 Firefox Web 浏览器本身支持 RSS 提要,Internet Explorer 7 也将支持 RSS。
但是如果还有其他数据需要随着每个项一起发送怎么办?只需要添加额外的标记即可,如 清单 10 所示。
清单 10. Rssextra.php
<?php
header( 'Content-type: text/xml' );
?>
<rss version="0.91">
<channel>
<title>Book List</title>
<description>My book list</description>
<?php
require_once("DB.php");
$dsn = 'mysql://root:password@localhost/ajaxdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "SELECT author, title, id FROM books" );
while( $res->fetchInto( $row ) )
{
?>
<item><description>A book by <?php echo($row[0]) ?>
</description><title><?php echo($row[1]) ?>
</title>
<link>http://myhost/book.php?id=<?php echo($row[2]) ?></link>
<author><?php echo($row[0]) ?></author>
<id><?php echo($row[2]) ?></id>
</item>
<?php
}
?>
</channel>
</rss>
|
在命令行中运行 rssextra.php,就会看到包含作者和 ID 数据的新增标记,如 清单 11 所示。
清单 11. rssextra.php 的输出
% php rssextra.php
<rss version="0.91">
<channel>
<title>Book List</title>
<description>My book list</description>
<item><description>A book by Jack Herrington</description>
<title>Code Generation in Action</title>
<link>http://myhost/book.php?id=1</link>
<author>Jack Herrington</author>
<id>1</id>
</item>
<item><description>A book by Jack Herrington</description>
<title>Podcasting Hacks</title>
<link>http://myhost/book.php?id=2</link>
<author>Jack Herrington</author>
<id>2</id>
</item>
<item><description>A book by Jack Herrington</description>
<title>PHP Hacks</title>
<link>http://myhost/book.php?id=3</link>
<author>Jack Herrington</author>
<id>3</id>
</item>
</channel>
</rss>
%
|
由此可见,可以为 RSS 提要添加额外的标记,多数读者将会忽略这些标记。但是如果对此不满意,还可以使用 RDF,这种相似的联合格式允许您使用其他名称空间添加额外的标记。从而避免违反规范。清单 12 显示了生成 RDF 提要的代码。
清单 12. Rdf.php
<?php
header( 'Content-type: text/xml' );
echo( '<?xml version="1.0" ?>'."\n" );
?>
<rdf xmlns:mc="http://mysite.com">
<channel>
<title>Book List</title>
<description>My book list</description>
<link>http://mysite.com/</list>
</channel>
<?php
require_once("DB.php");
$dsn = 'mysql://root:password@localhost/ajaxdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "SELECT author, title, id FROM books" );
while( $res->fetchInto( $row ) )
{
?>
<item><description>A book by <?php echo($row[0]) ?>
</description><title><?php echo($row[1]) ?>
</title>
<link>http://mysite.com/book.php?id=<?php echo($row[2]) ?></link>
<mc:author><?php echo($row[0]) ?></mc:author>
<mc:id><?php echo($row[2]) ?></mc:id>
</item>
<?php
}
?>
</rdf>
|
运行 rdf.php 将看到 清单 13 所示的 RDF 输出。
清单 13. rdf.php 的输出
% php rdf.php
<?xml version="1.0" ?>
<rdf xmlns:mc="http://mysite.com">
<channel>
<title>Book List</title>
<description>My book list</description>
<link>http://mysite.com/</list>
</channel>
<item><description>A book by Jack Herrington</description>
<title>Code Generation in Action</title>
<link>http://mysite.com/book.php?id=1</link>
<mc:author>Jack Herrington</mc:author>
<mc:id>1</mc:id>
</item>
<item><description>A book by Jack Herrington</description>
<title>Podcasting Hacks</title>
<link>http://mysite.com/book.php?id=2</link>
<mc:author>Jack Herrington</mc:author>
<mc:id>2</mc:id>
</item>
<item><description>A book by Jack Herrington</description>
<title>PHP Hacks</title>
<link>http://mysite.com/book.php?id=3</link>
<mc:author>Jack Herrington</mc:author>
<mc:id>3</mc:id>
</item>
</rdf>
%
|
该脚本在 <rdf> 标记中定义了单独的名称空间 mc。名称空间是否连接到真正的站点实际上并不重要。重要的是利用该名称空间定义了两个额外的标记:<mc:author> 和 <mc:id>,在每个项中添加了作者和 ID 数据。
构建 JavaScript (JSON) 服务
JavaScript 编码格式在 Ajax 开发中大受欢迎。经常被称作 JavaScript Object Notation 格式 或 JSON,但实际上那就是 JavaScript。清单 14 显示了这种数据服务脚本的第一个版本。
清单 14. Js.php
<?php
require_once("DB.php");
header( "Content-type: text/javascript" );
$dsn = 'mysql://root:password@localhost/ajaxdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "SELECT author, title FROM books" );
$items = array();
while( $res->fetchInto( $row ) )
{
$items []= "{ author: '".$row[0]."', title: '".$row[1]."' }";
}
?>
[ <?php echo( join( ",\n", $items ) ); ?> ];
|
注意,这里 MIME 类型被设置为 text/javascript,以便浏览器能够识别。运行该脚本将得到 清单 15 所示的输出。
清单 15. js.php 的输出
% php js.php
[ { author: 'Jack Herrington', title: 'Code Generation in Action' },
{ author: 'Jack Herrington', title: 'Podcasting Hacks' },
{ author: 'Jack Herrington', title: 'PHP Hacks' } ];
%
|
这种输出不过是一个数组,每个记录都有对应的一个数组(或散列表)。这种输出形式非常适合 JavaScript eval 函数求值。
不幸的是,这个例子不能使用 <script> 标记,因为这段 JavaScript 代码除了创建一个数组外什么也没做。为了能在 <script> 标记中使用,JavaScript 代码必须对数据调用某个方法或函数,如 清单 16 所示。
清单 16. Jsadd.php
<?php
require_once("DB.php");
header( "Content-type: text/javascript" );
$dsn = 'mysql://root:password@localhost/ajaxdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "SELECT author, title FROM books" );
$items = array();
while( $res->fetchInto( $row ) )
{
$items []= "{ author: '".$row[0]."', title: '".$row[1]."' }";
}
?>
addData( 'jsadd.php', [ <?php echo( join( ",\n", $items ) ); ?> ] );
|
这段代码和 清单 14 基本相同,只不过它对数据调用了 addData 函数。在命令行中运行 jsadd.php 将得到 清单 17 所示的输出。
清单 17. jsadd.php 的输出
% php jsadd.php
addData( 'jsadd.php', [ { author: 'Jack Herrington',
title: 'Code Generation in Action' },
{ author: 'Jack Herrington', title: 'Podcasting Hacks' },
{ author: 'Jack Herrington', title: 'PHP Hacks' } ] );
%
|
这段代码非常适合 <script> 标记,但是 iframes 又如何呢?要将 JavaScript 数据传递给 iframe,JavaScript 数据必须用 HTML 编码,如 清单 18 所示。
清单 18. Js_text.php
<?php
require_once("DB.php");
header( "Content-type: text/html" );
$dsn = 'mysql://root:password@localhost/ajaxdb';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "SELECT author, title FROM books" );
$items = array();
while( $res->fetchInto( $row ) )
{
$items []= "{ author: '".$row[0]."', title: '".$row[1]."' }";
}
?>
[ <?php echo( join( ",\n", $items ) ); ?> ];
|
与 清单 14 相比,这里惟一的变化是 MIME 类型变成了 html。清单 19 显示了 js_text.php 的输出。
清单 19. js_text.php 的输出
% php js_text.php
[ { author: 'Jack Herrington', title: 'Code Generation in Action' },
{ author: 'Jack Herrington', title: 'Podcasting Hacks' },
{ author: 'Jack Herrington', title: 'PHP Hacks' } ];
%
|
JavaScript 数据在 Ajax 世界中的真正优势是速度。在一般快的机器上解析 XML 可能需要半分钟,而用编码为 JavaScript 的同样数量的数据生成数组和关联数组只需要几毫秒的时间。
处理的下一步是为这些数据形式构建客户机。
构建客户机
现在有了客户机可用的数据,还必须决定如何使用三种 Ajax 方法从服务器读取数据。图 1 显示了数据格式和使用这些数据的不同传输方法。
图 1. Ajax 传输兼容表
表面上看,XMLHttp 传输方法似乎是最佳选择。但是由于浏览器中的安全问题,还必须了解两种替代方法。下面几节介绍传输机制,描述用于消费各种数据类型的客户端代码。
XMLHttp 客户机
XMLHttp 方法很容易与 Web 服务器交换数据。它支持 GET 和 POST 方法,因此可以传输大量数据。此外,目前所有浏览器都支持它。
注意:Internet Explorer 需要一些小技巧来获得与 XMLHttp 等价的对象,不过 Internet Explorer 的新版本(版本 7)将支持 Firefox、Opera、Safari 和其他浏览器支持的标准方法。为了解决 Safari 缓冲 XMLHttp 请求的问题,可以添加一个随机值作为 URL 参数来避免缓冲。
XMLHttp 方法的最大优点在于其简单性。最大缺点是对域的安全限制。如果网页来自 www.mysite.com,那么脚本就能从 www.cnn.com 甚至 data.mysite.com 请求数据。只能向 www.mysite.com 发出请求,这就意味着在客户机上创建 RSS 阅读器是不可能的。在服务器上需要代理页面从 www.cnn.com 检索数据,然后通过 www.mysite.com 返回这些数据。
但是先假设安全限制对您来说并不重要。那么如何获取数据呢?
通过 XMLHttp 使用文本服务
清单 20 中的 DHTML 代码从 text.php 检索数据并将它们显示在浏览器中。
清单 20. Http_text.html
<html>
<title>Text test</title>
<head>
<script>
var req = null;
function processReqChange()
{
if (req.readyState == 4 && req.status == 200 )
{
var dobj = document.getElementById( "dataDiv" );
var text = req.responseText;
text = text.replace( "\n", "<br/>" );
dobj.innerHTML = text;
}
}
function loadUrl( url )
{
if(window.XMLHttpRequest) {
try { req = new XMLHttpRequest();
} catch(e) { req = false; }
}
else if(window.ActiveXObject)
{
try { req = new ActiveXObject("Msxml2.XMLHTTP");
} catch(e) {
try { req = new ActiveXObject("Microsoft.XMLHTTP");
} catch(e) { req = false; }
}
}
if(req) {
req.onreadystatechange = processReqChange;
req.open("GET", url, true);
req.send("");
}
}
var url = window.location.toString();
url = url.replace( /\/.*?$/, "sources/text.php" );
loadUrl( url );
</script>
<body>
<div id="dataDiv">
</div>
</body>
</html>
|
图 2 显示了结果。
图 2. http_text 测试页
这可能是最简单的 XMLHttp 请求。loadUrl 函数创建一个 XMLHttpRequest 对象,然后使用 open 函数开始数据下载。处理完成后,将调用 processReqChange 函数检查请求的状态。如果请求完成,该函数通过 dataDiv 元素将文本添加到 HTML 文档中。正则表达式将文本中的回车换行替换为 <br> 标记,这样文本流看起来就很自然,而不是长长的一行。
通过 XMLHttp 使用 HTML 服务
使用 XMLHttp 对象的另一种常见方法是从服务器请求更新的 HTML 标记数据。Microsoft 的新 Atlas 框架就采用了这种方法。其思路是,当更新页面的一部分时,页面返回服务器以请求新的 HTML,然后更新包含该 HTML 片段的那部分页面。清单 21 显示了这种处理方法。
清单 21. Http_html.html
<html>
<title>HTML test</title>
<head>
<style>
td { border: 1px solid #666; padding: 3px; }
</style>
<script>
var req = null;
function processReqChange()
{
if (req.readyState == 4 && req.status == 200 )
{
var dobj = document.getElementById( "dataDiv" );
dobj.innerHTML = req.responseText;
}
}
function loadUrl( url )
{
...
}
var url = window.location.toString();
url = url.replace( /\/.*?$/, "sources/html.php" );
loadUrl( url );
</script>
<body>
<div id="dataDiv">
</div>
</body>
</html>
|
loadUrl 代码和 清单 20 相同。主要区别是 processReqChange 函数使用 responseText 函数填充 dataDiv 元素的 innerHTML,从而更新页面内容。
通过 XMLHttp 使用 XML 和 RSS 服务
到目前为止,XMLHttp 对象最常见的用法是向浏览器传输给定格式的 XML。因为返回的请求对象有内置的 XML 文档对象模型(DOM)元素,可用它搜索 XML 中的新数据。
清单 22 显示了使用定制的 XML 格式从服务器请求图书数据的 DHTML 代码。
清单 22. Http_xml.html
<html>
<title>XML test</title>
<head>
<script>
var req = null;
function processReqChange()
{
if (req.readyState == 4 && req.status == 200 && req.responseXML )
{
var dobj = document.getElementById( "dataBody" );
var nl = req.responseXML.getElementsByTagName( 'book' );
for( var i = 0; i < nl.length; i++ )
{
var nli = nl.item( i );
var elAuthor = nli.getElementsByTagName( 'author' );
var author = elAuthor.item(0).firstChild.nodeValue;
var elTitle = nli.getElementsByTagName( 'title' );
var title = elTitle.item(0).firstChild.nodeValue;
var elTr = document.createElement( 'tr' );
dobj.appendChild( elTr );
var elAuthorTd = document.createElement( 'td' );
elAuthorTd.innerHTML = author;
elTr.appendChild( elAuthorTd );
var elTitleTd = document.createElement( 'td' );
elTitleTd.innerHTML = title;
elTr.appendChild( elTitleTd );
}
}
}
function loadXMLDoc( url )
{
...
}
var url = window.location.toString();
url = url.replace( /\/.*?$/, "sources/xml.php" );
loadXMLDoc( url );
</script>
<body>
<table cellspacing="0" cellpadding="3">
<tbody id="dataBody">
</tbody>
</table>
</body>
</html>
|
(loadXMLDoc 函数和上例中的 loadUrl 相同,为简便起见,我们将它省略了。)巧妙之处在于 processReqChange 函数,其中的 JavaScript 代码使用 getElementsByTagName 函数查找 XML 中的 <book>、<title> 和 <author> 标记。图 3 显示了输出结果。
图 3. 完成的 http_xml 页面
清单 23 显示了解析 RSS 版本的 processReqChange 代码。
清单 23. http_rss.html 中的 processReqChange
...
function processReqChange()
{
if (req.readyState == 4 && req.status == 200 && req.responseXML )
{
var dobj = document.getElementById( "dataBody" );
var nl = req.responseXML.getElementsByTagName( 'item' );
for( var i = 0; i < nl.length; i++ )
{
var nli = nl.item( i );
var elDescription = nli.getElementsByTagName( 'description' );
var description = elDescription.item(0).firstChild.nodeValue;
var elTitle = nli.getElementsByTagName( 'title' );
var title = elTitle.item(0).firstChild.nodeValue;
var elLink = nli.getElementsByTagName( 'link' );
var link = elLink.item(0).firstChild.nodeValue;
var elTr = document.createElement( 'tr' );
dobj.appendChild( elTr );
var elTitleTd = document.createElement( 'td' );
elTr.appendChild( elTitleTd );
var elLink = document.createElement( 'a' );
elLink.innerHTML = title;
elLink.href = link;
elTitleTd.appendChild( elLink );
var elDescriptionTd = document.createElement( 'td' );
elDescriptionTd.innerHTML = description;
elTr.appendChild( elDescriptionTd );
}
}
}
...
|
这里最大的区别是代码发现额外的链接并用它在表中围绕着标题创建了一个 <anchor> 标记。结果如 图 4 所示。
图 4. 完成的 http_rss.html 页面
使用 RSS 传输的优点体现在两方面。首先,RSS 客户机可以监控 RSS;其次,可以将这段 HTML 和 JavaScript 代码应用于任何 RSS 格式的提要,只要数据来自服务页面的同一个域。
通过 XMLHttp 使用 JavaScript 代码和 JSON 服务
为了完成关于 XMLHttp 传输方法的讨论,还要使用 JavaScript 格式的数据。第一个例子(清单 24)显示了处理 js.php 页面数据的 processReqChange 函数,该页面返回 JavaScript 代码(或者 JSON,如果您想赶时髦的话)。
清单 24. http_js.html 页面中的 processReqChange
...
function processReqChange()
{
if (req.readyState == 4 && req.status == 200 && req.responseText )
{
var books = eval( req.responseText );
var dobj = document.getElementById( "dataBody" );
for( var b in books )
{
var elTr = document.createElement( 'tr' );
dobj.appendChild( elTr );
var elTitleTd = document.createElement( 'td' );
elTitleTd.innerHTML = books[b].author;
elTr.appendChild( elTitleTd );
var elDescriptionTd = document.createElement( 'td' );
elDescriptionTd.innerHTML = books[b].title;
elTr.appendChild( elDescriptionTd );
}
}
}
...
|
注意,与解析 XML 相比,解析 js.php 返回的 JavaScript 代码是多么容易。只需要对代码运行 eval 函数并获得返回值,该返回值是一个散列表数组,然后使用标准的 JavaScript for 循环遍历该数组。
传输 JavaScript 代码的另一种方法是使用 清单 16 所示的 JavaScript 代码类型,在求值过程中调用 JavaScript 函数。解析该函数的 JavaScript 代码如 清单 25 所示。
清单 25. http_jsadd.html 中的 processReqChange 和 addData
...
function addData( url, books )
{
var dobj = document.getElementById( "dataBody" );
for( var b in books )
{
var elTr = document.createElement( 'tr' );
dobj.appendChild( elTr );
var elTitleTd = document.createElement( 'td' );
elTitleTd.innerHTML = books[b].author;
elTr.appendChild( elTitleTd );
var elDescriptionTd = document.createElement( 'td' );
elDescriptionTd.innerHTML = books[b].title;
elTr.appendChild( elDescriptionTd );
}
}
function processReqChange()
{
if (req.readyState == 4 && req.status == 200 && req.responseText )
eval( req.responseText );
}
...
|
可以看到,processReqChange 函数中的代码现在转移到了 addData 中。
这组 XMLHttp 的用法可作为参考手册,帮助您在应用程序中使用 XMLHttp 对象处理不同的数据格式。
iframe 客户机
在加载页面后再与服务器交换数据的更传统的方法(如果能够这么说的话)是使用 <frame> 或 <iframe> 标记。强烈建议使用不可见的框架(<iframe> 标记),不可见框架更容易使用,而且不像框架那样影响页面布局。
框架的优点在于可以在那些不支持 XMLHttp 对象的浏览器(越来越少)中使用。还可以使用框架在用户页面历史中添加项,这样后退按钮处理的实际上是您的 Ajax 页面。不幸的是,该框架的缺点也很多。使用框架进行传输仅对 HTML 来说比较简单,虽然通过某些技巧也能传输 JavaScript 代码和 XML。
采用 iframe 方法可采用两种方式将数据传递给服务器。第一种方式是通过与 <iframe> 标记的 src 属性有关联的 URL 参数。第二种方式是创建与 iframe 文档中 <input> 元素有关联的 <form> 标记,然后使用 <form> 标记的 submit() 方法向服务器 POST 或 GET 数据。本教程示范了 src 属性。
通过 iframe 使用 HTML 服务
清单 26 中的代码说明了如何使用 iframe 加载 html.php 页面中的数据。
清单 26. Iframe_html.html
<html>
<title>Iframe HTML test</title>
<head>
<script>
function iframe_loaded( frame )
{
var dobj = document.getElementById( "dataDiv" );
dobj.innerHTML = frame.contentWindow.document.body.innerHTML;
}
var urlsToLoad = [];
function processRequests()
{
for( var u in urlsToLoad )
{
var sObj = document.createElement( 'iframe' );
sObj.src = urlsToLoad[ u ];
sObj.onload = function() { iframe_loaded( sObj ); };
if ( sObj.attachEvent != null )
sObj.attachEvent( 'onload', function() { iframe_loaded( sObj ); } );
sObj.style.visibility = 'hidden';
sObj.style.display = 'none';
document.body.appendChild( sObj );
}
}
function loadUrl( url )
{
urlsToLoad.push( url );
}
if ( window.addEventListener )
window.addEventListener( 'load', processRequests, 0 );
else
window.attachEvent( 'onload', processRequests );
var url = window.location.toString();
url = url.replace( /\/.*?$/, "sources/html.php" );
loadUrl( url );
</script>
<body>
<div id="dataDiv"></div>
</body>
</html>
|
如果用浏览器打开 iframe_html.php,将看到 图 5 所示的结果。
图 5. iframe_html.html 页面
那么这个页面是如何工作的呢?首先代码调用 loadUrl 函数,该函数将 URL 请求放入将在加载页面后执行的请求队列中。processRequests 函数在加载页面时调用,其任务是为每个请求创建 <iframe> 标记。
为了检查 iframe 是否已经加载,数据是否准备好,该代码将设置 iframe 的 onload 函数并使用 attachEvent 函数。该函数适用于 Internet Explorer 和 Firefox。iframe_loaded 函数使用 DOM 获得页面内容,然后将 dataDiv 元素设置为 iframe 的内容。
通过 iframe 使用 JavaScript 服务
解决了简单的任务,我们再看看如何用 iframe 方法支持 JavaScript 代码。第一个技巧在服务器端,需要将 JavaScript 编码为 HTML。第二个技巧在于如何获得包含 JavaScript 代码的 iframe 文档的文本。清单 27 说明了这种技巧。
清单 27. iframe_js.html 中的 iframe_loaded 函数
...
function iframe_loaded( frame )
{
var books = null;
if ( frame.contentDocument )
books = eval( frame.contentDocument.body.textContent );
else
books = eval( frame.contentWindow.document.body.innerText );
var dobj = document.getElementById( "dataBody" );
for( var b in books )
{
var elTr = document.createElement( 'tr' );
dobj.appendChild( elTr );
var elTitleTd = document.createElement( 'td' );
elTitleTd.innerHTML = books[b].author;
elTr.appendChild( elTitleTd );
var elDescriptionTd = document.createElement( 'td' );
elDescriptionTd.innerHTML = books[b].title;
elTr.appendChild( elDescriptionTd );
}
}
...
|
这些技巧主要用于 Internet Explorer,当下载以 text/javascript MIME 类型返回的 JavaScript 文件时会弹出一个窗口。除此以外,iframe 方法还使用了 eval,这与读取 JavaScript 代码的 XMLHttp 版本类似。
通过 iframe 使用 XML 服务
通过 iframe 方法传递最困难的是 XML。与 JavaScript 代码一样,解决之道也包括服务器和客户机。首先,XML 必须编码成 HTML 字符串。其次,客户机必须在内部使用 XML 处理程序将 iframe 方法中的字符串转化成 XML 文档。具体的做法如 清单 28 中的 iframe_loaded 函数所示。
清单 28. iframe_xml.html 中的 iframe_loaded 函数
...
function iframe_loaded( frame )
{
var dobj = document.getElementById( "dataBody" );
var xmlDoc = null;
if( frame.contentDocument )
{
// Mozilla/Firefox
var xml = frame.contentDocument.body.textContent;
xmlDoc = new DOMParser().parseFromString( xml, "text/xml" );
}
else
{
var xml = frame.contentWindow.document.body.innerText;
xmlDoc = new ActiveXObject( 'MSXML2.DOMDocument' );
xmlDoc.loadXML( xml );
}
var nl = xmlDoc.getElementsByTagName( 'book' );
for( var i = 0; i < nl.length; i++ )
{
var nli = nl.item( i );
var elAuthor = nli.getElementsByTagName( 'author' );
var author = elAuthor.item(0).firstChild.nodeValue;
var elTitle = nli.getElementsByTagName( 'title' );
var title = elTitle.item(0).firstChild.nodeValue;
var elTr = document.createElement( 'tr' );
dobj.appendChild( elTr );
var elAuthorTd = document.createElement( 'td' );
elAuthorTd.innerHTML = author;
elTr.appendChild( elAuthorTd );
var elTitleTd = document.createElement( 'td' );
elTitleTd.innerHTML = title;
elTr.appendChild( elTitleTd );
}
}
...
|
不幸的是,Internet Explorer 和 Firefox 采用不同的方式将字符串中的 XML 解析成 XML DOM。因此要检查 frame.contentDocument。XML 变回 DOM 后,代码和通过 XML DOM 读取数据创建 HTML 的 XMLHttp 页面类似。
脚本标记客户机
第三种方法也是最后一种方法是使用 <script> 标记从服务器下载 JavaScript 编码的数据。使用 <script> 标记可以在网络上发送任何类型的数据:文本、XML 或者 JavaScript 格式的数据结构。
<script> 标记方法的优点很突出:可以从任何服务器上请求数据。Google Maps 这类服务验证了这种优势。通过 <script> 标记,您的网站用户可以将 Ajax 代码片段复制和粘贴到他们自己的网站上运行,但他们仍然可以访问您的数据。通过 <script> 标记可以发送大量数据。
但这种方法也有一个缺点,JavaScript 以外的格式需要进行 JavaScript 编码才能从服务器传递到客户机。另一个更重要的不足之处在于这种方法只能使用 GET 协议。
通过 <script> 标记使用 JavaScript 服务
清单 29 中的代码使用 <script> 标记从 jsadd.php 页面访问数据。
清单 29. script.html 页面
<html>
<title>Script tag test #2</title>
<head>
<script>
function addData( url, books )
{
var dobj = document.getElementById( "dataBody" );
for( var b in books )
{
var elTr = document.createElement( 'tr' );
dobj.appendChild( elTr );
var elTitleTd = document.createElement( 'td' );
elTitleTd.innerHTML = books[b].author;
elTr.appendChild( elTitleTd );
var elDescriptionTd = document.createElement( 'td' );
elDescriptionTd.innerHTML = books[b].title;
elTr.appendChild( elDescriptionTd );
}
}
var urlsToLoad = [];
function processRequests()
{
for( var u in urlsToLoad )
{
var sObj = document.createElement( 'script' );
sObj.src = urlsToLoad[ u ];
document.body.appendChild( sObj );
}
}
function loadDoc( url )
{
urlsToLoad.push( url );
}
if ( window.addEventListener )
window.addEventListener( 'load', processRequests, 0 );
else
window.attachEvent( 'onload', processRequests );
var url = window.location.toString();
url = url.replace( /\/.*?$/, "sources/jsadd.php" );
loadDoc( url );
</script>
<body>
<table cellspacing="0" cellpadding="3">
<tbody id="dataBody">
</tbody>
</table>
</body>
</html>
|
这段代码在形式上与 iframe 方法类似。它使用 onload 处理程序向文档 <body> 标记添加 <script> 标记。然后,动态添加的 <script> 标记请求 jsadd.php 页面,后者包括调用 addData 函数的 JavaScript 代码。然后该函数使用 createElement() 和 appendChild() 方法添加要显示的数据,就像本教程中您看到的那样。
总之,<script> 标记是从服务器动态获取数据的最灵活、最简便的方法,虽然我建议在服务器端花点时间将用服务器语言编写的数据结构代码集中转化成适当编码的 JavaScript 结构。很多动态编程语言都提供了完成这类任务的包。(JSON 网站(http://json.org)也是一个很好的资源。请参阅 参考资料。)
结束语
用 Ajax 传输数据的同时就意味着踏上了 Web V2.0 探险之路
本教程示范了不需要重新加载页面从服务器获取数据的三种独特方法。根据我的经验,建议使用 XMLHttp 或 <script> 标记方法。无论哪种方法,我都更喜欢使用 JavaScript 作为传输语言,因为客户机解释起来更容易也更快。
至于传输方法,我推荐 <script> 标记方法,因为它允许用户使用网页上的 “查看源代码” 功能抓取代码,然后在用户自己的环境中使用这些代码显示您的数据。只要适当地进行设置,这种借用可以使您的网站像病毒那样迅速传播,而 Web V2.0 与这种传播密切相关。
也就是说,如果要问本教程的主题,它介绍了 Ajax 方程中数据传输部分的各种方法。您必须自己决定您的 Web V2.0 应用程序的最佳方法。