Ajax 改造,第 4 部分: 用 jQuery 和 Ajax 表单改造现有站点

用开源工具把一个多步过程流线化到一个单屏界面中

Ajax 技术改变了大型商业 Web 应用程序的外观,但是许多较小的 Web 站点都不具备重新构建整个用户界面(UI)的资源。Ajax 的一些新特性能够解决实际中的界面问题并改善用户体验。通过本系列文章,您可以了解如何使用开源的客户端库让您的用户界面变得更为时尚。本文将展示如何使用 Ajax 技术将一个多步结帐过程从一系列表单转变成一个单一屏幕的界面。我们将采用渐进增强方法,从而确保所有用户代理仍然能够访问您的站点。

Brian J. Dillard, 副总裁, Ajax Development, Pathfinder Development

Brian DillardBrian J. Dillard 是一名具有 12 年经验的 Web 开发人员,曾经为 Orbitz Worldwide、Reflect True Custom Beauty、Archipelago LLC 和 United Airlines 等公司构建了富用户界面。现在,他是芝加哥市 Pathfinder Development 的 Ajax 开发副总裁,为各类客户构建富 Internet 应用程序,并参与了开源项目,还为 Agile Ajax blog 撰稿。他是 Really Simple History(一个流行的 Ajax 历史和书签库)的项目主管。



2008 年 8 月 22 日

关于本文

本文将逐步引导您使用 Ajax 技术改进一个 Web 1.0 购物站点。您可以下载改进之前和之后的示例应用程序源代码,也可以在作者的 Web 服务器上查看两个版本的运行效果。除了 Ajax 技术和最佳实践之外,您还将了解 Ajax 如何通过渐进增强原理改善用户体验。

本文假设您已经牢固掌握 HTML 和 CSS,基本了解 JavaScript 和 Ajax 编程技术。示例应用程序仅使用客户端代码构建;本文演示的技术适用于任何服务器端应用程序框架。对于所有 Ajax 应用程序,您都必须从 Web 服务器而不是从自己桌面上的文件运行示例代码。此外,您也可以仅跟随源代码在我的 Web 服务器上查看示例站点的运行效果。

Ajax 资源中心

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


回顾第 1 和第 2 部分

本系列的 第 1 部分第 2 部分 介绍了示例应用程序 Customize Me Now,并开始将它从 Web 1.0 版本改造成受 Ajax 支持的 Web 2.0 版本。借助 jQuery JavaScript 框架和其他的开源库,通过用模式对话框(modal dialog)、工具提示和 lightbox 分别替换弹出消息、离站链接和导航边栏,流线化了 Customize Me Now 的用户流。在 第 3 部分,您进行了进一步的改进,将大块内容放到 Ajax/DHTML 选项卡内并用快捷的图像 carousel 替代了单击-等待式的图片页。

第 4 部分的目标

在本文中,您将学习如何将多页表单转变成 Ajax 选项卡来流线化复杂的过程。所使用的用例是示例购物网站的结帐过程。如果没有 Ajax,在潜在的客户看来,多页表单既冗长又让人头疼。进行 Ajax 改造后,即使是一个复杂的结帐过程也可以变得友好和易于操作 — 只要您在如何构造用户界面方面足够用心。电子商务站点并不是这些技术的惟一受益者。同样的原理可以应用于为了完成一个多步过程用户必须填写一系列相关表单的任何情况。

要理解本文中的这些概念,请参考 Customize Me Now 1.2(参见 下载,它是对未进行 Ajax 改造的原始示例站点稍作修改后的版本。如果对 1.2 版再做更改,就会得到 Customize Me Now 2.2,它综合了在整个系列中所做的全部更改。)

电子商务结帐过程:Web 1.0 版本

即使是喜欢在线购物的客户通常也会十分讨厌结帐的过程。所涉及的问题太多:

  • 结帐过程会涉及多少步骤通常不是很清晰。
  • 每一步需要多少时间也不清晰。
  • 根据用户如何回答特定的问题,他们有可能被转到应用程序中不相关的部分,之后还要再回来,走了不必要的弯路。选择购物选项、应用折扣编号甚至是登录都会让看起来很直观的过程花费比预期更长的时间。
  • 除非站点开发人员对该过程的安全性编程十分仔细,否则,莫名其妙的安全警告会让用户很恼怒。
  • 含义不明的错误消息和不一致的错误标志都会让用户弄不清楚何时发生了错误。
  • 编码糟糕的验证例程也会导致无休止的问题。这样的例子有很多,比如服务器响应之后,信用卡号作废必须重新输入,再比如复选框没有按要求保持选中状态。

电子商务结帐过程:Web 2.0 版本

在准备使用 Ajax 改造现有的 Web 站点而开始投入时间和开发时,改进用户体验应该成为您的首要目标之一。Ajax 虽然不能解决用户之所以憎恨结帐过程的全部症结,但它至少能在如下三个方面提供一些帮助:

  • 使完成结帐过程所需步骤的数量变得清晰
  • 在步骤间转换的速度较快
  • 当用户必须在登录、输入折扣编号或由于其他原因背离正常的结帐过程时,返回的过程简单且统一

示例应用程序:Customize Me Now 1.2

如果在 Web 浏览器中观察 Customize Me Now 1.2,就会发现该站点的结帐过程与其他很多站点一样让人迷惑。所提供的面包屑式导航(breadcrumb navigation)显示结帐需要 5 步:

  1. Personal Info
  2. Shipping Details
  3. Billing Details
  4. Order Review
  5. Confirmation

但是,实际的过程更为复杂。作为一个用户,在开始步骤 1 之前,必须要选择是作为客人结帐还是登录。如果选择前者,就会直接进入步骤 1,可以在此输入名字和联系信息。如果选择后者,就必须进入登录屏幕,输入用户名和密码并返回到步骤 1 来查看先前输入的联系信息。

完成步骤 1 后,剩余的过程是一个线性过程。但在到达步骤 4 Order Review 时,又会遇到另一个潜在的弯路,这次是到达 Apply Discount 页。

在整个过程中(包括所走的弯路),有一个工具能帮助您了解自己处在该过程中的何处:即前面提到的 breadcrumb navigation。在任何时候,您只能单击在前面已经完成的 breadcrumb 项。您不可能跳转到过程的前面,而是只能向后跳转。这个 breadcrumb 路径如图 1 所示:

图 1. Customize Me Now 1.2 breadcrumb 路径
图 1. Customize Me Now 1.2 breadcrumb 路径

此 breadcrumb navigation 在某种情况下很有用,但由于其格式上是一个由竖线分割的列表,很多用户甚至都注意不到它。而且它不能准确反映用户可以使用的弯路。登录实际上是步骤 1 的一个子步骤,而折扣编号则是步骤 4 的子步骤。这些弯路的每一个都需要两次服务器往返才能让用户回到其之前所在位置。每次到服务器的请求都会延长过程的处理时间并且当新页面加载时也会让用户迷失方向。

对于 Customize Me Now 所有以前的版本,在页头和页脚的导航可能在实际中并不存在。它只是为了帮您在阅读本文时能快速跳到示例应用程序的不同页面。Customize Me Now 1.2 示例代码和真实的电子商务应用程序间还存在其他的一些差异。比如,示例中没有 Secure Sockets Layer (SSL) 加密。在 HTTP 连接的两端均没有验证。此外,此过程的每一步都是静态的。比如,即使是用户选择了复选框来使用其邮寄地址作为其帐单地址,但帐单地址还是没有被预填写。而实际的电子商务站点一般会通过服务器端代码启用该功能。就本文的目的而言,客户端代码至少展露出实际应用程序内可能存在的额外复杂性。

改造结帐过程:首要步骤

现在就可以开始构建 Customize Me Now 2.2 了。通过将结帐过程转变成一个单一屏幕的界面,可以减少服务器往返带来的延迟,还可以让步骤间的转变不再如此让人迷惑。完成之后,转到登录和折扣屏幕的过程将很直观而不会让人迷路。

插入 JavaScript 库

这次改造所需工具包括在之前的文章中用到过的流行 Ajax 库 jQuery 和其他两个您很熟悉的插件:

  • jQuery UI Tabs,在第 3 部分用过,可以将一个无序列表转变成一个选项卡界面,而且可以用来自现有的 Document Object Model (DOM) 对象或 Ajax 调用的内容填充每个选项卡。这里,我们使用它来将 7 个单独的 HTML 文件转变成单一屏幕的界面。在上次部署此插件时,我们更改了所包括的背景图像以改进其外观。这次我们还将使用这个修改后的同一个图像。
  • jQuery Form,在第 1 部分和第 2 部分用过,提供了一些方法,可通过 Ajax 提交表单以及以多种方式处理结果。这里,我们使用它来控制单一屏幕的结帐过程各步骤间的流程。

这次改造的重头戏都发生在一个页面:checkout.html。要将此文件准备好,请下载 jQuery 的 JavaScript 和 CSS 文件及其插件并在 HTML 文件头部引用它们。结果应该如清单 1 所示:

清单 1. JavaScript 库链接
<!--jquery assets-->
<script type="text/javascript"
	src="../js/jquery-1.2.3.min.js"></script>
<script type="text/javascript"
	src="../js/jquery.form.js"></script>

<!--jquery.ui.tabs assets-->
<script type="text/javascript"
	src="../ui.tabs/ui.tabs.pack.js"></script>
<link rel="stylesheet"
	href="../ui.tabs/ui.tabs.css" type="text/css"
	media="print, projection, screen">

创建 HTML 片段

和本系列 第 3 部分 一样,还需要创建现有的 HTML 文件的一些 HTML 片段版本。此步骤十分必要,因为 Ajax 响应通常需要忽略原本充当页面主要内容的页头、页脚和其他外部元素。HTML 文件的整页格式和片段格式都可以从相同的服务器端模板引擎获得。这里,我们如法炮制,我们将几个文件复制过来并改成新文件名,而保留原始的文件原样不动。这样操作之后,得到的新文件如下:

  • login-fragment.html
  • checkout1-fragment.html
  • checkout2-fragment.html
  • checkout3-fragment.html
  • checkout4-fragment.html
  • checkout4a-fragment.html

接下来,打开这些文件并去掉除主表单元素及其子元素之外的所有内容。完成后,login-fragment.html 的全部内容类似清单 2 ,而每个后续文件也都遵循相同的模式:

清单 2. HTML 片段文件格式
<form method="GET" action="checkout1.html"
	class="checkout" id="lform">
				
	<h2>Step 1: Personal Info</h2>
	
	<h3>Login</h3>
	
	<div>
		<label>
			Email Address
			<input type="text" name="email"
				id="email"/>
		</label>
		<label>
			Password
			<input type="password" name="password"
				id="password"/>
		</label>
	</div>

	<div>
		<input class="button" type="submit"
			name="submit" id="submit"
			value="submit" />
	</div>

</form>

改造结帐过程:重头戏

有了所需的全部文件后,就可以开始进行 Ajax 改造的真正工作了。大多数的代码更改都发生在 checkout.html 之内,正如您所见,该文件没有复制并改变成片段版本。这是因为 checkout.html 的现有版本既充当新选项卡界面的中心枢纽,又是现有界面的起点,要求即使在 JavaScript 功能不可用时,用户仍能看到。

借助 jQuery UI Tabs 将单独的页面转变成选项卡

在 checkout.html 内,需要创建一些 div 元素来保存将要转变成选项卡格式的内容。每个 div 都接收 "tabContent"class 属性以便对其应用样式,而每个元素都有惟一的 id 属性以便能在 JavaScript 代码内对其进行对象引用。第 1 个 div 元素包围的是 checkout.html 的现有内容。3 个额外的空 div 元素被添加在其下作为之后将要通过 Ajax 获取的内容的占位符。完成之后,HTML 代码应类似清单 3:

清单 3. 选项卡内容包装器的 HTML
<div id="personalInfo" class="tabContent">
	
	<h2>Step 1: Personal Info</h2>
	
	<div class="buttons">
		<a class="button" href="checkout1.html"
			id="checkoutAsGuest">
			check out as guest
		</a>
		<a class="button" href="login.html"
			id="login">log in</a>
	</div>

	<div class="fakeForm">
		<p>[long, boring playback of order details]</p>
	</div>

</div>

<div id="shippingDetails" class="tabContent"></div>
<div id="billingDetails" class="tabContent"></div>
<div id="orderReview" class="tabContent"></div>

您可能会注意到我们只为结帐过程的 5 步骤中的 4 个步骤创建了 div 元素。没有关系。步骤 5 Confirmation 是个特殊的情况。之所以让它成为 5 步骤中的一个步骤是为了方便用户知道自己在过程中所处的位置,但步骤 5 实际发生在结帐过程完成之后。所以,它在新的一个页面打开而不是选项卡界面,所以无需为之创建占位符。

此外,还要确保刚刚创建的容器 div 被适当地样式化。加上边界和间距以使处于选项卡下的这些容器有较好的外观。另外,要压缩它们中的 h2 元素,因为,选项卡上的标签会过多的呈现它们。幸运的是,您已经在本系列中的 第 3 部分 创建了满足这两个需求的样式规则,所以,只需确保这些规则被加入到 customizemenow.css 的未尾就可以了。结果如请单 4 所示:

清单 4. 为选项卡内容添加样式的 CSS
#CMN .tabContent {
	padding: 14px;
	border: 1px solid #97a5b0;
}
#CMN .tabContent h2{
	display: none;
}

至此,我们已为选项卡内容创建并样式化了 div 包装器,下面需要创建选项卡本身了。您可能还记得在 第 3 部分,jQuery UI Tabs 从一个无序列表中构造了它的选项卡式的界面。我们已经有了一个显示 breadcrumb navigation 的 ul 元素;如清单 5 所示:

清单 5. breadcrumb navigation 的 HTML
<div class="breadcrumb nav">
	<ul>
		<li class="current">1. Personal Info</li>
		<li>2. Shipping Details</li>
		<li>3. Billing Details</li>
		<li>4. Order Review</li>
		<li class="last">5. Confirmation</li>
	</ul>
</div>

然而,这个标记与 jQuery UI Tabs 所期望的 HTML 结构之间存在着一些差异。因此,应该为选项卡创建一个备用的 ul 元素,然后用 CSS 和 JavaScript 代码来切换这两个列表的可视性。默认的模式是显示 breadcrumb navigation 并隐藏选项卡; 这确保了没有 JavaScript 功能的用户也能看到他们经常看到的相同的 Web 1.0 界面。也可以使用 JavaScript 代码来隐藏这个 breadcrumb 列表,而为使用 Ajax 结帐界面的那些启用了 JavaScript 功能的用户显示选项卡列表。

这些选项卡的 ul 元素应该类似清单 6:

清单 6. 选项卡的 HTML
<ul class="navTabs">
	<li><a id="tab0" href="#personalInfo">
		<span>1. Personal Info</span></a>
	</li>
	<li><a id="tab1" href="#shippingDetails">
		<span>2. Shipping Details</span></a>
	</li>
	<li><a id="tab2" href="#billingDetails">
		<span>3. Billing Details</span>
	</a></li>
	<li><a id="tab3" href="#orderReview">
		<span>4. Order Review</span>
	</a></li>
	<li class="last"><a id="tab4" href="#">
		<span>5. Confirmation</span>
	</a></li>
</ul>

注意到选项卡内链接元素的这些 URL 对应于之前创建的内容 div 的名称。无需任何相关内容,Confirmation 选项卡只需一个 hash 符号来表示其伪 URL。

用来隐藏新 ul 元素的 CSS 处于 checkout.html 主体的 noscript 块。正如您所注意到的,对选项卡使用了与本系列 第 3 部分 相同的策略。现在,正如在第 3 部分一样,添加几个额外的 noscript 样式来协助没有启用 JavaScript 功能的那些用户。实际上,改变了 清单 4 中创建的样式规则。处理完 noscript 样式块后,它应该类似清单 7:

清单 7. Noscript CSS
<noscript>
	<style type="text/css">
		/*don't show tabs when JS disabled*/
		#CMN .navTabs {
			display: none;
		}
		/*without the tab box, disable border+padding*/
		#CMN .tabContent {
			padding: 0;
			border: 0;
		}
		/*without the tab labels, stop hiding h2s*/
		#CMN .tabContent h2 {
			display: block;
		}
	</style>
</noscript>

接下来的部分十分有趣。现在,可以借助一些定制 JavaScript 代码来将所有东西集中在一起。将所有代码捆绑到 jQuery 的定制 document.ready 事件,该事件在 DOM 可用于浏览器的 JavaScript 引擎时触发。这确保了所有需要处理的 DOM 元素都已准备就绪。

要创建这个复杂的事件处理程序,仅需向 checkout.html 的 head 元素的底部添加一个脚本块,如清单 8 所示:

清单 8. 创建选项卡的 JavaScript 代码
<script type="text/javascript">
//when the document is ready
$(document).ready(function() {

	/* hide the breadcrumbs ul and show the tab ul */
	$('div.breadcrumb').hide();
	$('ul.navTabs').show();
	
	/*turn the newly visible ul into tabs*/
	var tabSet = $('ul.navTabs').eq(0).tabs(
		{
			/*apply a nice visual effect to tab activation*/
			fx: { height: 'toggle', opacity: 'toggle' },
			/*disable all but the first tab by default*/
			disabled: [1,2,3,4]
		}
	).bind('select.ui-tabs', function(event, ui) {
			/*
			ensure that each time a new tab is activated
			all subsequent tabs are disabled. This will
			prevent users from jumping around in the process
			*/
			var currentTab = parseInt(ui.tab.id.substring(3));
			var tabSetLength = 5;//a necessary hack
			for (var i = 0; i < tabSetLength; i++) {
				if (i > currentTab) {
					tabSet.tabs("disable", i);
				}
			}
	});

	/*
	bind the checkout and login links to new click
	handlers and cancel their original behavior.
	now, these links will open the fragment versions
	of their original targets inside the first tab.
	*/
	$("#checkoutAsGuest,#login").click(function() {
		$('#personalInfo')
			.load(
				$(this)
				.attr("href")
				.replace(".html", "-fragment.html")
			)
		;
		return false;
	});

});
</script>

正如 清单 8 中的注释所表明的,我们借助 jQuery 的力量来将原始的标记转变成其新的选项卡格式。我们隐藏了页面不再需要的元素,展示需要的元素并告知 jQuery UI Tabs 转变这些元素的外观和行为。传递给 tabs 方法的选项包使用了与 第 3 部分 相同的视觉效果,但额外的一个参数可用来禁用除第一个选项卡之外的所有选项卡。这能让结帐过程成为单向过程,用户如果没能按顺序完成每个步骤,他将无法向前跳转。在本文后面,我们还将编写代码来在需要的时候逐个启用和禁用这些选项卡。

jQuery UI Tabs 还提供了选项卡式界面的生命周期内的一些定制事件挂钩。与诸如 onclickonmouseover 这样的内置 DOM 事件类似,定制事件也能够让回调处理程序应用于它们。惟一的不同点是事件本身由 jQuery 及其插件而不是浏览器定义。可以使用这些定制事件处理程序中的一个来进一步控制在给定的时间哪个选项卡被启用或禁用。

每当一个新的选项卡被激活,一个名为 select.ui-tabs 事件就会抛出(这个事件的名称在 jQuery UI Tabs 的后续版本中有所更改,所以,如果遇到问题,可以去查看相关文档)。通过在新创建的选项卡组上调用 jQuery 的 bind 方法,可以给这个定制事件附加一个处理程序。这个处理程序遍历整个选项卡组并会禁用当前选项卡后面的所有选项卡。然而,这个 select.ui-tabs 事件处理程序并不能访问整个选项卡组,而只能访问所选中的选项卡。因此,您不得不硬编码对选项卡集的引用以及其中的选项卡数。

连接好其他选项卡的行为后,需要告诉第一个选项卡中的内容要做些什么。这包括更改标签为 log incheck out as guest 的两个链接的行为,以便它们通过 Ajax 在当前选项卡中加载它们的目标页面。原始的链接指向整个 HTML 页面,而我们只需要前面创建的 HTML 片段。因此要使用 jQuery 来获取两个链接的对象引用并用 click 事件处理程序覆盖它们的默认行为。还需要获取每个链接的 href 属性,使其转而指向一个片段文件并通过 Ajax 获取结果 URL。最后,在 Personal Info 选项卡内呈现响应。

好了!我们现在构建了五步结帐过程中的步骤 1。在浏览器中访问结帐页面,将看到一个类似图 2 所示的页面:

图 2. Customize Me Now 2.2 结帐步骤 1:登录或作为访客结帐
图 2. Customize Me Now 2.2 结帐步骤 1:登录或作为访客结帐

如果选择 check out as guest 链接,那么 Personal Info 选项卡的内容会使用实际的个人信息表单刷新,如图 3 所示:

图 3. Customize Me Now 2.2 结帐步骤 1:Personal Info 表单
图 3. Customize Me Now 2.2 结帐步骤 1:Personal Info 表单

如果单击 log in 链接,那么个人信息选项卡的内容就被登录表单刷新,如图 4 所示:

图 4. Customize Me Now 2.2 结帐步骤 1:Login 表单 图 4. Customize Me Now 2.2 结帐步骤 1:Login 表单
图 4. Customize Me Now 2.2 结帐步骤 1: Login 表单

要想在此时通过登录或是个人信息表单,选项卡界面很快就会中断。选项卡内并没有继续载入内容,而是对页面的原始的、未分段的版本进行全页面刷新。这是因为您还没有告诉您的单个 HTML 片段文件如何在选项卡基础结构内运行。要完成最后的这个任务,需要另一个插件:jQuery Form。

用 jQuery Form 控制这个分步流程

log incheck out as guest 链接重定向到 Ajax 选项卡很简单。结帐页面的其余部分包括 HTML 表单,其默认行为很难覆盖。正如您在 第 1 部分 中所见到的,jQuery Form 提供了所需的功能。完成 Ajax 改造的技巧就是用此插件来将繁琐的表单转化为漂亮的 Ajax 小部件。

要完成这个工作,我们要向每个 HTML 片段文件末尾加上一个脚本块,以便重新布置此片段的表单提交。由于片段都通过 Ajax 加载而且它们的脚本都会在 checkout.html 的上下文内执行,所以没有必要让这些片段链接到任何外部脚本文件。它们均包括在 checkout.html 之内。

首先,我们来处理登录表单。之前,我们已经通过 Ajax 让用户能够访问该表单。现在,只需要将其通过 Ajax 提交,方法还是通过向表单的 submit 事件添加一个处理程序。由于要在同一个 HTML shell 内加载几个表单,所以必须给每个表单一个惟一的 HTML id 属性以便脚本能够区分这些表单。如果浏览一下片段文件的 HTML,就不难看出这些 HTML 已经被处理好了。login-fragment.html 内的表单具备 lformid,checkout1-fragment.html 内的表单则有 pformid,以此类推。

(为了避免冲突,需要为每个表单字段提供一个惟一的 ID。同样地,这在示例代码中已经被处理好了。)

login-fragment.html 的提交处理程序类似清单 9:

清单 9. 登录页面的 JavaScript 提交处理程序
<script type="text/javascript">
// bind the form and provide a callback function
$('#lform').submit(function() { 

	//submit the form via ajax
	$(this).ajaxSubmit({
		target:     '#personalInfo', 
		url:        'checkout1-fragment.html', 
		success:    function() { 
			var tabSet = $('ul.navTabs');
			tabSet.tabs("enable", 0);
			tabSet.tabs("select", 0);
		}
	});

	//don't actually submit the form normally
	return false; 
});
</script>

此处理程序获得一个对相关表单的对象引用并使用 ajaxSubmit 方法通过 Ajax 重新提交。通过将一个选项包传递给 ajaxSubmit,它会得到如下信息:

  • 何处提交表单的值(包含个人信息表单的文件 checkout1-fragment.html)
  • 何处加载响应(第一个选项卡的 div 元素,它包含 personalInfoid 属性)
  • 如果 Ajax 调用成功应该如何动作(启用和选择第一个选项卡,由索引 0 标识)

最后,通过返回 false,取消表单的正常的、非 Ajax 提交。

要查看这个过程,可以在浏览器内重新开始这个结帐过程。单击 log in 链接,提交登录表单,会看到个人信息表单载入第一个选项卡。现在能够通过两个方式得到这个表单:直接从结帐过程的起点,或者通过其他路径到达登录页面(当然,在实际情况中,登录后,个人信息表单可能已经为您预先填写好了)。

不过,现在必须找到一种方法来离开第一个选项卡并前进到第二个及后续的选项卡。这次,需要向 checkout1-fragment.html 添加一个表单提交处理程序,该文件含有个人信息表单。此处理程序类似于登录的那个处理程序。与在第一个选项卡内载入结果、启用它并选择它有所不同,我们要在第二个选项卡上执行所有这些动作。为此,选项包将 shippingDetailsid 指向 div,而相应选项卡基于 0 的索引是 1。完成后,处理程序会类似清单 10:

清单 10. Personal Info 表单的 JavaScript 提交处理程序
<script type="text/javascript">
// bind the form and provide a callback function
$('#pform').submit(function() { 

	//submit the form via ajax
	$(this).ajaxSubmit({ 
		target:     '#shippingDetails', 
		url:        'checkout2-fragment.html', 
		success:    function() { 
			var tabSet = $('ul.navTabs');
			tabSet.tabs("enable", 1);
			tabSet.tabs("select", 1);
		}
	});

	//don't actually submit the form normally
	return false; 
});
</script>

可以继续使用这种方式来处理大多数的后续片段文件,只有一个例外:步骤 4 Order Review 中的文件(checkout4-fragment.html)不需要 Ajax 表单。正如之前所讨论的,步骤 5 是 Confirmation 页面,它打破了选项卡式界面的惯例,加载的是一个全新的页面。因此,通常的表单动作对步骤 4 而言是正确的。不过,Order Review 的确提供了输入折扣编号的另一个弯路。因此,此片段需要一个 click 处理程序来用 checkout4b-fragment.html 的折扣表单重新加载当前的选项卡。结果类似清单 11:

清单 11. Order Review 的 Click 处理程序
<script type="text/javascript">
$('#enterDiscount').click(function() {
	/*
	grab the url of the current link and load
	the fragment version it in an existing tab
	*/
	$('#orderReview')
		.load(
			$(this).attr("href")
				.replace(".html", "-fragment.html")
			)
		;
	return false;
});
</script>

回顾所做的改造

总结一下,我们所做的更改现在已经将 Customize Me Now 1.2 转变为 2.2 版本。

遵循一个比较方便的结帐过程

要查看选项卡式结帐过程的运行结果,可以在浏览器中访问此站点并在各个步骤中导航,但不要绕道到登录或应用折扣的步骤。应该能够看到单一屏幕的界面,其中结帐过程的每个步骤都在各自的选项卡内进行,而百叶窗式的视觉效果则标记了步骤间的转换。当到达倒数第二个步骤并单击 purchase 时,就会进入 Confirmation 页面,它看上去正如在 Customize Me Now 1.2 中的一样。

遵循一个较为复杂的结帐过程

现在,多次执行此结帐过程并通过其他弯路进入登录和折扣过程。线性的选项卡式的过程没有改变,但包含多个子步骤的某些复合步骤(1 和 4)除外。各个子步骤的加载没有显示视觉效果。当执行到后续的选项卡时,会看到类似的百叶窗式的效果。

现在是需要真正的创意的时候了。通过步骤 4 完成此过程,然后单击步骤 2 的选项卡来模仿想要重新执行先前步骤的一个用户。应该可以看到第二个选项卡重新激活(表单字段内之前输入的值均完整无缺)而第三和第四个选项卡则是禁用的。在向后执行此过程后,要再次重新执行此过程的惟一方法是重新提交这些表单。在 清单 8 中添加的激活和解除激活代码可以很好地工作。

在示例应用程序中,这种限制看起来似乎有些专制,但在实际环境中,它非常重要。用户在步骤 2 所做的回答可能会影响步骤 4 中的提问,所以如果之前的回答更改了,用户将必须重新进行之后的步骤。在这些实际的情景中,必须要向选项卡式界面添加额外的代码,以便每次选项卡被重新访问时,其内容都会被一个刷新 Ajax 调用重新载入。

测试渐进增强

最后,使用浏览器首选项或插件禁用 JavaScript 功能。重新加载结帐页面并确保禁用了 JavaScript 功能的浏览器在使用旧的、未进行 Ajax 改造的界面时仍能完成结帐过程。通过对旧的界面实施渐进增强而不是根据 JavaScript 功能进行重新设计,确保了各类用户代理在将来都能访问应用程序。

已完成的成果

对于使用选项卡页面呈现一系列表单这样的简单目标,上述操作看起来工作量很大。但您从中获得哪些收获呢?答案仍然归结于用户体验。

Customize Me Now 1.1 的多页界面本来不是很糟糕,但加载连续表单的过程减缓了用户操作的速度,而且还会让他们在各个步骤间迷失方向。我们的确提供了老式的 breadcrumb navigation。但视觉上,breadcrumb 式导航所提供的结帐体验的一致性较差。因为它们看上去更像全局导航或文本内容,所以,与视觉特色明显的选项卡式界面相比,breadcrumb 链接难免略逊一筹。而且选项卡式界面为过程提供了视觉上的凝聚力,将一系列分离页面转变成单一界面。步骤间发生的百叶窗式效果加强了这种聚合力的效果,同时也巧妙地提醒了用户在过程中的位置。

此外,还巧妙地改进了那些绕道进行登录和申请折扣的用户的体验。在老的界面内,这类用户通常都会对自己所处的位置感到困惑。通过将单个步骤的复合动作放到从来不会从页面消失的单个选项卡内,新的结帐路径总是可以让用户明确方向。由于在单个选项卡内子步骤间没有百叶窗效果,所以用户就能知道他们没有完成当前步骤。

剩余的工作

构建完选项卡后,可以继续改造界面内的其他问题区域。以下是所能进行的其他一些增强:

  • 处理 Ajax 故障:提交处理程序只能响应成功的 Ajax 调用。而实际的生产型站点必须要预料到因服务器错误或网络延迟而导致的无效 Ajax 调用,并且能够进行恢复。
  • 提供表单验证:输入验证是开发人员和用户都十分头痛的问题。到目前为至,我们一直回避着这一棘手的问题。若能正确处理,Ajax 表单验证可极大地改进用户体验并能让代码变得更为有效、可维护性更好。
  • 让购物车可见:新的 Ajax 界面只在过程的开始和结束部分显示用户的购物车。可以将购物车的压缩视图作为挨着选项卡的一个侧栏添加进来,并在过程中完成每个步骤后对之进行更新。这一有关用户订单状态的连续反馈将对用户十分有价值。
  • 加入 Back 按钮管理:虽然有所改进,但还是会给用户带来一个新的问题。整个结帐过程中的 Back 按钮都是无用的。不过,借助 Ajax 的历史管理库,这很容易补救,该库在每次新表单被加载进选项卡时允许向浏览器的历史堆栈添加一项。这样一来,当用户单击 Back 按钮时,就会被带回到之前的选项卡而不是被从结帐过程硬拉出来,回到之前所在的页面。要实现这一功能,可以返回到一个独立库,比如 Really Simple History,它适用于 jQuery、Prototype 和很多其他的 Ajax 框架(内幕公布:本文的作者是 Really Simple History 的编程人员之一。)

正如您所见,存在无限可能。您在本文中所做的更改为您提供了一个坚强的 Ajax 基础,您可借此继续改造结帐过程。


下载

描述名字大小
原始演示应用程序的源代码wa-aj-overhaul4OnePointTwo.zip832KB
经过改进的演示应用程序的源代码wa-aj-overhaul4TwoPointTwo.zip928KB

参考资料

学习

  • 您可以参阅本文在 developerWorks 全球网站上的 英文原文
  • 通过 jQuery 文档 Web 站点了解 jQuery 应用程序编程接口(API)。
  • 参与 jQuery 社区并访问 Learning jQuery Web 站点上的教程和论坛。
  • 借助其他文章,比如 “使用 jQuery 简化 Ajax 开发”(Jesse Skinner,developerWorks,2007 年 4 月),继续学习 Ajax。
  • 要获得更多 jQuery 帮助,阅读 jQuery in Action(Manning Publication Co.,2008 年 2 月)。
  • 查看 Brian Dillard 的 blog Agile Ajax 获得有关 jQuery 和其他 UI 主题的更多信息。
  • 有关 Ajax 应用程序安全性最佳实践的详尽介绍,请参看 Billy Hoffman 和 Bryan Sullivan 的近作 Ajax Security(Addison Wesley Professional,2007 年 12 月)。
  • 访问著名 Web 开发人员 Luke Wroblewski 的 blog Functioning Form,获得他对 Web 表单设计最佳实践的注释。
  • 浏览 技术书店,查找关于这些主题和其他技术主题的图书。

获得产品和技术

  • jQuery 主站点下载 jQuery,了解所有关于 jQuery 的信息并寻找各种插件。撰写本文时,其当前版本为 1.2.3。
  • jQuery UI Tabs 是一个 jQuery 插件,允许将内联或 Ajax 内容包装在选项卡式界面。此插件是可定制小部件和用户界面组件集 jQuery UI 的一部分。
  • jQuery Form 是一个 jQuery 插件,允许使用各种直观的方法将 HTML 表单转变成功能强大的 Ajax 组件。
  • 浏览 Really Simple History,一个 Ajax 历史库,可恢复用户期望 Web 应用程序能够提供的 Back 按钮和书签行为。

讨论

更多下载

  • 演示: Customize Me Now 1.2 (在作者的公司 Web 服务器上可以看到此原始演示应用程序的运行情况。)
  • 演示: Customize Me Now 2.2 (在作者的公司 Web 服务器上可以看到经过改进的演示应用程序的运行情况。)
  • 演示: Customize Me Now: All Versions (在作者的公司 Web 服务器上可以看到演示程序的所有版本,包括本系列中以前文章提供的版本。)

条评论

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, XML
ArticleID=331106
ArticleTitle=Ajax 改造,第 4 部分: 用 jQuery 和 Ajax 表单改造现有站点
publish-date=08222008