Ajax 改造,第 3 部分: 用 jQuery、Ajax 选项卡和照片 carousel 改进现有的站点

用开放源码框架和插件改进界面并节省用户带宽

Ajax 技术已经改变了大型商用 Web 应用程序的面貌,但是许多小型 Web 站点不具备重新构建整个用户界面所需的资源。新特性应该能够解决现实的界面问题并改进用户体验,以此证明它们的价值。本系列讲解如何用开放源码的客户端库逐步改进用户界面。在本期中,学习如何使用 DHTML 和 Ajax 把缓慢、混乱、烦人的产品细节页面改造成快速优雅的页面。我们将采用渐进式改进方法,从而确保所有用户代理仍然能够访问您的站点。

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

Brian Dillard在作为 Web 开发人员的 12 年里,Brian J. Dillard 为许多公司构建了丰富的用户界面,包括 Orbitz Worldwide、Reflect True Custom Beauty、Archipelago LLC 和 United Airlines。目前,担任芝加哥 Pathfinder Development 公司 Ajax Development 的副总裁,Brian 为许多客户构建富因特网应用程序、参与开放源码项目并在 Agile Ajax blog 上发表文章。他是 Really Simple History(一种流行的 Ajax 历史和书签库)的项目主管。



2008 年 7 月 31 日

关于本文

Ajax 资源中心

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

本文讲解用 Ajax 技术改进一个 Web 1.0 购物站点的步骤。可以 下载 改进之前和之后的示例应用程序源代码。还可以在作者的 Web 站点上看到这两个版本的运行效果。除了 Ajax 技术和最佳实践之外,还要学习如何通过渐进式改进、易用性和用户体验设计(UxD)原理用 Ajax 改进用户体验。

本文假设读者充分了解 HTML 和 CSS,至少基本了解 JavaScript 和 Ajax 编程技术。示例应用程序是只使用客户端代码构建的;但是,演示的技术也可以应用于任何服务器端应用程序框架。要想运行示例站点,至少需要在本地主机上运行一个基本的 Web 服务器。您也可以研究源代码并在作者的 Web 站点上查看示例站点的运行效果。


回顾第 1 部分和第 2 部分

本系列的 第 1 部分第 2 部分 介绍了示例应用程序 Customize Me Now,并开始从 Web 1.0 版本到 Ajax 支持的 Web 2.0 版本的改进过程。它们讨论了这种改进的业务原因和易用性原因。它们还帮助您设置几个开放源码工具,包括 jQuery JavaScript 框架以及它的几个插件。通过使用这些库,把弹出消息、离站链接和导航边栏替换为模态对话框、工具提示和 lightbox,从而改进了 Customize Me Now 的用户操作流。在这个过程中,采用了渐进式改进原理;在禁用 JavaScript 的情况下,改进的 Web 2.0 应用程序的页面会自动退化到 Web 1.0 体验。

第 3 部分的目标

在本期中,我们将把产品细节页面的内容放到一个选项卡式界面中,从而提高它的可管理性。还要在一个图像 carousel 中显示产品图像,从而提高对图像的控制能力。您将学习如何通过简单的 Dynamic HTML(DHTML)或比较复杂的 Ajax 代码应用这两种技术。无论采用哪种方法,都应用渐进式改进原理,使页面在禁用 JavaScript 时仍然是可访问的。为了实现这些目标,将使用另外两个 jQuery 插件:用于实现图像幻灯片的 jCarousel 和用于实现选项卡的 jQuery UI Tabs。

为了理解本期中的概念,先请看一下 Customize Me Now 1.1,这是原示例站点稍加改进后的版本。我们将通过修改 1.1 版创建 Customize Me Now 2.1,这个版本包含整个系列介绍的所有改进。

两种产品细节页面:单页面和多页面

在电子商务 Web 站点中,最复杂的部分之一是产品细节部分。站点会把大量关于产品的信息集中在一起,从简单的说明和技术规格到从用户社区收集的内容(比如用户评论)。当然,还有产品图像,而且每种产品常常有多个图像。从用户体验的角度来看,难题在于向用户显示决定购买产品所需的数据,同时避免过多的信息困扰用户。

在某种程度上,Customize Me Now 1.0 比较容易进行 Ajax 改造。它显示的产品细节内容很容易放在单一页面上。在 Customize Me Now 2.0 中,通过使用 jQuery 和 Thickbox,把原来的页面替换为一个模态对话框。这有助于改进从搜索到购买的用户操作流程。

但是,现在需求改变了。Customize Me Now 1.1 显示的产品细节信息远远多于 1.0 版。这些内容包括许多很长的文本块和许多大照片(图像来自流行的 Web 2.0 照片共享站点 Flickr)。在 Ajax 技术出现之前,有两种方法可以显示如此多的内容:一个很长的滚动页面(见 图 1图 2),或者把页面分割为多个小页面,每个文本块或照片各有一个页面(见 图 3图 4)。

在 Web 浏览器中访问 Customize Me Now 1.1,比较一下 Product Details 页面的版本 A 和 B 以及老版本(Customize Me Now 1.0)之间的差异。页眉和页脚中有这三个版本的链接。可以看到,版本 A 和 B 在易用性方面的困难比老版本大得多。显然,对于 Product Details 页面的这些新版本,Thickbox 模态对话框并不合适。

图 1. Product Details 页面版本 A:单一页面,文本内容
Product Details 页面版本 A:单一页面,文本内容
图 2. Product Details 页面版本 A:单一页面,图像内容
Product Details 页面版本 A:单一页面,图像内容

版本 A 不但可能困扰用户,还会给浏览器和服务器的处理造成麻烦。用户会被大量信息所困扰(假设他们注意所有信息的话)。同时,浏览器和服务器必须处理大量数据,造成很大负担。目前,这个页面只有 6 张照片,所以在使用宽带连接时页面装载速度相当快。但是,如果有 16 张甚至 60 张照片,会怎么样呢?如果还有 150 条用户评论,又会怎么样呢?如果用户使用的网络比较慢呢?如果同时装载与一个产品相关的所有数据,性能就会严重受损,而且用户也难以一下子吸收这么多的内容。

图 3. Product Details 页面版本 B:多页面,文本内容
Product Details 页面版本 B:多页面,文本内容
图 4. Product Details 页面版本 B:多页面,图像内容
Product Details 页面版本 B:多页面,图像内容

版本 B 在每个页面上只显示少量信息,从而避免用户被大量信息困扰。但是,每当用户要查看更多信息时,他们必须单击链接并等待装载新页面。另外,版本 B 中的每个子页面包含大量让人糊涂的链接。虽然数据少了,但是导航过程很复杂。

改造单页面版本

因为所有内容都在一个页面上,所以很容易用图像幻灯片和选项卡式界面改进版本 A。这不需要 Ajax,只使用老式的 DHTML 就可以实现。这种方法的优点是它使用了渐进式改进。提供给浏览器的内容仍然是很长的滚动页面,但是 JavaScript 代码把它转换为更现代更容易使用的界面。如果使用不支持 JavaScript 的浏览器,用户只能看到原来的页面。当然,这种方法没有解决单页面方法的带宽问题。

下载和安装开放源码工具

为了开始 Ajax 改造,首先要下载 jQuery 的最新版本,它是所有新功能的基础(参见 参考资料)。如果您已经学习了本系列的第 1 部分和第 2 部分,应该已经安装了 jQuery 1.2.1。到编写本文时,当前版本是 1.2.3,其中增加了一些 bug 补丁。

还需要从 参考资料 小节下载两个插件。jQuery UI Tabs 是 jQuery UI 的一部分,jQuery UI 是一组可配置的用户界面部件和组件。jQuery UI Tabs 把 ul 元素转换为选项卡式界面,并把内联内容和 Ajax 标记转换为这些选项卡的内容。jCarousel 是一个单独的插件,它把图像集转换为幻灯片。与 jQuery UI Tabs 一样,这些幻灯片的内容可以是内联内容或是 Ajax。

下载这些组件之后,把它们放在应用程序目录结构中的适当位置。每个下载包都包含代码示例和其他多余的文件,可以根据自己的需要保留或删除它们。每个 JavaScript 库的简化版适用于生产环境,但是您可能希望获得完整的源代码,这样就可以通读代码,更好地了解每个组件:

  • 对于 jQuery,需要的惟一文件是库本身的简化版。
  • 对于 jQuery UI Tabs,应该获取库的简化版、附带的 CSS 文件和两个图像:loading.gif 和 tab.png。因为 tab.png 假设您的站点采用白色背景,可能需要在 Adobe Photoshop 或其他图像编辑程序中修改它,使它的圆角效果与 Customize Me Now 的实际背景颜相匹配。
  • 对于 jCarousel,需要库的简化版和附带的 CSS 文件,以及库附带的三种预打包的 skin 之一的完整目录。在 jCarousel 中,skin 由一个 CSS 文件和一组图像组成,用来控制 carousel 的视觉样式。对于 Customize Me Now,我们使用 Tango skin,但是把目录重命名为 “tango-modified”;我们将对默认的 Tango 方案做一些修改。

以上文件就位之后,要把它们插入 detailA.html 的开头。结果见清单 1:

清单 1. 部署 jQuery 及其插件
<!--jquery assets-->
<script type="text/javascript"
	src="../js/jquery-1.2.3.minjs"></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">

<!--jcarousel assets-->
<script type="text/javascript"
	src="../jcarousel/lib/jquery.jcarousel.pack.js"></script>
<link rel="stylesheet" type="text/css"
	href="../jcarousel/lib/jquery.jcarousel.css" />
<link rel="stylesheet" type="text/css"
	href="../jcarousel/skins/tango-modified/skin.css" />

创建 DHTML 图像 carousel

要想创建 DHTML 图像 carousel,首先要修改 jCarousel 的外观。因为 jCarousel 可以配置为显示水平或垂直的图像 carousel,所以它的样式表包含用来实现这两种类型的规则。我们的幻灯片是水平的,所以可以删除与垂直幻灯片相关的所有 CSS 声明。另外,需要修改许多元素的宽度、高度、填充和空白边。在默认情况下,jCarousel 创建的幻灯片在任何时候都显示三个小的缩略图。在我们的幻灯片中,图像大得多(宽 500 像素),所以应该每次只显示一个图像。完成这些修改之后,skin.css 应该像清单 2 这样:

清单 2. 幻灯片的 CSS 代码
..jcarousel-skin-tango.jcarousel-container {
    -moz-border-radius: 10px;
    background: #F0F6F9;
    border: 1px solid #346F97;
}

..jcarousel-skin-tango.jcarousel-container-horizontal {
    width: 502px;
    padding: 20px 125px !important;
}

.jcarousel-skin-tango .jcarousel-clip-horizontal {
    width:  502px;
    height: 410px;
}

..jcarousel-skin-tango .jcarousel-item {
    width: 502px;
    height: 410px;
}

..jcarousel-skin-tango .jcarousel-item-horizontal {
    margin-right: 125px;
}

..jcarousel-skin-tango .jcarousel-item-placeholder {
    background: #fff;
    color: #000;
}

/**
 *  Horizontal Buttons
 */
..jcarousel-skin-tango .jcarousel-next-horizontal {
    position: absolute;
    top: 43px;
    right: 5px;
    width: 32px;
    height: 32px;
    cursor: pointer;
    background: transparent url(next-horizontal.png) no-repeat 0 0;
}

..jcarousel-skin-tango .jcarousel-next-horizontal:hover {
    background-position: -32px 0;
}

..jcarousel-skin-tango .jcarousel-next-horizontal:active {
    background-position: -64px 0;
}

..jcarousel-skin-tango .jcarousel-next-disabled-horizontal,
..jcarousel-skin-tango .jcarousel-next-disabled-horizontal:hover,
..jcarousel-skin-tango .jcarousel-next-disabled-horizontal:active {
    cursor: default;
    background-position: -96px 0;
}

..jcarousel-skin-tango .jcarousel-prev-horizontal {
    position: absolute;
    top: 43px;
    left: 5px;
    width: 32px;
    height: 32px;
    cursor: pointer;
    background: transparent url(prev-horizontal.png) no-repeat 0 0;
}

..jcarousel-skin-tango .jcarousel-prev-horizontal:hover {
    background-position: -32px 0;
}

..jcarousel-skin-tango .jcarousel-prev-horizontal:active {
    background-position: -64px 0;
}

..jcarousel-skin-tango .jcarousel-prev-disabled-horizontal,
..jcarousel-skin-tango .jcarousel-prev-disabled-horizontal:hover,
..jcarousel-skin-tango .jcarousel-prev-disabled-horizontal:active {
    cursor: default;
    background-position: -96px 0;
}

接下来,对 detailAhtml 的标记做一处小修改。页面原来版本中的图像已经组织成一个 ul 元素。需要在这个 HTML 列表外添加一个 div。在这个包装器 div 中添加一个值为 jcarousel-skin-tangoclass 属性,这样就会把 Tango skin 中的样式规则应用于这个 carousel。还要在这个 div 中添加一个值为 imageCarouselid 属性,让 jQuery 可以解析文档对象模型(DOM)并找到要转换的元素。完成修改之后,HTML 如清单 3 所示:

清单 3. 幻灯片的 HTML 代码
<div id="imageCarousel" class="jcarousel-skin-tango">
	<ul class="productImages">
		<li>
			<img class="product" alt="product photo"
				width="500" height="375" src="../img/pizza1.jpg" />
			Photo credit: <a target="_blank"
			href="http://www.flickr.com/photos/kankan/">Kanko*</a>,
			Flickr, Creative Commons Attribution License
		</li>
		<!--additional <li> items, images and photo credits here-->
	</ul>
</div>

最后,创建幻灯片的 JavaScript 代码。由于有 jCarousel 的帮助,这只需要几行代码。在 HTML 文档的顶部,包含的 JavaScript 和 CSS 文件的下面,添加清单 4 所示的内联脚本块:

清单 4. 幻灯片的 JavaScript 代码
<script type="text/javascript">
$(document).ready(function() {
 
	$('#imageCarousel').jcarousel({
		scroll: 1
	});
 
});
</script>

这段 JavaScript 代码看起来简单,但是它会完成所有复杂的工作。它通过调用 jQuery 的 ready 事件处理函数,指示 jQuery 等待页面的 DOM 装载完成,然后执行其他任务。通过调用 jQuery 的选择符机制($ 函数),让 jQuery 找到 id 属性值为 imageCarousel 的 DOM 元素。然后,调用包含这个 DOM 节点的对象上的 jcarousel 方法,这让 jCarousel 把这个节点中的所有标记转换为一个图像幻灯片。需要向 jcarousel 方法传递一个参数:配置参数的散列。jCarousel 的大多数默认选项是合适的,所以这个散列只包含一个键-值对:scroll,值为 1,这让 jCarousel 每次只显示一个图像。

现在,图像幻灯片完成了。

创建 DHTML 选项卡

接下来,为页面上的每部分内容创建 DHTML 选项卡。首先,把每部分内容包装在一个新的 div 中,并在其中添加惟一的 id 属性和值为 tabContentclass 属性。修改后的内容见 清单 5。这些包装器的作用是为 jQuery UI Tabs 提供钩子,让它知道把哪些标记转换为选项卡的内容。

清单 5. detailA.html 中五部分内容的 HTML
<div class="tabContent" id="introduction">

	<h2>Introduction</h2>

	<!--paragraphs of text content here-->
 
</div>

<div class="tabContent" id="moreDetails">

	<h2>More Details</h2>

	<!--paragraphs of text content here-->

</div>

<div class="tabContent" id="userReviews">

	<h2>User Reviews</h2>

	<!--paragraphs of text content here-->

</div>

<div class="tabContent" id="techSpecs">

	<h2>Technical Specifications</h2>

	<!--paragraphs of text content here-->

</div>

<div class="tabContent" id="productImages">

	<h2>Product Images</h2>

	<div id="imageCarousel" class="jcarousel-skin-tango">
    	<ul class="productImages">
			<!--<li> items from your image slideshow here-->
		</ul>
	</div>

</div>

接下来,在 HTML 文档中添加一些标记:一个无序的链接列表。这会出现在文档顶部,就在实际内容的第一部分前面。jQuery UI Tabs 会把这些标记转换为新的内容选项卡界面。尽管链接的 href 属性看起来像内部文档锚,但是它们实际上与前面创建的包装器 divid 属性匹配。这些标记见清单 6:

清单 6. detailA.html 中增加的 HTML 内容
<ul class="navTabs">
    <li><a href="#introduction"><span>Introduction</span></a></li>
    <li><a href="#moreDetails"><span>More Details</span></a></li>
    <li><a href="#userReviews"><span>User Reviews</span></a></li>
    <li><a href="#techSpecs"><span>Technical Specifications</span></a></li>
    <li><a href="#productImages"><span>Product Images</span></a></li>
</ul>

接下来,创建两组样式,分别用于启用和不启用 JavaScript 的浏览器。在启用 JavaScript 的浏览器中,用户会看到新的选项卡式界面,所以需要调整选项卡内容的外观。首先,添加一定的填充和边框。然后,在每个选项卡中禁止显示 h2 元素;在新界面中,这些标题会显示为选项卡的文本标签,所以不需要重复显示。这些样式规则应该添加在全局样式表中。

禁用 JavaScript 的用户不会看到选项卡式界面。对于这些用户,需要隐藏前面添加的无序列表,删除在内容部分中添加的边框和填充,恢复显示 h2 元素。幸运的是,通过使用 noscript 标记很容易完成这些任务。为了在禁用 JavaScript 的浏览器中覆盖全局样式,在 noscript 标记中添加一些 CSS 规则。这些样式只在关闭 JavaScript 支持的情况下应用。

最终结果是一组外观漂亮的标记,它们会根据浏览器的功能显示非常不一样的界面。完成之后,两个 CSS 代码块见清单 7 和清单 8:

清单 7. 在 customizemenow.css 中添加的 CSS 样式
#CMN .tabContent {
	padding: 14px;
	border: 1px solid #97a5b0;
}
#CMN .tabContent h2{
	display: none;
}
清单 8. 在 detailA.html 的 noscript 块中添加的样式
<noscript>
	<style type="text/css">
		#CMN .tabContent {
			padding: 0;
			border: 0;
		}
		#CMN .tabContent h2 {
			display: block;
		}
		#CMN ul.nav {
			display: none;
		}
	</style>
</noscript>

最后,添加用来创建 DHTML 选项卡的定制 JavaScript。为此,需要在前面创建的内联脚本块(见 清单 4)中添加代码。清单 9 给出内联脚本块:

清单 9. detailA.html 中的定制 JavaScript
$(document).ready(function() {
 
	/*earlier jCarousel code goes first*/

	/*create tabs from an unordered list using jquery.ui.tabs*/
	$('ul.navTabs').tabs(
		{ fx: { height: 'toggle', opacity: 'toggle' } }
	);
 
});

与使用 jCarousel 时一样,jQuery UI Tabs 的定制 JavaScript 也非常简单。同样使用 jQuery 的选择符机制寻找一个 DOM 元素(在这里是无序的链接列表)。然后,jQuery UI Tabs 的 tabs 方法把这个 ul 及其 li 子元素转换为选项卡式界面。tabs 方法检查无序列表中的链接,并把每个选项卡与对应的包含内容的 div 匹配起来。每个 div 最初被隐藏,直到用户单击对应的 li 元素(显示为选项卡)。与 jcarousel 方法一样,可以通过传递初始化参数的散列来定制 tabs 方法的行为。在这里,我们添加一些视觉效果,让选项卡在浏览器中的表现更酷。最后注意一点:需要在创建选项卡界面之前创建 jCarousel 图像幻灯片;如果先创建选项卡界面,jCarousel 就会在图像 carousel 的 CSS 属性方面产生混乱。

检查单页面版本

如果看一下 Customize Me Now 2.1 的 Product Details 页面的版本 A,就会看到 DHTML 选项卡(图 5)和图像 carousel(图 6)的显示效果。为了看到淡出和窗帘效果等视觉效果,在 Web 浏览器中装载页面。

图 5. 改进后的 Product Details 页面:选项卡式内容
改进后的 Product Details 页面:选项卡式内容
图 6. 改进后的 Product Details 页面:图像 carousel
改进后的 Product Details 页面:图像 carousel

与 1.1 版相比,2.1 版的 Product Details 页面版本 A 有几个优点。它对用户隐藏了庞大复杂的信息,提供了更加简洁的界面。选项卡式导航方法使用户能够有条理地吸收理解各部分内容。如果在浏览器中禁用 JavaScript 并重新装载页面,就会看到与 1.1 版几乎完全一样的页面。这就是渐进式改进的作用。但不幸的是,这个版本没有解决单页面方法的带宽问题。用户仍然必须下载所有文本和图像,即使他们可能根本不查看这些内容。下面就来解决这个问题。

改造多页面版本

既然已经在单页面版本的 Product Details 页面中添加了选项卡和图像 carousel,下面就对多页面版本进行相似的改进。这里的代码比较复杂,因为选项卡和 carousel 的内容要通过 Ajax 动态地装载到页面中。但是,这会换来更快的页面装载速度并节省带宽。额外的内容只在需要时动态地装载。

创建 Ajax 图像 carousel

要想用多页面的 Product Details 内容创建一个图像 carousel,第一步是构建一个新的 JavaScript Object Notation(JSON)文档。JSON 是一种与 XML 相似的数据传输格式,但是更轻量。这种格式非常适合 Ajax,因为 jQuery 和其他 Ajax 框架可以可靠地把 JSON 文档转换为 JavaScript 对象,而且速度非常快。我们的 JSON 文档不包含任何标记,只包含要转换为幻灯片的图像的 URL 和元数据。创建这个文档之后,把它保存为 detailB5-fragment.html,见 清单 10

清单 10. detailB5-fragment.html 中的 JSON 数据
{"items": [
	{
		"url": "pizza1.jpg",
		"width": "500",
		"height": "375",
		"creditURL": "kankan",
		"creditLabel": "Kanko*"
	},
	{
		"url": "pizza2.jpg",
		"width": "500",
		"height": "374",
		"creditURL": "lenore-m",
		"creditLabel": "L. Marie"
	},
	{
		"url": "pizza3.jpg",
		"width": "500",
		"height": "375",
		"creditURL": "roadhunter",
		"creditLabel": "Topato"
	},
	{
		"url": "pizza4.jpg",
		"width": "500",
		"height": "369",
		"creditURL": "sgt_spanky",
		"creditLabel": "Kevitivity"
	},
	{
		"url": "pizza5.jpg",
		"width": "500",
		"height": "368",
		"creditURL": "fooey",
		"creditLabel": "foéöÞoooey"
	},
	{
		"url": "pizza6.jpg",
		"width": "500",
		"height": "334",
		"creditURL": "pancakejess",
		"creditLabel": "jsLander"
	}
]}

接下来,按照前面处理 detailA.html 的方式(参见 清单 1),在 detailB1.html 的顶部添加 jQuery、jQuery UI Tabs 和 jCarousel 文件。然后,向 detailB1.html(它为图像幻灯片创建占位符)的文档体添加 HTML;jCarousel 将用图像内容填充这个空的无序列表。占位符的 HTML 见 清单 11

清单 11. detailB1.html 中的占位符
<div class="tabContent" id="productImages">
 
	<h2>Product Images</h2>
 
	<div id="imageCarousel" class="jcarousel-skin-tango">
    	<ul class="productImages">
		</ul>
	</div>
 
</div>

最后,在 HTML 文档头中这些文件下面的脚本块中添加定制的 JavaScript 代码。这个脚本块见 清单 12

清单 12. detailB1.html 中的 JavaScript 代码
<script type="text/javascript">

window.alert = function() {
	return;
};

$(document).ready(function() {
 
	/*
		create an image slideshow from a JS array of URLs using
		jcarousel
	*/
	var itemLoadCallback = function(carousel, state) {
		if (state != 'init') {
			return;
		}
		jQuery.getJSON("detailB5-fragment.html", function(data){
	 		itemAddCallback(carousel, carousel.first,
				carousel.last, data.items);
		});
	};

	var itemAddCallback = function(carousel, first, last, data) {
	    for (i = 0, j = data.length; i < j; i++) {
	        carousel.add(i, getItemHTML(data[i]));
	    }
	    carousel.size(data.length);
	};

	var getItemHTML = function(d) {
		return '<img class="product" alt="product photo" width="' +
			d.width + '" height="' +
			d.height + '" src="../img/' +
			d.url + '" />Photo credit: <a target="_blank"' +
			' href="http://www.flickr.com/photos/' +
			d.creditURL + 's/">' +
			d.creditLabel + '</a>, Flickr, ' +
			'Create Commons Attribution License'
		;
	};

	jQuery('#imageCarousel').jcarousel({
		itemLoadCallback: itemLoadCallback,
		scroll: 1
	});
 
	jQuery('ul.productImages').css("width","3012px");
 
});
</script>

这段 JavaScript 比前面创建的代码都复杂。它包含以下函数:

  • itemLoadCallback。使用 Ajax 从前面创建的 JSON 文档读取数据,然后把数据传递给 itemAddCallback
  • itemAddCallBack。解析 itemLoadCallback 装载的 JSON 数据并把每个图像添加到 carousel 中。
  • getItemHTML。把 JSON 数据转换成插入 DOM 所需的 jCarousel 标记。

为了调用这些函数,要把 itemLoadCallback 作为选项散列的一部分传递给 jcarousel 方法。这告诉 jCarousel,它应该用 Ajax 数据动态地构建幻灯片,而不是使用 DOM 中现有的标记。itemLoadCallback 和它的辅助函数会自动完成这个任务。但是,jCarousel 在采用这种方式时会产生两个奇怪的现象。

首先,jCarousel 似乎会错误地计算动态装载的内容的 CSS 属性。结果是在幻灯片中有几个图像根本不出现。调试表明,jCarousel 弄错了包含图像的 ul 的宽度。您可以花时间调试这个问题并扩展 jCarousel 代码来纠正它,但是有一个更简便的方法:在构建 carousel 之后,用 jQuery 的 css 方法调整它的宽度,使它与 Product Details 版本 A 中非动态生成的 carousel 的宽度匹配。

jCarousel 的第二个问题与第一个问题相关:因为 jCarousel 对 DOM 元素宽度的计算不正确,所以每当用户调整浏览器窗口大小时,它会显示一个错误消息。这个错误消息以 JavaScript 警告框的形式出现,这绝对不是很好的用户体验。要想纠正这种行为,可以用一个哑函数替换 JavaScript 内置的 window.alert 方法。

创建 Ajax 选项卡

要想在多页面的 Product Details 上使用 jQuery UI Tabs,应该混合使用内联内容和动态的 Ajax 内容。用 detailB1.html 文件作为所有内容的包装器页面(内容以前包含在 detailB2.html、detailB3.html 和 detailB4.html 中)。通过 Ajax 把这些文件的内容装载到 detailB1.html 中的问题是,每个页面都包含一个完整的 HTML 文档;而我们真正需要的是每个页面有一个对应的 div。为了解决这个问题,我们要创建这些页面的另一个版本:复制每个文件,修改文件名,删除多余的标记。结果是三个新文件,detailB2-fragment.html、detailB3-fragment.html 和 detailB4-fragment.html。这些文件与 清单 13 相似。当然,在真实环境中,可以用服务器端模板引擎生成完整 HTML 版和片段版两种内容。例如,Ruby on Rails 等框架可以根据请求是正常请求还是 Ajax 调用,自动地对相同的内容应用不同的包装器。但是,因为这个示例只使用客户端代码,所以我们用单独的文件模拟这种效果。

清单 13. HTML 片段文件的标记
<div class="tabContent" id="moreDetails">
 
	<h2>More Details</h2>

	<!--paragraphs of text content here-->

</div>

接下来,需要修改 detailB1.html 中的一些标记。在 Customize Me Now 1.1 中,这个页面包含一个辅助导航菜单,帮助用户在页面之间移动,见清单 14。对这些标记做一些修改,让 jQuery UI Tabs 把它转换为选项卡界面。修改后的标记见清单 15。

清单 14. detailB1.html 中原来的导航菜单标记
<ul class="nav">
	<li><a href="detailB1.html">Introduction</a></li>
	<li><a href="detailB2.html">More Details</a></li>
	<li><a href="detailB3.html">User Reviews</a></li>
	<li><a href="detailB4.html">Technical Specifications</a></li>
	<li class="last"><a href="detailB5a.html">Photos</a></li>
</ul>
清单 15. detailB1.html 中修改后的 Ajax 选项卡标记
<ul class="nav">
    <li><a href="detailB1.html"><span>Introduction</span></a></li>
    <li><a href="detailB2.html"><span>More Details</span></a></li>
    <li><a href="detailB3.html"><span>User Reviews</span></a></li>
    <li><a href="detailB4.html"><span>Technical Specifications</span></a></li>
    <li class="last"><a href="detailB5a.html"><span>Product Images</span></a></li>
</ul>

接下来,在页面上的内联脚本块中添加 JavaScript 代码。完成之后,脚本块类似于清单 16:

清单 16. 用来创建 Ajax 选项卡的内联脚本块
$(document).ready(function() {
 
	/*transform urls for tabs with inline content*/
	$('ul.nav > li:first > a').attr("href", "#introduction");
	$('ul.nav > li:last > a').attr("href", "#productImages");

	/*transform urls for tabs with ajax content*/
	$('ul.nav > li:not(:first):not(:last) > a').each(function (i) {
		var el = $(this);
		el.attr("href", el.attr("href").replace(".html",
			"-fragment.html"));
	});
 
	/*earlier jCarousel code goes here*/
 
	/*
		replace ul classname of "nav" with "navTabs" to
		reset styling to a blank state
	*/
	$('ul.nav').attr({"class":"navTabs"});

	/*create tabs from an unordered list using jquery.ui.tabs*/
	$('ul.navTabs').tabs(
		{ fx: { height: 'toggle', opacity: 'toggle' } }
	);
 
});

用来创建 Ajax 选项卡的 JavaScript 代码比前面的 DHTML 选项卡代码复杂得多。同样,这也是因为我们需要渐进式改进。在前面修改辅助导航菜单的标记时(见 清单 15),没有修改链接 URL。这样的话,在关闭 JavaScript 支持时,这些链接仍然表现正常。但是,为了创建 Ajax 选项卡,这些链接需要转换为 jQuery UI Tabs 可以理解的格式。对于包含内联内容的 Introduction 和 Product Images 选项卡,href 属性采用 #wrapperDivIDAttribute 格式。对于其他三个选项卡(其内容是通过 Ajax 获得的),href 属性需要引用前面创建的 HTML 片段文件。幸运的是,jQuery 很容易转换这些链接。

也很容易修改辅助导航菜单的 class 属性。许多样式规则与组成菜单的 ul 相关联。在把 li 元素转换为选项卡时,需要取消原来的样式。为此,使用 jQuery 把 ul 元素的 class 属性值由 nav 改为 navTabs。完成之后,可以用 jQuery 选择符机制($ 函数)选择这个 ul 元素并对它应用 tabs 方法。

现在基本上完成了,但是还有一些 CSS 问题要处理。首先,需要覆盖所有不应该应用于页面的 Ajax 版本的样式。为此,与前面一样在页面顶部添加一个 noscript 样式块。但是,这一次需要用一个额外的样式规则隐藏前面添加的哑图像幻灯片标记。完成之后,noscript 样式块如清单 17 所示:

清单 17. detailB1.html 的 noscript 样式
<noscript>
	<style type="text/css">
		#CMN .tabContent {
			padding: 0;
			border: 0;
		}
		#CMN .tabContent h2 {
			display: block;
		}
		#CMN #productImages {
			display: none;
		}
	</style>
</noscript>

最后,需要解决 jQuery Tabs UI 把 Ajax 内容插入选项卡界面时的一个怪异现象。如果在 Web 浏览器中看看这个页面,会看到选项卡和 Ajax 选项卡内容之间的边框厚度是预期厚度的两倍。这是因为 jQuery UI Tabs 把每部分 Ajax 内容放在一个具有顶边框的包装器 div 中;但是,在包装器内部,已经对 HTML 片段应用了边框,所以会看到双重边框。为了纠正这个问题,在 HTML 片段文件的包装器 div 中添加另一个类,然后添加一个样式规则,从具有这个类的元素上删除顶边框。代码见清单 18 和清单 19:

清单 18. 纠正双重边框问题的 CSS 声明
#CMN .tabContent.noTop {
	border-top: 0;
}
清单 19. 应用于 HTML 片段文件的 CSS 类
<div class="tabContent noTop" id="moreDetails">
 
	<h2>More Details</h2>

	<!--paragraphs of text content here-->

</div>

检查多页面版本

现在,可以在 Web 浏览器中查看 2.1 版的 Product Details 页面版本 B。它的外观和表现应该很像版本 A(图 5图 6)。但是在幕后,版本 B 的效率高得多。在用户单击对应的选项卡之前,并不装载 Ajax 内容,这会节省带宽。但是,这并不会节省传输图像所需的带宽。按照定制 Ajax 代码的工作方式,会默认装载 jCarousel 幻灯片中的所有图像。尽管如此,页面的装载时间仍然得到了改进。直到显示页面的其余部分之后,浏览器才会向服务器请求这些图像。对于使用慢速连接的用户,这会显著改进用户体验。

还可以进一步改进这个页面:让浏览器在用户选择 Product Images 选项卡之前不自动装载图像。这样的话,如果用户不打算查看图像,就不必浪费带宽来装载它们。这样的解决方案会增加 JavaScript 代码的复杂性,但是如果产品的图像比较多,这样做就是值得的。

当关闭 JavaScript 支持时,Product Details 的版本 A 和 B 之间的根本差异就会表现出来。版本 A 会退化为单一滚动页面,而版本 B 退化为多页面版本。

结束语

在本系列的第 3 部分中,学习了如何应用渐进式改进原理实现现代的便于使用的 Ajax 界面。还进一步了解了 jQuery 及其插件。可以使用在本文中学到的技能进一步改进您的站点。例如,可以使用 jQuery Cookie 插件让浏览器在用户下一次访问页面时记住原来选择的选项卡。还可以在 Purchase Confirmation 页面上构建选项卡式界面:交叉销售、订单重放和帐单重放各有一个选项卡。改进的机会是无限的。

在真实环境中,可能根本用不到构建 Product Details 页面的三个版本。对于这样的简单功能,这样做花的时间太多了。另外,混合使用不同的界面会把用户弄糊涂。尽管如此,本文说明了在 jQuery 等开放源码工具的帮助下,很容易实现 Ajax、渐进式改进和以用户为中心的设计。


下载

描述名字大小
原演示程序的源代码wa-aj-overhaul3OnePointOne.zip797KB
改进的演示程序的源代码wa-aj-overhaul3TwoPointOne.zip889KB

参考资料

学习

  • 您可以参阅本文在 developerWorks 全球网站上的 英文原文
  • 了解关于 JSON 的更多信息。
  • 参与 jQuery 社区并访问 Learning jQuery Web 站点上的教程和论坛。
  • 通过 jQuery documentation Web 站点了解 jQuery API。
  • 通过 “使用 jQuery 简化 Ajax 开发”(Jesse Skinner,developerWorks,2007 年 4 月)等其他文章进一步学习 Ajax。
  • 通过阅读 Learning jQuery(Packt Publishing,2007 年 7 月)掌握 jQuery。
  • 查阅 jQuery in Action(Manning Publication Co.,2008 年 2 月),获取更多 jQuery 帮助。
  • jQuery Reference Guide(Packt Publishing,2007 年 7 月)中可以找到更全面的 jQuery 参考资料。
  • 在 Brian Dillard 的 blog Agile Ajax 中了解关于 jQuery 和其他 UI 主题的更多信息。
  • 关于 Ajax 应用程序安全性最佳实践的全面概述,参见 Billy Hoffman 和 Bryan Sullivan 的新书 Ajax Security(Addison Wesley Professional,2007 年 12 月)。
  • Ajax 资源中心:developerWorks 上所有有关 Ajax 的问题都可以在这里找到解答。
  • 浏览 技术书店 中关于这些主题和其他技术主题的图书。

获得产品和技术

  • jQuery 主站上下载 jQuery、学习它的使用方法和寻找各种插件。到编写本文时,当前版本是 1.2.3。
  • jQuery UI Tabs 是一个 jQuery 插件,可以把内联内容或 Ajax 内容包装在选项卡式界面中。这个插件是新的可定制部件和用户界面组件集 jQuery UI 的一部分。
  • jCarousel 是另一个 jQuery 插件,可以把内联内容或 Ajax 图像装载到幻灯片/carousel 界面中。
  • jQuery Cookie 是另一个插件,可以把它添加到 jQuery UI Tabs 中,让浏览器在用户下一次访问页面时 “记住” 最近选择的选项卡。

讨论

更多下载

条评论

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=325831
ArticleTitle=Ajax 改造,第 3 部分: 用 jQuery、Ajax 选项卡和照片 carousel 改进现有的站点
publish-date=07312008