级别: 中级 Jack D Herrington (jherr@pobox.com), 高级软件工程师, Leverage Software Inc.
2008 年 2 月 13 日 在 HTML 表单中结合 Asynchronous JavaScript™ + XML
(Ajax) 的服务器回调机制,对表单应用 Ajax,对于为应用程序增加 Web 2.0 功能来说是一种可行的办法。通过本文了解增加 Ajax 代码改进 PHP 应用程序用户体验的各种技术。
谈到 Web 2.0 应用程序,首先想到的就是那些最杰出的应用:YouTube 的视频、 üGoogle Maps 可滚动的地图以及 Flikr 的地理定位功能。但是人们常常忽略的是,在这些网站上那些粗糙的 HTML 表格正随着 Ajax 技术的普及经历一场重大的变革。
 | |
请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
|
|
本文介绍了如何利用 Prototype.js JavaScript 库在表单中结合 Ajax 代码来解决常见的用户体验问题。
简单的 Ajax 表单提交
我们从最简单的东西入手:一个包含多个字段的注册表单,希望通过 Ajax 而不是常规的 Web 表单上传办法来提交。清单 1 显示了这个简单的表单。
清单 1. index.html
<html>
<head>
<script src="prototype.js"></script>
</head>
<body>
<form id="myform">
<table>
<tr><td>First</td><td><input type="text" name="first"></td></tr>
<tr><td>Last</td><td><input type="text" name="last"></td></tr>
<tr><td>Email</td><td><input type="text" name="email"></td></tr>
</table>
<input type="button" onclick="dosubmit()" value="Submit">
</form>
<div id="result" style="padding:5px;">
</div>
<script>
function dosubmit( ) {
new Ajax.Updater( 'result', 'add.php', { method: 'post',
parameters: $('myform').serialize() } );
$('myform').reset();
}
</script>
</body>
</html>
|
文件的一开始包含了 prototype.js JavaScript 文件,它将帮助我完成所有的 Ajax 操作。接下来是传统的 HTML 表单,包含三个字段:first、last 和 email。再下面是使用 dosubmit() JavaScript 函数提交表单的按钮。
dosubmit() 函数使用 Ajax.Updater 类把数据上传给 add.php
脚本。然后添加调用选项。这里将提交方法设置为 post,然后使用表单的 serialize() 方法添加参数。serialize() 方法不是标准的 JavaScript 代码,由 JavaScript 库来提供。
调用 Ajax.Updater 的第一项是 <div> 标记的 ID,它接收 add.php 脚本返回的 HTML。在用户单击按钮时通知用户发生了什么,这是最简单的办法。
add.php 脚本如清单 2 所示。
清单 2. add.php
Thanks <?php echo( $_POST['first'] ) ?> <?php echo( $_POST['last'] ) ?>!
|
脚本仅仅回显表单上传的内容。事实上,这时候可能需要增加一条数据库记录,这类业务逻辑处理您可以自己完成。
第一次打开表单的时候,浏览器中显示的结果如 图 1 所示。
图 1. 简单的 Ajax 表单
然后单击 Submit,将表单数据发送到页面,add.php 页面返回的 HTML 显示在 Submit 按钮下面,如 图 2 所示。
图 2. 提交之后的响应
Ajax 表单的另一个变化是自动填充表单,根据某些关键字段的值更新字段值 — 比如,输入客户 ID 并单击按钮就可以在另一个字段中得到当前客户的记录。
这类自动填充表单如 清单 3 所示。
清单 3. index.html
<html>
<head>
<script src="prototype.js"></script>
</head>
<body>
<form id="myform">
<table>
<tr><td>ID</td><td><input
type="text" name="id"></td></tr>
<tr><td>First</td><td><input type="text"
name="first" id="elFirst"></td></tr>
<tr><td>Last</td><td><input type="text"
name="last" id="elLast"></td></tr>
<tr><td>Email</td><td><input type="text"
name="email" id="elEmail"></td></tr>
</table>
<input type="button" onclick="dofill()" value="Fill Fields">
</form>
<script>
function dofill( ) {
new Ajax.Updater( 'result', 'getdata.php',
{ method: 'post', parameters: $('myform').serialize(),
onSuccess: function( transport ) {
$('elFirst').value = transport.responseXML.getElementsByTagName('first')
[0].firstChild.nodeValue;
$('elLast').value = transport.responseXML.getElementsByTagName('last')
[0].firstChild.nodeValue;
$('elEmail').value = transport.responseXML.getElementsByTagName('email')
[0].firstChild.nodeValue;
} } );
}
</script>
</body>
</html>
|
在 清单 1
所示表单的基础上增加了一个字段 ID。这里存放客户的 ID。然后新增的 dofill() 函数调用 getdata.php 页面,后者返回包含给定客户名字、姓氏和电子邮件地址的 XML 代码。
发送给 Ajax.Updater 调用的 onSuccess 处理程序使用所有浏览器都支持的文档对象模型(DOM)函数解析下载的 XML 数据。然后将 elFirst、elLast 和 elEmail <input> 项设置为 XML 返回的数据。
getdata.php 页面如 清单 4 所示。
清单 4. getdata.php
<?php
header( "content-type: text/xml" );
$first = ' ';
$last = ' ';
$email = ' ';
if ( $_POST['id'] == '1' )
{
$first = 'Jack';
$last = 'Herrington';
$email = 'jherr@pobox.com';
}
?>
<data>
<first><?php echo( $first ) ?></first>
<last><?php echo( $last ) ?></last>
<email><?php echo( $email ) ?></email>
</data>
|
这里的代码实际上是一个存根方法,通常需要访问数据库来得到给定记录的名字、姓氏和电子邮件地址。
第一次打开该页面的结果如 图 3 所示。
图 3. 填充表单
然后在 ID 字段中输入 1 并单击 Fill
Fields,于是访问 getdata.php 页面获得名字、姓氏和电子邮件地址并更新相应的字段。如 图 4 所示。
图 4. 根据 ID 填充字段
下一个 Ajax 小例子将创建一个就地更新(in-place)的 to-do 列表。
原地更新的列表
最常见的 Ruby on Rails 范例是原地 更新的 to-do 列表。就是说列表在页面上部,列表下有一个文本框。在文本框中输入一些内容并单击 Submit 之后,页面上方的列表就会加上新的项目而不改变页面。输入文本的文本框重置以便输入新的内容。
实际上非常困难,因此采用 PHP 来完成。to-do 列表所在页面如 清单 5 所示。
清单 5. index.php
<html>
<head>
<script src="prototype.js"></script>
</head>
<body>
<div id="result" style="padding:5px;">
<?php
$fh = fopen( 'list.txt', 'r' );
while( $str = fgets( $fh ) ) {
?>
<?php echo( $str ); ?><br/>
<?php
}
?>
</div>
<form id="myform">
<input type="text" name="todo">
</form>
<input type="button" onclick="dosubmit()" value="Submit">
<script>
function dosubmit( ) {
new Ajax.Updater( 'result', 'add.php',
{ method: 'post', parameters: $('myform').serialize() } );
$('myform').reset();
}
</script>
</body>
</html>
|
这里没有将 to-do 列表存储到数据库中,而采用普通文本文件 list.txt 存储,每行一项。因此,要将列表置于页面顶部,只需要打开文件并把每一行读入 ID 为
result 的 <div> 标记中。
表单如下,其中包含作为 to-do 项的输入文本。其 下方是调用 dosubmit() JavaScript
函数的按钮。这个 JavaScript 函数使用 Ajax.Updater
类调用 add.php 页面,后者添加新的项并返回添加了新项的列表。
add.php 脚本如 清单 6 所示。
清单 6. add.php
<?php
$total = '';
$fh = fopen( 'list.txt', 'r' );
while( $str = fgets( $fh ) ) {
?>
<?php echo( $str ); ?><br/>
<?php
$total .= $str;
}
if ( array_key_exists( 'todo', $_POST ) )
{
?>
<?php echo( $_POST['todo'] ); ?><br/>
<?php
$fh = fopen( 'list.txt', 'w' );
fwrite( $fh, $total."\n".$_POST['todo'] );
fclose( $fh );
}
?>
|
有点傻里傻气的 to-do 列表如 清单 7 所示。
清单 7. list.txt
Get swim goggles
Practice swimming
Swim in race
|
第一次打开页面的结果如 图 5 所示。
图 5. 准备添加 to-do 项
输入 Finish in record time 然后单击 Submit。在页面没有刷新的情况下得到的结果如 图 6 所示。
图 6. 插入记录后的页面
当然这绝不是 Rails to-do 列表范例惟一的精彩之处。但的确是 Web 2.0 令人侧目的因素之一。实际上,如果还没有尝试过 Rails,我建议所有的 Web 工程师都应当试一试。即便不在项目中应用,看看应用程序是如何组织的、MVC 机制是如何使用的、数据库持久模型是多么简单也很有意思。
另一种常见的 Web 需求是扩展字段列表。我称之为 expando 列表。
Expando 列表
如果某条记录有无数多个关键字怎么办?当然,一种办法是使用逗号分隔关键字。另一种办法是让按钮随时增加新的关键字,允许用户根据需要添加任意多的关键字。清单 8 采用了第二种办法。
清单 8. index.html
<html>
<head>
<script src="prototype.js"></script>
</head>
<body>
<form id="myform">
<table id="keytable">
<tr><td>Keyword</td><td><input type="text"
name="keyword_1"></td></tr>
</table>
</form>
<input type="button" onclick="addkeyword()" value="Add Keyword">
<input type="button" onclick="dosubmit()" value="Submit">
<div id="result" style="padding:5px;">
</div>
<script>
var nextkeyid = 2;
function addkeyword()
{
var elTR = $('keytable').insertRow( -1 );
var elTitleTD = elTR.insertCell( -1 );
elTitleTD.appendChild( document.createTextNode( 'Keyword' ) );
var elInput = document.createElement( 'input' );
elInput.type = 'text';
elInput.name = 'keyword_'+nextkeyid;
nextkeyid++;
var elInputTD = elTR.insertCell( -1 );
elInputTD.appendChild( elInput );
}
function dosubmit( ) {
new Ajax.Updater( 'result', 'add.php',
{ method: 'post', parameters: $('myform').serialize() } );
}
</script>
</body>
</html>
|
其中的关键在于 addkeyword() 函数,它使用 insertRow 和 insertCell
在关键字表格中创建新行。然后用 document.createElement 建立存放关键字的新的输入字段。Ajax.Updater 在用户单击 Submit 时被调用,本身则调用 add.php 脚本。add.php 脚本返回表单给出的关键字列表。脚本如 清单 9 所示。
清单 9. add.php
Post Result:<br/>
<?php var_export( $_POST ) ?>
|
在浏览器中打开的时候,页面如 图 7 所示。
图 7. 只有一个关键字的表单
多次单击 Add Keyword 添加新的字段,然后单击 Submit。结果如 图 8 所示。
图 8. 添加关键字并提交之后的表单
为了让用户添加和同一记录关联的多个值,比如电话号码、关键字和地址,这是一种理想的办法。Ajax 另一种常见的用法是实现登录表单。
登录表单
Ajax 登录窗口非常棒,因为能够马上得到反馈是否能够登录进去,而且可完成原地登录。假设您正在寻找一篇希望提供建议的文章,但是没有登录。利用 Ajax 可以在查找的同时登录。如果身份得到认可,将出现一个新表单供您添加评论。这种方法和跟踪查找的文章然后在登录之后重定向相比要容易得多。
Ajax 登录的简单版本如 清单 10 所示。
清单 10. index.html
<html>
<head>
<script src="prototype.js"></script>
</head>
<body>
<form id="logform">
User: <input type="text" name="user"><br/>
Password: <input type="password" name="password"><br/>
<input type="button" onclick="login()" value="Login">
</form>
<div id="noway" style="display:none;">
No way!
</div>
<script>
function login() {
new Ajax.Request( 'login.php',
{
method: 'post',
postBody: $('logform').serialize(),
onSuccess: function( transport ) {
if( transport.responseText.match( /\<ok\/\>/ ) )
window.location = 'home.html';
else
$('noway').style.display='block';
}
} );
}
</script>
</body>
</html>
|
User 和 Password 字段位于文件上方的表单。此后是 ID
为 noway 的 <div> 标记,登录不成功的话将显示它。login() JavaScript 方法使用 Ajax.Request 类尝试登录。若
login.php 返回的 XML 是 <ok />,表单把用户重定向到主页。否则显示 noway
文本。
login.php 代码如 清单 11 所示。
清单 11. login.php
<?php
header( 'Content-type: text/xml' );
if ( $_POST['user'] == 'jack' && $_POST['password'] == 'password' )
echo( "<ok/>" );
else
echo( "<bad/>" );
?>
|
这段简单的代码检查硬编码的用户和密码,匹配返回 ok,否则返回
bad。
为了完整起见,主页代码如 清单 12 所示。
清单 12. home.html
<html>
<body>
You are logged in and this is your home page.
</body>
</html>
|
在浏览器中打开该页然后输入错误的用户名和密码并单击 Login。结果如 图 9 所示。
图 9. 输入错误密码后的登录页面
然后修改用户名和密码并单击
Login,就会重定向到主页,如 图 10 所示。
图 10. 输入正确用户名和密码之后的登录页面
本文的最后一个例子是基于 XForms 的 Ajax 文章。
XForms Ajax
我不认为自己是一位 XForms 专家,但认为这是一个不错的标准。本文将客户端 XForms、Protoype.js
与 Ajax 技术结合了起来。
这个简单的 XForms 例子如 清单 13 所示。
清单 13. index.xhtml
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xforms="http://www.w3.org/2002/xforms">
<head>
<title>XForms AJAX Example</title>
<xforms:model id="modelData">
<xforms:instance xmlns="">
<Data>
<First>First</First>
<Last>Last</Last>
<Email>email@email.com</Email>
</Data>
</xforms:instance>
</xforms:model>
<script src="prototype.js"></script>
</head>
<body>
<xforms:input ref="/Data/First">
<xforms:label>First: </xforms:label>
</xforms:input><br/>
<xforms:input ref="/Data/Last">
<xforms:label>Last: </xforms:label>
</xforms:input><br/>
<xforms:input ref="/Data/Email">
<xforms:label>Email: </xforms:label>
</xforms:input><br/><br/>
<button onclick="submit()">Submit</button>
<script>
function submit()
{
var m = $('modelData');
var base = m.getElementsByTagName('Data')[0];
var s = new XMLSerializer();
var data = ( s.serializeToString( base ) ).toString();
new Ajax.Updater( 'result', 'params.php',
{ method: 'post', parameters: 'data='+escape( data ) } );
}
</script>
<br/><br/>
<div id="result">
</div>
</body></html>
|
文件开始部分是 XForm 的 XML 模型。接下来是一些 XForms 标签和组成表单的输入字段。Submit 按钮调用 submit() JavaScript 函数。该函数利用 XMLSerializer JavaScript 对象将 XForms 数据模型转化成 XML 字符串传递给 params.php。params.php 脚本如 清单 14 所示,它把 XML 作为 HTML 返回以便能看到页面输出的内容。
清单 14. params.php
<?php
echo( htmlentities( $_POST['data'] ) );
?>
|
在浏览器中(安装了 XForms 增件)打开 XForms 页面的结果如 图 11 所示。
图 11. 支持 Ajax 的 XForms 页面
然后填充表单并单击 Submit 得到 图 12 所示的结果。
图 12. 提交之后的页面
它显示了转化成 XML 文本、发送给 params.php 然后返回到 result <div> 标记的数据模型。
结束语
利用 Ajax 来改进 HTML 表单有很多事情可做,本文仅仅介绍了一点皮毛。但至少希望能帮助您了解如何稍加修改页面代码来改进您的应用程序。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 表单应用程序的源代码 | x-ajaxxml9-forms.zip | 101KB | HTTP |
|---|
参考资料 学习
讨论
关于作者  | |  | Jack D. Herrington 是一位有着超过 20 年经验的高级软件工程师。他是 Code Generation in Action、Podcasting Hacks 和
PHP Hacks 这三本书的作者。他还发表了 30 多篇文章。可以通过 jherr@pobox.com 与 Jack 联系。 |
对本文的评价
|