jQuery 1.4.1 中文文档 CHM 版发布
新鲜出炉,更新了大量内容,应该还有一些BUG,欢迎纠错:
http://code.google.com/p/jquery-api-zh-cn/downloads/list
本来更早出来的,结果半路又杀出1.4.1,而且发现bind、live还有ajax等方法有大量更新,所以重新翻译这三个方法,消耗了大把时间。 所以今天才发出来。
原文来自 shawphy.com
新鲜出炉,更新了大量内容,应该还有一些BUG,欢迎纠错:
http://code.google.com/p/jquery-api-zh-cn/downloads/list
本来更早出来的,结果半路又杀出1.4.1,而且发现bind、live还有ajax等方法有大量更新,所以重新翻译这三个方法,消耗了大把时间。 所以今天才发出来。
原文来自 shawphy.com
Ajax 技术改变了大型商业 Web 应用程序的外观,但是许多较小的 Web 站点都不具备足够的资源重新构建完整的用户界面(UI)。Ajax 的一些新特性能够解决实际中的界面问题并改善用户体验。本文将展示如何使用简单的模式窗口消除弹出窗口和导航死角。通过应用渐进增强(progressive enhancement)这一理念,能够保证这些增强的 UI 特性不会损害站点的可访问性,并且严格遵守 Web 标准。
本文假设您已经牢固掌握超文本标记语言(Hypertext Markup Language,HTML)和级联样式表(Cascading Style Sheet,CSS),基本了解 JavaScript 编程和 Ajax。示例应用程序仅使用客户端代码构建;本文演示的技术适用于任何服务器端应用程序框架。要运行示例站点,您至少需要在本地主机上运行一个基本的 Web 服务器。此外,您也可以仅跟随源代码并在我的 Web 服务器上查看运行中的示例站点。
引导用户跟随特定路径 — 即,从产品搜索到付款购买 — 这种需求与 Web 本身的历史一样久。它一直都充满着风险:让用户迷失方向。您的导航路径越长、越复杂,用户经历的内容就越多。您需要为用户提供足够的信息,以防止他们在导航过程中失去兴趣。
在 Web 1.0 世界,购物站点通过构造一条流畅的路径(从搜索和结果到选择和购买)流线化用户体验。当购买路径提供的信息无法满足用户需求时,他们需要通过导航找到提供 更多信息的产品细节或比较页面。这种方法存在的问题是,它们使用户离开了购买路径,增加了用户放弃购买的机会。而且还难以维护,因为您的导航逻辑必须存储 与用户如何到达此死角相关的信息。
弹出窗口似乎提供了一个解决方案。通过弹出窗口提供补充信息,主窗口中直接的分布路径不会受到干扰。然而,不幸的是,弹出窗口容易使人混淆和反感。它们可能比上面的导航弯路更容易维护,但是它们很可能使用户退出您希望其完成的购买流程。
幸运的是,开源 JavaScript 库提供了一种简单方式,可以彻底摆脱导航弯路和弹出窗口。本文将演示如何使用 Ajax 和 Dynamic HTML (DHTML) 技术在工具提示、lightbox 和其他模式窗口中呈现补充信息。由于这些元素可以动态地插入任何页面,它们能够保持从主页到购买的快速、分步路径。
本文中的示例应用程序主要针对电子商务。我已经构造了一个虚构的购物应用程序Customize Me Now,允许用户定制和购买一组不同的产品:批萨、旅行包或有价证券。当然,在实际中,这些产品类别绝不会出现在同一个站点上。但是,将它们放在一起可以演示许多站点面对的复杂、真实的导航难题。
本 文首先提供一个 Web 1.0 版的 Customize Me Now,然后将其改进为 Web 2.0 版,当您了解到补充信息只是通过一个 Ajax 调用提供时,就能想象到导航路径有多么的流畅。此处涉及的技术适合于任何需要保持简单性并能引导用户的流程。配置产品、做一个调查、注册一些服务,或者只 是完成一个注册表单 — 所有这些流程都可以使用 Ajax 实现流线化。
现在,也许不再需要介绍 Ajax 了:在 Web 开发领域到处都是它身影。这些年来,聪明的编程人员一直使用 JavaScript 代码逐步更新 Web 页面,而不再与服务器往返通信。但仅仅是因为xmlHttpRequest对象的 采用 — 最初是一个 Windows® Internet Explorer® 扩展,但现在支持许多不同的浏览器 — 才使 Ajax 流行起来的。无论何时,只要您看到一个与桌面应用程序非常相似的 Web 应用程序,那么它很可能采用了 Ajax。本文无意讨论 Ajax 编程的基础知识,但是您将使用到许多使用 Ajax 技术的开源库。
Ajax UI 通常使用工具提示、lightbox 和模式窗口。它们都是指一个浏览器视区(viewport)的弹出屏幕,而不是在一个独立的窗口中运行。工具提示(Tooltips)通常是一些提供上下文内容的小型窗口,将鼠标悬停在一个触发器元素上就会显示。模式窗口(Modal windows)通常比较大,而且通过一个单击事件触发。Lightbox是 一种特殊的模式窗口,它通过半透明的重叠将窗口的原始内容和模式内容分隔开。任何一种这类容器都可以填充各种内容:使用 DHTML 技术隐藏的内联内容;通过 Ajax 调用从服务器拉取的新内容;或者拉入 iframe 中的完整的独立文档。流行的 DVD 租赁服务 Netflix 提供了一个关于这些界面元素的出色示例。
工具介绍:jQuery、GreyBox、ThickBox、JTip 和 jQuery 表单
自 从 2005 年 Ajax 开始流行之后,开源 JavaScript 工具箱不断增多。每个工具箱都有自己的优点、缺陷和特性,但是出色的工具箱隐藏了浏览器的差异,并提供了一个针对 Ajax、DHTML 和视觉效果的出色的跨浏览器应用程序编程接口(API)。一些解决方案(比如 Google Web Toolkit)使用服务器端 Java™ 代码自动生成客户端 JavaScript 代码。但是,大多数都使用本机 JavaScript 库。
您 将使用一个这样的库,jQuery。自从 2006 年首次登场,jQuery 因其优雅的 API 和谦逊的 JavaScript 而变得非常流行。使用 jQuery,几乎无需对服务器代码、HTML 标记或 CSS 进行任何更改,您就可以将一个未使用 Ajax 的 Web 1.0 站点转变为一个通过 Ajax 增强的 Web 2.0 站点。在运行时,您的 JavaScript 代码将会在浏览器内部转换现有的 HTML 元素和行为。如果 JavaScript 被禁用或者不受浏览器支持,标记将会按原来的方式运行。这种渐进增强原理可以确保您的 Web 应用程序仍然能够被广大用户访问。物理条件较差的移动设备、辅助性软件,或者甚至是 10 年前的 Web 浏览器:所有这些都能够运行您的应用程序。
除了支持渐进增强之外,jQuery 还允许您利用大型的、活跃的插件开发人员社区。许多 JavaScript 工具箱都试图预测所有可能的开发人员需求,但是 jQuery 只关注基本原理。其核心库仍然很简洁,而附加功能是通过开源社区的插件来提供的。具体来讲,您可以利用以下插件:
以下小结帮助您了解 Customize Me Now 1.0 — 它的用户体验和源代码。
图 1 展示了 Customize Me Now Results 页面和一个信息弹出窗口。它演示了站点能够向用户提供的与他们定制的产品有关的信息。尽管最佳的用户路径是从搜索和结果到定制和购买的直线路径,但该界面 却提供了一些迂回的路径。站点使用传统的弹出窗口显示每个产品和产品选项的一小段上下文信息。单击Pizza,您将会看到关于站点的批萨产品的信息。单击Cheese,您将会看到关于您的批萨可用的奶酪的信息。此外,用户可以导航到厂商的 Web 站点 — 同样通过弹出窗口呈现。
图 1. Results 页面 1.0

如 果用户想要获得更多信息,站点就会提供一个 Product Details 页面,其中包括每个产品的更详细信息 —— 图片、文章、用户评论等等。最后,如果用户想要将一个产品与其他产品进行比较,他可以在 Comparison 页上逐个查看这些产品。在这些资源中进行导航非常复杂。由于用户需要定制每个产品,很难在每步操作中都提供一个Add to cart链接。站点鼓励用户定制、然后添加到购物车中。但是也允许用户跳过这些步骤,使用一组默认选项添加到购物车,在稍后进行定制。
图 2 展示了 Customize Me Now 1.0 的站点地图。它显示了用户的路径非常复杂。大多数屏幕都链接到至少 2 或 3 个其他屏幕。
图 2. Customize Me Now 1.0 站点地图

Customize Me Now 的功能演示代码只提供了客户端部分:HTML CSS、JavaScript 代码和图像文件。在实际中,您显然还需要一个大型的服务器端组件:Microsoft® ASP.NET、Java 技术、Ruby on Rails、Django 或 PHP。但是 jQuery 的美妙之处在于,它纯粹关于客户端。您可以使用 Ajax 简化您的用户体验,无需服务器端组件。我没有提供任何服务器端代码,因为这些细节对于这个项目无关紧要。您只需知道当您打开一个 HTML 文件时,就会有一个 PHP 模板、一个 JavaServer Pages™ (JSP) 文件或任何其他将 HTML 发送到服务器的文件。
下载了 1.0 版应用程序的源代码之后,请看一下其中一个页面的标记。它应该与清单 1 类似。
清单 1. results.html 1.0 的 HTML 代码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Customize Me Now: Search Results</title>
<link rel="stylesheet" type="text/css" href="../css/customizemenow.css"/>
</head>
<body id="CMN">
<!--[header goes here]-->
<div id="main">
<form method="GET" action="comparison.html">
<h1>Search Results</h1>
<div>
<input type="submit" name="submitTop" id="submitTop"
value="Compare Selected Products" />
or <a href="index.html">search again</a>
</div>
<table>
<thead>
<tr>
<th>Product</th>
<th>Description</th>
<th>Options</th>
<th>Compare</th>
<th>Actions</th>
</tr>
</thead>
<tr>
<td>
<a target="productPopup"
href="productPopup.html?product=A">Pizza</a>
</td>
<td>
A delicious Italian treat.<br />
[<a href="detail.html?product=A">full product details</a>]
</td>
<td>
<ul>
<li>
<a target="optionsPopup"
href="optionsPopup.html?product=A">crust</a>
</li>
<li>
<a target="optionsPopup"
href="optionsPopup.html?product=A">cheese</a>
</li>
<li>
<a target="optionsPopup"
href="optionsPopup.html?product=A">toppings</a>
</li>
</ul>
</td>
<td>
<input type="checkbox" target="productPopup" name="compareA"
id="compareA" value="true" checked="checked"/>
</td>
<td>
<a href="customize.html?product=A">customize
product</a>
<a href="cart.html?product=A">add to cart with
default options</a>
</td>
</tr>
<!--[additional table rows go here]-->
</table>
<div>
<div>
<input type="submit" name="submitBottom" id="submitBottom"
value="Compare Selected Products" />
or <a href="index.html">search again</a>
</div>
</form>
</div>
<!--[footer goes here]-->
</body>
</html>
|
本文将介绍将 Customize Me Now 改进为 2.0 版的过程,本系列的第 2 部分还会进一步开发。
要将一个 Ajax 行为层添加到您的站点,第一步是下载所有的开源库。如果您从下载小节下载了示例 2.0 应用程序,那么所有的库都应包含在其中了。如果想要下载这些库的最新版本,您可以从 下载 小节重新下载一次。
接下来,为 jQuery 和表单插件创建一个 /js 目录。注意,GreyBox、ThickBox 和 JTip 都需要自己的目录;它们与图片、CSS 文件和多个 JavaScript 库绑定在一起,所有这些都要求特定的目录结构。当链接到您的 CSS 和 .js 文件时,您必须包含一小段脚本,用来为 GreyBox 设置正确的根目录指针。这个指针必须是一个绝对目录路径,因此您可能需要在自己的代码中调整该值。完成之后,HTML 文件的头部元素应该与清单 2 类似。
清单 2. Customize Me Now 2.0 头部元素
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Customize Me Now: Shopping Cart</title>
<!--customizemenow assets-->
<link rel="stylesheet" type="text/css" href="../css/customizemenow.css"/>
<!--jquery assets-->
<script type="text/javascript" src="../js/jquery-1.2.1.min.js"></script>
<script type="text/javascript" src="../js/jquery.form.js"></script>
<!--thickbox assets-->
<script type="text/javascript" src="../thickbox/thickbox.js"></script>
<link rel="stylesheet" type="text/css" href="../thickbox/thickbox.css" />
<!--jtip assets-->
<script type="text/javascript" src="../jtip/scripts/jtip.js"></script>
<link rel="stylesheet" type="text/css" href="../jtip/css/jtip.css" />
<!--greybox assets-->
<script type="text/javascript">
/*this needs to be a non-relative reference*/
var GB_ROOT_DIR = "/customizemenow/2/0/greybox/";
</script>
<script type="text/javascript" src="../greybox/AJS.js"></script>
<script type="text/javascript" src="../greybox/AJS_fx.js"></script>
<script type="text/javascript" src="../greybox/gb_scripts.js"></script>
<link rel="stylesheet" type="text/css" href="../greybox/gb_styles.css" />
</head>
使用 ThickBox 和 jQuery 将辅助链接转换为 lightbox
由于 jQuery 及其插件遵循渐进增强的原则,您几乎不需要定制 JavaScript 代码来创建 Ajax 功能。您只需向现有的 HTML 标记添加特定的属性。您的 JavaScript 库将解析 Document Object Model (DOM) 以寻找这些特殊属性,然后向拥有这些属性的元素添加合适的行为。jQuery 支持这种编码风格,当所有标记被呈现时它将自动解析 DOM。如果研究一下 jQuery 插件的底层操作,您将会发现它们都将自己的事件模型委托给了核心
现在已经解决了 Product Details 页的问题,接下来改进 Comparison 页。到达此页的惟一途径是通过表单提交;用户必须使用复选框选择需要比较的产品。只能使用 jQuery Forms 呈现该表单提交的结果,jQuery Forms 是一个库,它将一些方便的方法和事件挂钩(hook)封装到
这段代码展示了 jQuery API 的简单性。通过少量代码,您就截取了一个 HTML 表单的正常提交,并执行了一些定制 JavaScript 代码。使用这种技术,您可以在允许提交表单之前执行定制验证逻辑,或者在表单提交之后触发一个定制事件。在本例中,您需要避免提交表单。相反,我们将手动 “伪造” 表单提交生成的 HTTP 请求,以便将表单的目标定向到 ThickBox 窗口。 用户并不知道所有这些后台操作。他们只知道在提交表单之后,将在模式窗口中看到结果。查看 Comparison 页面之后,用户可以关闭 ThickBox 窗口,然后返回来定制和购买产品。 使用 ThickBox 呈现 Product Details 和 Comparison 页面的惟一问题是,对于 ThickBox 窗口来说页面太大了。您可以更改传递给 ThickBox 的页面宽度和高度值,但是如果用户使用较小的视区又该怎么办?您不想让 ThickBox 覆盖整个窗口,更不想让它延伸到视区以外。您只需将
模式窗口实现过程的最后一步是,限制 Product Details 和 Comparison 页面中不希望与用户交互的元素。由于这些页面现在只是为了提供信息,所以无需包含操作链接和按钮。也无需显示您的导航栏或其他 chrome。 可以通过几种方式实现此目的。可以从页面删除这些元素,但是这不符合您的渐进增强策略。不支持 JavaScript 的用户转到这些页面之后无法退出或继续购买流程。您也可以将 querystring 参数附加到您的链接,让服务器端框架使用不同的模板呈现这些页面。在实际中,您很可能会这么做。但是此处还有另一种有效方式,这种方式只依赖客户端代码: 老式的 页眉和页脚的结果 HTML 代码与清单 6 中的代码类似。
Product Details 页面的主要内容
Comparison 页面的主要内容
在浏览器中查看 Customize Me Now 2.0 Search Results 页面并启动 Product Details 或 Comparison 页面,您可以看到所有的改进结果。其结果应该与图 3 类似。 尽管本文涵盖了大量内容 — 向您展示了一些 Ajax 技术和最佳实践 —— 但是我们才刚刚开始。在本系列的第 2 部分中,您将使用 JTip 将弹出链接转换为工具提示,继续改善您的导航。然后,将使用 GreyBox 将 off-site 链接转换为 lightbox。最后,回顾一下示例应用程序背后的所有关键概念,并分析如何使用它们改善用户体验。如果您还想继续学习,您可以更深入地研究 Customize Me Now 2.0 的源代码,并在 Web 浏览器中查看实际效果。 |
现在已经回顾了第 1 部分中的模式对象的实现,接下来将通过 jTip 把所有弹出窗口转换成工具提示以优化导航。就像 ThickBox 一样,jTip 很容易实现。对于每一个想要转换的链接,只需添加如下属性:
class属性。这个神奇的词语会警告 jTip 的 Document Object Model (DOM)-解析例程应该把此链接转换成工具提示。即使此链接元素已经具有class属性,也可以简单地将“jTip”添加为额外的类。id属性具有惟一的值,以便 jTip 能够获得该链接的对象引用。id的值是什么并不重要,只要存在即可。name属性,jTip 使用它在工具提示内呈现标题。同样,只要属性存在,它的值是什么并不重要。如果不想让标题出现,只需将值设置为空字符串。将这些属性添加到标记之后,每个弹出链接如清单 1 所示:
清单 1. jTip 链接所需的 HTML 代码
<a name="About Pizza" id="pizza" target="productPopup" href="productPopup.html?product=A">Pizza</a> |
现在只需要调整 CSS。随 Customize Me Now 1.0 一起包含的 CSS 文件会以加了红色下划线的文本呈现大多数链接元素,当鼠标悬浮于其上时,这类文本会变化颜色。用户都希望单击加下划线的链接就能进入链接,但这些 jTip 链接却无此功能; 当用户将鼠标悬浮其上时,就触发了工具提示。因此,应该改变它们的样式使之与其他链接稍微不同,但同时又要确保它们充分突出以吸引用户与之交互。为此,可 以用一个新的 CSS 声明将某些定制样式添加到 jTip 类。保持红色不变,但去除下划线和悬浮状态。与此同时,添加一个新 CSS 声明以控制工具提示内容的宽度,从而不需要再用到滚动条。这个新声明基于这样一个事实:jTip 把它的工具提示和 JT 的id属性包装在同一个 div 里。最后,创建另一个声明以在工具提示内容内隐藏主标题;这样,就可以使用 jTip 通过链接的name属性自动生成的 header 了。完成之后,新的 CSS 如清单 2 所示:
清单 2. 用于 jTip 的 CSS
/*restyle tooltip links*/
#CMN a.jTip {
text-decoration: none;
color: #930;
}
/*narrow the width of pages that will be rendered by jTip*/
#JT #main.popup {
width: 200px;
padding: 4px;
}
/*hide the headline of pages that will be rendered by jTip*/
#JT #main.popup h1 {
display: none;
}
|
如果在 Web 浏览器内查看 2.0 Search Results 页面并将鼠标悬浮于产品和产品选项之上,就会看到运行中的 jTip,如图 1 所示:
图 1. 运行中的 jTip 的截图

使用 GreyBox 将 off-site 链接转换成 lightbox
排除了各种导航弯路的影响之后,Customize Me Now 2.0 就基本成型了。现在所需做的是处理 off-site 链接。查找结果页面仍然包含能连接到厂商网站的弹出链接。与前面用 jTip 替换的弹出窗口不同,这些链接可以转到不受控制的完全功能的 Web 站点。它们太大,因此无法以工具提示的形式呈现,而且它们与其他应用程序过于分开,因此也无法采用模式对话框的形式。GreyBox 正好适用于这种情况。
与多功能的 ThickBox 不同(可以处理 iframe 窗口、Ajax 数据、图片幻灯片和许多其他类型的内容),GreyBox 只有一种功能。它能够使用一种半透明的覆盖层(overlay)覆盖 Web 站点,然后在顶端打开另一个 Web 站点。Lightbox 处理与模式对话框之间的差别很小,但外观却显著不同。可以在这些链接上使用 ThickBox,但使用这种方式就不能如此清晰地区分两个站点。您总是希望用户认为他们看到的是两个完全不同的站点。
到现在为止,您应该比较熟悉使用 GreyBox 启用链接了。只需向链接添加一系列属性。
title:这会给 GreyBox 覆盖层一个标题。rel:添加这个不常用的 HTML 属性并将其值设为“gb_page_fs[]” — 一个启用开源库的特殊属性。把这些属性添加到 off-site 链接后,每个弹出链接将如清单 3 所示(尽管在现实世界中,Google 并不生产比萨):
清单 3. 用于 GreyBox 链接的 HTML
<a target="_new" href="http://www.google.com" title="Let's Pretend Google Is Our Product's Manufacturer" rel="gb_page_fs[]">manufacturer's website</a> |
同样,通过浏览器查看 Customize Me Now 2.0 的搜索结果页面并单击厂商站点的链接可以看到 GreyBox 的运行效果。如图 2 所示:
图 2. 运行中的 GreyBox 截图

至此,全部操作完毕。但这次 Ajax 改造是如何更改 Customize Me Now 的,又获得什么益处呢?
要理解这些更改的益处,请看图 3,它是最初的 1.0 版本的站点地图:
图 3. Customize Me Now 1.0 站点地图

现在请参看图 4,这是修改后的 2.0 版本的站点地图。现在导航路径提供了一条由搜索到购买的直线。站点所有者使用这种典型的通风管式的范例来增加转换,同时使用户更加容易理解站点的过程。您 仍然能访问相同的信息,但是现在额外的数据可以在过程中弹出,而不需要用户离开上下文环境。
图 4. Customize Me Now 2.0 站点地图

您已经设法增强了应用程序,同时也不因为 JavaScript Luddites 而破坏应用程序。要查看改进的效果,只需禁用浏览器中的 JavaScript 执行,然后与 2.0 站点进行交互。该站点的运作与 Customize Me Now 1.0 十分相似。除了某些链接看起来有些不同外,它的运作是一样的。
Customize Me Now 的 1.0 版和 2.0 版之间已经有了很大的改变。但这个应用程序还有很多可以改进之处。
您可能已经注意到工具提示和模式对话框并不是真正的传统 Ajax 调用。您所加载的是完整的 HTML 文档,而典型的 Ajax 功能加载的是小块的 XHTML、XML 或 JavaScript Serialized Object Notation (JSON)。ThickBox 和 jTip 都支持 Ajax 这种更为常用的格式。但加载完整的文档可以在每个页面的 Ajax 版本和非 Ajax 版本上使用相同的 URL。为了达到渐进增强的目标,这是最简单方式。对于传统的 Ajax,则必须更改服务器端的模板框架。此外,还必须编写定制 JavaScript 代码来向 URL 追加呈现逻辑以便服务器能够返回页面的全部或只返回页面的内容。借助 jQuery,所需编写的代码不会很多。但针对客户端演示而言,这是没有必要的。
本演示中并没有涉及 Ajax 开发在安全性方面的问题。Web 应用程序安全性本身就是一个很广泛的主题,超出了本文的讨论范围。在某个意义上,由于只侧重于客户端代码,本例省略了安全性。很多安全性措施都必须在服务 器上实现。这也就是说,处于各技术级的开发人员都必须为整个应用程序的安全性负起自己应付的责任。前端的开发人员也不例外,因为很多恶意的黑客攻击都是通 过利用 JavaScript 代码、CSS 和标记内的漏洞得逞的。有关此主题的更多信息,请参见 参考资料 部分。
了解定制过程之后,您还可以针对此应用程序进行很多操作。下面是利用额外的 JavaScript 和 CSS 调整实现其他一些改进:
这里进行的所有工作都涉及到把 Ajax 添加到应用程序,好像 Ajax 是解决所有问题的“万能钥匙”。但事实并非如此。
模式对话框和工具提示对 Customize Me Now 很有效,这是因为在其中呈现的内容的大小是可管理的,所使用的界面也只是简单的滚动界面。许多页面不适合使用模式对话框。如果 Web 页面使用复合的动态 HTML (DHTML) 标记或复杂的交互式 UI 窗口部件部件,那么它最好保持原样。不可以把 Ajax 特性简单地堆砌起来,而丝毫不考虑用户的体验。
同样地,ThickBox 应该用于信息页面而不是过程页面。当然,您也可以将 Customization 页面塞入 ThickBox 窗口并在搜索结果页面上弹出。但用户会对这样的界面有何反应呢?— 而您又该如何为其编写代码呢?如果用户在 ThickBox 窗口定制一个产品并单击 Submit,购物车会出现在相同的 ThickBox 窗口内吗?它下面的页面会自动更新吗?更重要的是,这会不会把用户搞糊涂了,连自己在定制过程的哪个地方都不知道呢?
高级 Ajax 应用程序能够在单一屏幕内提供丰富的交互功能。但这类应用程序一般都是从头开始构建起来的。它们需要仔细的规划、信息架构和用户体验设计。在改造现有应用 程序时,一个较小的更改往往都会涉及到很多的操作。本练习的目标就是向原本很传统的 Web 站点添加一点 Ajax 的魔力。如果希望得到的是一个完全基于 Ajax 的界面,最好还是从头创建。
在本期中,我们将把产品细节页面的内容放到一个选项卡式界面中,从而提高它的可管理性。还要在一个图像 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:单一页面,文本内容

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

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

图 4. 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 库的简化版适用于生产环境,但是您可能希望获得完整的源代码,这样就可以通读代码,更好地了解每个组件:
以上文件就位之后,要把它们插入 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,首先要修改 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-tango 的 class 属性,这样就会把 Tango skin 中的样式规则应用于这个 carousel。还要在这个 div 中添加一个值为 imageCarousel 的 id 属性,让 jQuery 可以解析文档对象模型(DOM)并找到要转换的元素。完成修改之后,HTML 如清单 3 所示:
清单 3. 幻灯片的 HTML 代码
<div id="imageCarousel"> <ul> <li> <img alt="product photo" width="500" height="375" src="http://www.phpchina.com/../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 选项卡。首先,把每部分内容包装在一个新的 div 中,并在其中添加惟一的 id 属性和值为 tabContent 的 class 属性。修改后的内容见 清单 5。这些包装器的作用是为 jQuery UI Tabs 提供钩子,让它知道把哪些标记转换为选项卡的内容。
清单 5. detailA.html 中五部分内容的 HTML
<div id="introduction">
<h2>Introduction</h2>
<!--paragraphs of text content here-->
</div>
<div id="moreDetails">
<h2>More Details</h2>
<!--paragraphs of text content here-->
</div>
<div id="userReviews">
<h2>User Reviews</h2>
<!--paragraphs of text content here-->
</div>
<div id="techSpecs">
<h2>Technical Specifications</h2>
<!--paragraphs of text content here-->
</div>
<div id="productImages">
<h2>Product Images</h2>
<div id="imageCarousel">
<ul>
<!--<li> items from your image slideshow here-->
</ul>
</div>
</div>
|
接下来,在 HTML 文档中添加一些标记:一个无序的链接列表。这会出现在文档顶部,就在实际内容的第一部分前面。jQuery UI Tabs 会把这些标记转换为新的内容选项卡界面。尽管链接的 href 属性看起来像内部文档锚,但是它们实际上与前面创建的包装器 div 的 id 属性匹配。这些标记见清单 6:
清单 6. detailA.html 中增加的 HTML 内容
<ul>
<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 页面:选项卡式内容

图 6. 改进后的 Product Details 页面:图像 carousel

与 1.1 版相比,2.1 版的 Product Details 页面版本 A 有几个优点。它对用户隐藏了庞大复杂的信息,提供了更加简洁的界面。选项卡式导航方法使用户能够有条理地吸收理解各部分内容。如果在浏览器中禁用 JavaScript 并重新装载页面,就会看到与 1.1 版几乎完全一样的页面。这就是渐进式改进的作用。但不幸的是,这个版本没有解决单页面方法的带宽问题。用户仍然必须下载所有文本和图像,即使他们可能根本 不查看这些内容。下面就来解决这个问题。
既然已经在单页面版本的 Product Details 页面中添加了选项卡和图像 carousel,下面就对多页面版本进行相似的改进。这里的代码比较复杂,因为选项卡和 carousel 的内容要通过 Ajax 动态地装载到页面中。但是,这会换来更快的页面装载速度并节省带宽。额外的内容只在需要时动态地装载。
要想用多页面的 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 id="productImages">
<h2>Product Images</h2>
<div id="imageCarousel">
<ul>
</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 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 方法。
要想在多页面的 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 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> <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><a href="detailB5a.html">Photos</a></li> </ul> |
清单 15. detailB1.html 中修改后的 Ajax 选项卡标记
<ul>
<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><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;
}
|
<div 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、渐进式改进和以用户为中心的设计。
在本文中,您将学习如何将多页表单转变成 Ajax 选项卡来流线化复杂的过程。所使用的用例是示例购物网站的结帐过程。如果没有 Ajax,在潜在的客户看来,多页表单既冗长又让人头疼。进行 Ajax 改造后,即使是一个复杂的结帐过程也可以变得友好和易于操作 — 只要您在如何构造用户界面方面足够用心。电子商务站点并不是这些技术的惟一受益者。同样的原理可以应用于为了完成一个多步过程用户必须填写一系列相关表单的任何情况。
要理解本文中的这些概念,请参考 Customize Me Now 1.2(参见下载,它是对未进行 Ajax 改造的原始示例站点稍作修改后的版本。如果对 1.2 版再做更改,就会得到 Customize Me Now 2.2,它综合了在整个系列中所做的全部更改。)
即使是喜欢在线购物的客户通常也会十分讨厌结帐的过程。所涉及的问题太多:
在准备使用 Ajax 改造现有的 Web 站点而开始投入时间和开发时,改进用户体验应该成为您的首要目标之一。Ajax 虽然不能解决用户之所以憎恨结帐过程的全部症结,但它至少能在如下三个方面提供一些帮助:
如果在 Web 浏览器中观察 Customize Me Now 1.2,就会发现该站点的结帐过程与其他很多站点一样让人迷惑。所提供的面包屑式导航(breadcrumb navigation)显示结帐需要 5 步:
但是,实际的过程更为复杂。作为一个用户,在开始步骤 1 之前,必须要选择是作为客人结帐还是登录。如果选择前者,就会直接进入步骤 1,可以在此输入名字和联系信息。如果选择后者,就必须进入登录屏幕,输入用户名和密码并返回到步骤 1 来查看先前输入的联系信息。
完成步骤 1 后,剩余的过程是一个线性过程。但在到达步骤 4 Order Review 时,又会遇到另一个潜在的弯路,这次是到达 Apply Discount 页。
在 整个过程中(包括所走的弯路),有一个工具能帮助您了解自己处在该过程中的何处:即前面提到的 breadcrumb navigation。在任何时候,您只能单击在前面已经完成的 breadcrumb 项。您不可能跳转到过程的前面,而是只能向后跳转。这个 breadcrumb 路径如图 1 所示:
图 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 了。通过将结帐过程转变成一个单一屏幕的界面,可以减少服务器往返带来的延迟,还可以让步骤间的转变不再如此让人迷惑。完成之后,转到登录和折扣屏幕的过程将很直观而不会让人迷路。
这次改造所需工具包括在之前的文章中用到过的流行 Ajax 库 jQuery 和其他两个您很熟悉的插件:
这次改造的重头戏都发生在一个页面: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"> |
和本系列 第 3 部分 一样,还需要创建现有的 HTML 文件的一些 HTML 片段版本。此步骤十分必要,因为 Ajax 响应通常需要忽略原本充当页面主要内容的页头、页脚和其他外部元素。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 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"> <h2>Step 1: Personal Info</h2> <div> <a href="checkout1.html" id="checkoutAsGuest"> check out as guest </a> <a href="login.html" id="login">log in</a> </div> <div> <p>[long, boring playback of order details]</p> </div> </div> <div id="shippingDetails"></div> <div id="billingDetails"></div> <div id="orderReview"></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> <ul> <li>1. Personal Info</li> <li>2. Shipping Details</li> <li>3. Billing Details</li> <li>4. Order Review</li> <li>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> <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><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 还提供了选项卡式界面的生命周期内的一些定制事件挂钩。与诸如 onclick 和 onmouseover 这样的内置 DOM 事件类似,定制事件也能够让回调处理程序应用于它们。惟一的不同点是事件本身由 jQuery 及其插件而不是浏览器定义。可以使用这些定制事件处理程序中的一个来进一步控制在给定的时间哪个选项卡被启用或禁用。
每当一个新的选项卡被激活,一个名为 select.ui-tabs 事件就会抛出(这个事件的名称在 jQuery UI Tabs 的后续版本中有所更改,所以,如果遇到问题,可以去查看相关文档)。通过在新创建的选项卡组上调用 jQuery 的 bind 方法,可以给这个定制事件附加一个处理程序。这个处理程序遍历整个选项卡组并会禁用当前选项卡后面的所有选项卡。然而,这个 select.ui-tabs 事件处理程序并不能访问整个选项卡组,而只能访问所选中的选项卡。因此,您不得不硬编码对选项卡集的引用以及其中的选项卡数。
连接好其他选项卡的行为后,需要告诉第一个选项卡中的内容要做些什么。这包括更改标签为 log in 和 check out as guest 的两个链接的行为,以便它们通过 Ajax 在当前选项卡中加载它们的目标页面。原始的链接指向整个 HTML 页面,而我们只需要前面创建的 HTML 片段。因此要使用 jQuery 来获取两个链接的对象引用并用 click 事件处理程序覆盖它们的默认行为。还需要获取每个链接的 href 属性,使其转而指向一个片段文件并通过 Ajax 获取结果 URL。最后,在 Personal Info 选项卡内呈现响应。
好了!我们现在构建了五步结帐过程中的步骤 1。在浏览器中访问结帐页面,将看到一个类似图 2 所示的页面:
图 2. Customize Me Now 2.2 结帐步骤 1:登录或作为访客结帐

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

要想在此时通过登录或是个人信息表单,选项卡界面很快就会中断。选项卡内并没有继续载入内容,而是对页面的原始的、未分段的版本进行全页面刷新。这 是因为您还没有告诉您的单个 HTML 片段文件如何在选项卡基础结构内运行。要完成最后的这个任务,需要另一个插件:jQuery Form。
将 log in 和 check 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 内的表单具备 lform 的 id,checkout1-fragment.html 内的表单则有 pform 的 id,以此类推。
(为了避免冲突,需要为每个表单字段提供一个惟一的 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,它会得到如下信息:
div 元素,它包含 personalInfo 的 id 属性)最后,通过返回 false,取消表单的正常的、非 Ajax 提交。
要查看这个过程,可以在浏览器内重新开始这个结帐过程。单击 log in 链接,提交登录表单,会看到个人信息表单载入第一个选项卡。现在能够通过两个方式得到这个表单:直接从结帐过程的起点,或者通过其他路径到达登录页面(当然,在实际情况中,登录后,个人信息表单可能已经为您预先填写好了)。
不过,现在必须找到一种方法来离开第一个选项卡并前进到第二个及后续的选项卡。这次,需要向 checkout1-fragment.html 添加一个表单提交处理程序,该文件含有个人信息表单。此处理程序类似于登录的那个处理程序。与在第一个选项卡内载入结果、启用它并选择它有所不同,我们要 在第二个选项卡上执行所有这些动作。为此,选项包将 shippingDetails 的 id 指向 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 基础,您可借此继续改造结帐过程。
jQuery 逐渐从其他 JavaScript 库选择中脱颖而出,并且成为 Web 开发人员的最佳选择。它迅速成为希望简化客户端开发和快速高效地创建富 Internet 应用程 序(RIA)的程序员的首选。随着 RIA 的广泛使用,将越来越多地使用 JavaScript 库协助开发。RIA 被定义(松散地)为通过浏览器运行的应用程序,这种浏览器结合使用 CSS/JavaScript/Ajax 创建类似桌面应用程序的外观。Firefox、Internet Explorer 和 Safari 最新发行版中新增的特性,以及 Google 新 Chrome 浏览器最近的版本,都专注于加快每个浏览器的内部 JavaScript 引擎的速度,其惟一目的是更加适应浏览器制造商为未来设计的 RIA。这些公司认为未来的 Web 页面将包含大量 JavaScript 代码,因此首先开发一个成熟的、没有 bug 的库是非常重要的。
因 此,随着未来 Web 应用程序不断朝富沉浸式界面(rich and immersive interface)的方向发展,Web 开发人员不断地寻找可以简化这些工作的工具。现在已经有几个 JavaScript 库,每个库都有各自的优点和缺点,同时也有各自的支持者和反对者。在这里,我不讨论哪个库的特性更好,因为这对最终结果没有多大影响。最重要的是哪个库使 用得更多 — 数量才是最重要的。看看下面 4 个最流行的 JavaScript 库的 Google 趋势图。很明显,在过去 6 到 8 个月里,jQuery 成为主导的 JavaScript 库,并且这一趋势仍在上升。
图 1. 常见的 JavaScript 库的 Google 趋势图

就业市场方面也显示出 jQuery 渐渐上升为最多人选择的 JavaScript 库。Monster.com 的一个非科研性结果表明,与 jQuery 相关的职位有 113 个,而 YUI、ExtJS 和 mootools 分别是 67、19 和 13。
这个 jQuery 系列文章的第一篇以探索 jQuery 语法开始,同时了解它的函数是 如何调用的。本文后面的小节探索这个库的核心函数,以及这个库如何使用它的强大的选择器和过滤器使 DOM 遍历更加简单明了。接下来的文章将阐述 CSS 操作、表单控制、文本更改、Ajax 简单性 和动画(所有人的爱好)。jQuery 的最有趣的特性之一是它的插件架构,它允许很多开发人员添加新的 jQuery 功能。本文的最后一篇文章将介绍许多强大的插件,它们可用于完成 RIA 开发过程。
本系列文章针对具备 JavaScript 语法、CSS 语法和 DOM 语法知识的读者。如果您需要在阅读本系列文章之前复习一下这些语法,我强烈推荐本文参考资料小节中的 W3Schools 链接。
基础知识
在深入研究有趣的 jQuery 内容之前,我们先了解一些基础知识 — 如何安装 jQuery,以及如何使其正常运行等。首先从下载小节下载 jQuery 库,然后像链接其他外部 JavaScript 文件一样链接到该库:
清单 1. 如何在代码中安装 jQuery
<script type="text/javascript" src="jquery.js"></script> |
由于 jQuery 调用或操作 DOM 对象,因此如果在文档装载完所有页面元素之前,使用 JavaScript 代码直接操作这些对象将会遇到问题。相反,您也不希望等待页面上的所有元素都 装载完 — 所有图像、标题广告、解析代码和 YouTube 视频预览 — 才开始调用 jQuery 代码。您可以适当折中,在文档完全装载完页面上的所有元素,但所有的图像、链接和呈现尚未完成之前,以一种安全的、无错误的方式调用 jQuery 代码。再强调一遍,您的所有 jQuery 代码必须在页面上的这个函数中,或在它自己的函数中。如果 jQuery 代码不在一个函数中,则不可将其代码放置在 JavaScript 代码中。
清单 2. 如何正确调用 jQuery 函数
// Incorrect
<script language=JavaScript>
$("div").addClass("a");
</script>
// Correct
$(document).ready(function(){
$("div").addClass("a");
});
// - or -
$(document).ready(function(){
myAddClass();
});
function myAddClass()
{
$("div").addClass("a");
}
|
此外,还有一个值得注意的地方:一个页面上可以有任意个 document.ready() 函数,它们将被依次调用。如果您正在使用模块动态地构建页面,并且每个模块有它自己的 jQuery 支持代码(例如,一个由许多很小的 PHP 页面片段构成的 PHP 页面),这是一个不错的方法。
jQuery 最有趣的特性之一是 “链接性”,它能够将一系列函数集中起来,改善可读性和简化代码的编写。几乎每个 jQuery 函数都返回一个 jQuery 对象,这意味着您仅需在其上反复调用其他函数,就可以将一个完整的 jQuery 命令链接在一起。我将这比作 Java 的 String 类,在其中有几个函数返回一个 String 对象,使您可以将在同一行上的多个函数链接在一起:
清单 3. jQuery 的链接性
String man = new String("manipulated").toUpperCase().substring(0,5).toLowerCase();
$("div").addClass("a").show().text("manipulated");
|
最 后需要记住的是,当使用 jQuery 或任何 JavaScript 库时,它们之间有可能发生冲突。换句话说,在同时使用两个以上的库时,会有一个以上的库同时使用变量 “$”,这意味着在进行 “$” 调用时,引擎将不知道引用哪个库。这种情况的一个很好的例子就是 CakePHP 库,它包含内置的 prototype.js。在这些页面上使用 jQuery 将导致错误。为了解决这个问题,jQuery 提供了一种方法将 “$” 变量映射到另一个变量,例如:
清单 4. jQuery 解决冲突的办法
j$ = jQuery.noConflict();
j$("div").addClass("a");
选择
jQuery 的根本在于它在页面上选择和操作某些元素的能力。从某种意义上说,需要围绕这些对象才能构建出有效的 jQuery 库。因此,面对一个普通 HTML 页面上提供的大量选项,您需要一种方法来快速高效地选择您需要在页面上使用的元素,只选择需要的元素(不多也不少)。jQuery 如我们所愿地提供了一些强大的选择方法,帮助我们在页面上寻找和选择对象。jQuery 创建了它自己的选择语法,并且这种语法很容易掌握。
(以下大部分示例所使用的函数将留在下一篇中讨论,不过它们的功能应该是很直观明了的)。
根本上来讲,jQuery 中的选择过程就是一个巨大的过滤过程,页面上的每个元素都经过这个过滤器,它将返回一个匹配的对象,或一个可以遍历的匹配对象的数组。
排在前面的 3 个示例是最常用的。它们通过 HTML 标记、ID 或 CLASS 查找对象。
HTML
要获取一个页面中所有匹配的 HTML 元素的数组,您仅需将 HTML 标记(不带括号)传递到 jQuery 搜索字段。这是查找对象的 “快速但是粗糙” 的方法。如果要将属性附加到通用的 HTML 元素,这种方法是很有用的。
清单 5. HTML 选择
// This will show every <div> tag in the page. Note that it will show
// every <div>, not the first matching, or the last matching.
// Traversing Arrays is discussed later in the article.
$("div").show();
// This will give a red background to every <p> tag in the page.
$("p").css("background", "#ff0000");
|
正确的页面设置要求页面上的每个 ID 都是惟一的,虽然有时并不是这样(有意或无意)。使用 ID 选择时,jQuery 仅返回第一个匹配的元素,因为它要求您遵循正确的页面设计。如果您需要将一个标记附加到同一页面上的几个元素,应当选择使用 CLASS 标记。
清单 6. ID 选择
// This will set the innerHTML of a span element with the id of "sampleText" to "Hi".
// Note the initial "#" in the command. This is the syntax used by jQuery to search
// for IDs, and must be included. If it is excluded, jQuery will search for the HTML
// tag instead, and with no <sampleText> tags on a page, will ultimately do
// nothing, leading to frustrating and hard-to-find bugs (not that that has ever
// happened to me of course).
$("#sampleText").html("Hi");
|
CLASS 与 ID 非常相似,不同之处是它可以用于一个页面上的一个或多个元素。因此,尽管受到同一页面的每个元素只有一个 ID 的限制,同一页面上的多个元素仍然可以拥有相同的 CLASS。这使您可以在一个页面上跨多个元素执行函数,并且只需传入一个 CLASS 名称。
清单 7. CLASS 选择
// This will create a red background on every element on the page with a CLASS of
// "redBack". Notice that it doesn't matter which HTML element this "redBack"
// CLASS tag is attached to. Also notice the period in the front of the query
// term -- this is the jQuery syntax for finding the CLASS names.
$(".redBack").css("background", "#ff0000");
<p>This is a paragraph</p>
<div>This is a big div</div>
<table><tr><td>Sample table</td></tr></table>
|
可以在一个搜索中,将以上的 3 个搜索条件和下面的所有过滤器合并起来。通过使用 “,” 分隔每个搜索条件,搜索将返回与搜索词匹配的一组结果。
清单 8. 合并搜索
// This will hide every <p>, <span>, or <div>.
$("p, span, div").hide();
|
虽然在 jQuery 中,这 3 个搜索参数无疑是最常用的,但还有许多其他搜索参数,可以帮助您在一个页面上快速查找所需的元素。这些过滤器以 “:” 开头,表明它们是 jQuery 搜索词中的过滤器。尽管它们也可以作为独立的搜索条件,但是设计它们的目的是将它们和以上 3 个搜索条件一起使用,从而可以调整搜索条件以找到您需要的特定元素。
清单 9. 更多的过滤器
// This will hide every <p> tag on a page
$("p").hide();
// This will hide the first element on a page, no matter its HTML tag
$(":first").hide();
// Notice how these can be used in combination to provide more fine tuning of
// search criteria. This will hide only the first <p> tag on a given page.
$("p:first").hide();
|
可以将多个过滤器用作搜索元素。虽然在这里我没有列举所有的过滤器(这是 API 页面的任务),但其中一些过滤器在处理页面和搜索元素方面非常方便。
我将 主要关注 Selection 包中一些非常重要的过滤器,它们就是表单 元素过滤器。如今的富 Internet 应用程序比较关注表单及包含在其内的元素(文本字段、按钮、复选框、单选按钮等),它们从服务器收集和传输信息,或收集信息并传输到服务器。由于它们在 RIA 中的重要作用,在当今的 Web 应用程序中,这些过滤器在处理 jQuery 时非常重要。
这些过滤器和前面介绍的过滤器的工作原理是一样的,并且也是以 “:” 开头,表明它们是过滤器。同样,它们也可以和其他搜索过滤器一起使用,以细化搜索条件。因此,一个 “:text” 搜索过滤器将返回页面上的每个文本字段,而一个 “.largeFont:text” 搜索过滤器仅返回页面上作为 “largeFont” 类的一部分的文本字段。这允许进一步细化和操作表单元素。
表单过滤器也包括元素的每个属性,了解这方面的知识对开发人员有好处。因此像 “:checked”、“:disabled” 和 “:selected” 等搜索过滤器将为特定的搜索进一步细化搜索条件。
现在,您已经学会如何搜索和过滤页面上的所有元素,接下来需要一种高效的方法来遍历结果,进一步处理元素。自然,jQuery 提供了几种遍历搜索结果的方法。
第一个也是最常用的遍历方法是 each() 函数。这和 “for loop” 的功能是一样的,遍历每个元素并通过迭代递增元素。此外,循环中的每个元素的引用可以通过 “this”(用于一般的 JavaScript 语法)或 $(this)(用于 jQuery 命令)来实现。
让我们看看下面的示例。
清单 10. each 循环
// Will loop through each <p> tag on the page. Notice the use of the
// inline function here -- this is analogous with the anonymous classes in Java.
// You can either call a separate function, or write an inline function like this.
var increment = 1;
$("p").each(function(){
// now add a paragraph count in front of each of them. Notice how we use the
// $(this) variable to reference each of the paragraph elements individually.
$(this).text(increment + ". " + $(this).text());
increment++;
});
|
因为搜索结果存储在一个数组中,您肯定希望函数遍历该数组,就像处理其他编程语言的数据对象一样。因此,要查找一个给定搜索结果的长度,则可以在该数组上调用 $().length。清单 11 展示了更多的数组遍历函数,可适用于其他编程语言的数组遍历。
清单 11. 其他数组函数
// the eq() function lets you reference an element in the array directly.
// In this case, it will get the 3rd paragraph (0 referenced of course) and hide it
$("p").eq(2).hide();
// The slice() function lets you input a start and an end index in the array, to
// create a subset of the array. This will hide the 3rd through 5th paragraphs on the
// page
$("p").slice(2,5).hide();
|
除了这些数组遍历函数之外,jQuery 还提供了一些函数,使您可以查找嵌套在搜索词周围的元素。为什么这很有用呢?例如,我们常常需要在图片的旁边嵌入一个文本标签,或在表单元素旁边嵌入一个 错误消息。使用这些命令可以查找特定的表单元素,然后通过将表单元素放置在下一个元素(span 标记)中,把该错误消息直接放置在表单元素旁边。清单 12 显示了这种设计的一个示例:
清单 12. 示例 next() 函数
<input type=text><span></span>
function validateForm()
{
$(".validate:text").each(function(){
if ($(this).val()=="")
// We'll loop through each textfield on the page with a class of "validate"
// and if they are blank, we will put text in the <span> immediately afterwards
// with the error message.
$(this).next().html("This field cannot be blank");
});
}
|
要了解如何结合使用以上知识,可以查看本文包含的示例应用程序(参见 参考资料 小节)。
现在简单介绍一下示例应用程序。我将在本系列所有文章中使用这个示例应用程序,因为它使用了大量不同的 jQuery 示例,并且几乎所有人都熟悉这个应用程序 — 一个处理 Web 邮件的富 Internet 应用程序。这个示例应用程序是一个简单的邮件客户机,它利用 jQuery 给用户这样的感觉:该电子邮件客户机非常类似于桌面应用程序。在最后一篇文章结束时,您将明白这个简单的应用程序是如何为用户制造这种感觉的,并且明白使 用 jQuery 实现这个功能是多么简单。
本文的重点是 “Select All”/“Deselect All” 复选框,它们出现在 Web 邮件表(下面突出显示)的左侧列的顶部。当选中该复选框时,它将选择该列的每个复选框;取消选择该复选框时,它将取消选择该列的所有复选框。
图 2. “Select All” 复选框

<!-- The first step is creating the Select All checkbox itself.
we give it a unique ID on the page -->
<input type=checkbox id=selectall>
<!-- The next step is giving each of the rows their own checkbox.
we put each row's checkbox into the 'selectable' class, since there can be many rows,
and we want each of the rows' checkboxes to have the same behavior. -->
<input type=checkbox>
<!-- The final step is bringing it all together with some jQuery code. -->
// remember that all jQuery setup code must be in this document.ready() function,
// or contained within its own function in order to function correctly.
$(document).ready(function(){
// We use the jQuery selection syntax to find the selectall checkbox on the page
// (note the '#' which signifies ID), and we tell jQuery to call the selectAll()
// function every time someone clicks on the checkbox (we'll get to Events in a
// future article).
$("#selectall").click(selectAll);
});
// This function will get called every time someone clicks on the selectall checkbox
function selectAll()
{
// this line determines if the selectall checkbox is checked or not. The attr()
// function, discussed in a future article, simply returns an attribute on the
// given object. In this case, it returns a boolean if true, or an undefined if
// it's not checked.
var checked = $("#selectall").attr("checked");
// Now we use the jQuery selection syntax to find all the checkboxes on the page
// with the selectable class added to them (each row's checkbox). We get an array
// of results back from this selection, and we can iterate through them using the
// each() function, letting us work with each result one at a time. Inside the
// each() function, we can use the $(this) variable to reference each individual
// result. Thus, inside each loop, it finds the value of each checkbox and matches
// it to the selectall checkbox.
$(".selectable").each(function(){
var subChecked = $(this).attr("checked");
if (subChecked != checked)
$(this).click();
});
}
|
jQuery 是 Web 应用程序开发社区中非常受欢迎的 JavaScript 库,并且随着富 Internet 应用程序越来越普及,它将变得更加重要。由于许多公司都在线迁移内部应用程序,并且在线移动日常的桌面应用程序(包括文字处理器和电子表格),能够简化开 发并实现跨平台支持的 JavaScript 库将成为设计应用程序架构的必选技术。
这份关于 jQuery 的系列文章的第一篇介绍了 jQuery 语法,如何在您自己的 JavaScript 代码中正确使用 jQuery,以及如何在结合使用其他库时避免冲突。此外,本文还介绍了 jQuery 搜索和选择语法,它们是其他 jQuery 功能的基础。它使您可以简单快捷地找到所需的页面元素。文章也谈到了如何遍历搜索结果,使您可以逐个地处理元素。jQuery 的这两个方面是本系列下一篇文章的基础,同时也是所有 jQuery 代码的基础。
最后介绍了一个演示应用程序,它是一个富客户端 Web 邮件应用程序。在本文,您通过学到的 jQuery 知识创建了 Select All/Deselect All 复选框,并且仅需几行代码就可以创建一个在许多 Web 站点上都非常常见的小部件。
下一篇文章将把一些交互添加到这个示例 Web 应用程序。您将学习如何处理页面事件(单击元素、按钮点击、组合框选择等),如何从页面上的元素获取值,以及如何修改页面上的标准 CSS 来更改颜色,布局等,而不需重新加载页面。
最近这几个月以来,JQuery 受欢迎的指数迅速攀升,现已成为 Web 开发人员首选的 JavaScript 库。与此同时,人们对富 Internet 应用程序(Rich Internet Application,RIA)的应用和需求也在迅速增长,并期待用基于浏览器的应用程序代替桌面应用程序。无论是电子表格,还是薪水册和电子邮件应用程序,现在都在浏览器中再现了类似于桌面的体验。随着这些应用程序数量的增多和功能的日益复杂,JavaScript 库将会变得越来越重要,因为它是构建这些应用程序的坚实基础。JQuery 无疑成为了开发人员最佳选择。本系列文章深入探索了 jQuery,并提供了坚实的基础。开发人员借助这个基础就可以快速轻松地构建自己的 RIA。
在本系列中,您了解了用来构建 RIA 以及为页面添加交互性的三个基本组件。第一个模块是 Event 模块,借助此模块,能捕获用户对页面的任意交互并以编程的方式进行响应。比如,您可以向按钮点击、鼠标移动等事件附加代码。下一个模块是 Attributes 模块,它解释了如何在页面元素上获得/设置值,以及如何将其作为带有变量的数据对象。 这些值包含了决定为用户提供何种响应的大部分信息。最后,您还看到了 如何进行 CSS 处理,以及如何在不重新加载页面的情况下更改页面上任意元素的布局、颜色、字体等。了解这三个模块之后,就等于掌握了交互 Web 页面的三个基本元素 — 获取用户交互(Event)、收集信息(Attribute)以及在事件和信息基础上提供反馈(CSS)。
在本文中,将对交互 Web 页面的这三个基本元素进行更进一步的探究,提供当今高级 Web 应用程序必备的 “酷” 效果和特征。这些附加的模块对提供 RIA 而言并不是至关重要的,但这些效果和特征会给用户留下深刻印象,并且还会极大地扩展 RIA 的可用范围和特性。您将看到的第一个模块是 Effects 模块,它包含很多特性,比如隐藏元素、随处移动元素、淡入淡出元素等。换言之,这些都是让 Web 页酷起来的 “亮点”。最后一个要讨论的模块是 Asynchronous JavaScript + XML (Ajax) 模块。大多数人都将该模块等同为 RIA。Ajax 让 Web 应用程序无需重载页面就能与服务器进行交互、向服务器传递信息并从中获取信息(与 Web 上的一些意见相反,Ajax不单纯是一个很酷的 JavaScript 动画工具)。您将发现 jQuery 提供了极其简单易用的 Ajax 工具。实际上,jQuery 使 Ajax 的使用与调用其他 JavaScript 方法一样简单。
本文中的示例应用程序是个总结,展示了 Effects 和 Ajax 模块如何融入到这个示例 Web 邮件应用程序中。我将向这个示例程序添加一些效果使其更加漂亮,而且更重要的一点是,我将添加一些 Ajax 代码,以便无需重载页面邮件应用程序就能显示信息。
从 其名字往往容易得出这样的结论,Effects 模块只包含一些动画和效果,而这些动画和效果往往是一些 “正规” 的 Web 页面所竭力避免的。但实际情况并非如此。几乎所有的应用程序都会遇到这样的情况,即某个页面元素需要隐藏或其视图应该根据另一个页面元素的状态进行切换。 这类更改对于一个 RIA 而言非常重要,因为它们让您能够加载某个页面的所有页面元素,然后通过隐藏/显示特定的元素只显示所需的信息。重载页面的方式并不可取。比如一个具有两个 选项的组合框,一个选项是隐藏 div,一个选项是显示此 div。很显然,与更改组合框并重载页面隐藏/显示 div 相比,用客户端代码隐藏/显示此 div 更简单和高效。而仅隐藏/显示还是要让它淡入/淡出,则完全取决于您。
如上所述,最基本的效果函数是show()和hide()函数。这非常直观;它们可分别用来显示和隐藏页面上的某个元素。
清单 1. 隐藏和显示函数
// shows every <p> on the page
$("p").show();
// hides every <p> on the page
$("p").hide();
// hides every other <p> on the page
$("p:odd").hide();
|
除了这些基本操作,利用show()和hide()这两个函数,还能更多地控制页面元素如何显示和隐藏。相关文档将hide()描述为 “优美” 的显示/隐藏,对于show(),就是综合淡入和滑出的效果。
在开始深入探讨一些例子之前,不妨回过头来看看传递给这些效果函数的参数。每个函数(除了通用的show()和hide()函数之外)都允许在效果完成时传入要调用的速度和函数。速度用来控制效果出现的快慢。这个参数可以是一个"slow"、"fast"或"normal"字 符串。此外,如果需要精确控制动画时间,那就需要用参数指定毫秒数。Effects 函数的第二个参数本身就是一个函数,此函数在效果完成后调用。如果想要将几个效果组合成一个较大规模的效果,这一点将非常重要,因为利用它,能够可靠地控 制一个效果何时完成,下一个效果何时开始。
清单 2. 复合效果
// the img with an ID of "picture" should start hidden
// when the "showPicture" button is pressed, show the img with an ID of "picture"
// and animate it, so it appears quickly in a fade In and slide out, and when
// it's done being shown, show the caption of the picture, which is
// always in the span right after the <img> tag
<input type="button" id="showPicture">
<img src="/pic.jpg" id="picture"><span>This is the picture's caption</span>
// jQuery code inside the document.ready() function
$("#picture").hide().next().hide();
$("#showPicture").click(function(){
$("#picture").show("fast", function(){
$("#picture").next().show();
});
});
// notice how it took more text to describe what you want to happen than it took
// to actually code it!
|
Effects 模块还有其他一些函数,它们与show()和hide()非常类似,并且最终所实现的功能也基本相同;只不过实现的方式不同。slideDown()和slideUp()函数分别用来显示和隐藏一个页面元素。不过,这是通过将该元素滑下或滑上的动画效果实现的(从其名称中不难看出这一点)。与我刚刚提到的增强了的hide()和show()函数类似,您也可以控制滑动的速度以及在效果完成时要调用的函数。此外,要显示/隐藏页面元素还有另一种选择,即fadeIn()和fadeOut()函数,正如其名字所示,这两个函数用来淡入页面元素直至该元素透明,然后使该元素消失。它们允许在效果完成时定制速度和要调用的函数。
有一个很有趣的函数,它可部分隐藏和显示页面元素,此函数是fadeTo(), 它能让页面元素部分透明。我认为此函数对 RIA 很重要,因为透明性是突出页面上某个元素的一种很好的方式,并可用来显示页面的禁用区域。例如,页面上有几个选项卡,可以让所有未被选中的选项卡呈现一些 透明性来突出其未被选中。或者,在一个页面表单上,可以让所有没有焦点的元素呈现一些透明性以向用户突出哪个Form元素当前具有焦点。此外,透明性本身就很酷。在进行设计时,不妨遵循这样一条通用的箴言 “如果苹果公司这么设计,效果一定很酷”。
// make all the Form elements on the page show transparency at 60%, except
// the one that currently has focus, which will not have any transparancy.
// This is Apple cool!
$(":input").fadeTo("fast", .60);
$(":input").focus(function(){
$(this).fadeTo("fast", 1);
});
$(":input").blur(function(){
$(this).fadeTo("fast, .60);
});
|
我想要强调的 Effects 模块内的最后一个函数是最酷也是最容易出错的。它是一种定制动画的方法,允许您定义动画的所有参数,而剩下的工作则由 jQuery 负责处理。您负责提供一个参数数组以及这些参数的最终值,jQuery 负责决定这些参数的当前值。而且,动画的速度也可指定,这能使页面元素动画的运行比较平稳,直至达到您所提供的最终值。清单 4 给出了此定制动画方法的一个例子。显然,该函数提供很多可能性,如果您想要自己定制动画,不妨多进行练习。
清单 4. 定制动画的方法
// when the button is clicked, make the div with an ID of "movingDiv"
// have the custom animation provided.
$("#myButton").click(function(){
$("#movingDiv").animate({
// will increase the width, opacity, and fontSize of "movingDiv"
// and do it in 5 seconds
width: 700;
opacity: 0.5;
fontSize: "18px";
}, 5000);
});
|
当前,与 Web 页面有关的最流行的一句话就是 “它用到 Ajax 了吗”,但是,是不是每个人都能真正理解 Ajax 的含义呢?在 Google 上搜索 “Ajax” 将会出现数百万个搜索结果(与大多数搜索一样),但是,这么多结果似乎表明对术语 Ajax 真正含义的理解还存在一些含糊不清之处。Ajax 并不是 某些页面上出现的一些很酷的动画,也不是弹出窗口下一个很酷的阴影效果。Ajax 很酷,但并不意味着 Web 页上的任何一个令人耳目一新的东西都是 Ajax 的成果。Ajax 的核心是在不重载页面的情况下客户端 Web 页面能与服务器来回传递信息。所以,虽然 Ajax 并不能提供页面上的绚丽效果,但利用 Ajax 却能使 Web 应用程序真实模仿桌面应用程序。所以,围绕 Ajax 沸沸扬扬的现象是有其根源的 —— Ajax 的广泛应用直接导致了目前 RIA 的迅速发展。
jQuery 则让使用 Ajax 变得异常简单!我并不是夸大其词。在没有 JavaScript 库的情况下使用过 Ajax 的人都知道,他们必须处理 XMLHttpRequests、处理 XMLHttpRequest 的 Microsoft® 和 Firefox 版本间的差异、必须解析全部返回代码等,而 jQuery 则让使用 Ajax 简单到进行一次函数调用即可。这是真的!原先需要 100 行代码才能完成的功能现在只需 3 或 4 行代码就可以了。这节省了多少时间啊!就我个人而言,获悉 jQuery 之前,若想添加 Ajax 函数往往需要大量工作。现在,有了 jQuery,这变得极其简单,并能让我的应用程序充分利用 Ajax 提供的全部益处。如果 Ajax 的使用能简单到一个常规的函数调用,那为什么不使用它呢?
现在来看看您最有可能在自己的 Ajax 需求中用到的两个函数:post() 和 get() 方法。这些函数的运作与其他的 jQuery 函数没有太大差别,它们均允许指定要调用的 URL 以及要传递的参数,并指定 Ajax 方法返回时的函数。在这种意义上,这两个方法设置的方式使得在 jQuery 调用 Ajax 方法与在 jQuery 调用其他方法基本相同。参见清单 5。
清单 5. Post 和 get Ajax 方法
// this code would be in a php file named myTest.php
// why did I switch to PHP for the Ajax examples? Java/JSP gets tough because
// you need to show the code in the Servlet, which isn't necessary with PHP. The
// functions work equally well in both PHP and Java/JSP though.
<?php
echo "This is my return String";
?>
// here's how simple the Ajax calls are in jQuery
// This is a post function
$.post("myTest.php", {}, function(data){
$("p").text(data);
});
$.get("myTest.php", {}, function(data){
$("p").text(data);
});
正如这两个示例所示,这些函数与其他 jQuery 函数相同,因此比不带 JavaScript 库的情况更容易使用。有几个参数可用来扩展 Ajax 调用的函数。第一个参数很显然是要调用的 URL。这可以是 PHP 文件、一个 JSP 文件或 Servlet — 通常可以是处理此请求的任何东西。它甚至可以不响应此请求(正如您在以后的示例应用程序中看到的一样)。第二个参数是可选的,用来传递 post/get 的数据。一般采用数组格式。通常,需要传递在 Form 元素中包含的信息以及来自页面的 userID 信息等。基于服务器文件的所有东西都要处理此请求。第三个参数也是可选的,是 Ajax 函数成功返回时所要执行的函数。该函数一般包含用来处理由服务器传递回的信息结果的代码。清单 6 给出了这三个参数的一些例子,我随后还会介绍第四个参数。
清单 6. 用可选参数进行 post
// place a username and password input field on the page
<input type=text id="username">
<input type=password id="pass">
// call a server-based PHP file that will process the information passed to it
$.post("myFormProcessor.php", {username: $("#username").val(),
password: $("#pass").val()});
// conversely, this PHP file could also return information to the function, from which
// you could process the results
$.post("myFormProcessor.php", {username: $("#username").val(),
password: $("#pass").val()},
function(data){
// the data variable contains the text returned from the server, you don't
// have to set anything up ahead of time, jQuery will do it all for you
if (data != "ERROR")
$("#responseDiv").text(data);
}
);
|
不难看出,在 jQuery 内处理 Ajax 非常直观和容易。不过,如果处理来自服务器的比文本字符串复杂的信息时,这些函数可以变得很复杂。在较多涉及 Ajax 调用的一些更为复杂的 Web 页面内,返回的数据结果通常都是 XML 格式的。返回的数据也可以是 JSON 对象格式的(JSON 是用来定义 JavaScript 对象的一种协议)。jQuery 还允许 get/post 方法有可选的第四个参数,以便提前就能指定由服务器返回值的类型。可以针对 XML 字符串传递 "xml" 字符串、针对 HTML 字符串(或纯文本)传递 "html"、针对 JavaScript 代码传递 "script"、针对 JSON 传递 "json"。所以,例如通过将返回对象指定为 "json" 类型,jQuery 将能自动将来自服务器的响应字符串转换成 JSON 对象,使您能够立即引用它。
清单 7. 指定 Ajax 内的返回类型
// specify the return object to be a type JSON object, and process the
// return object as a JSON object, referencing fields in it without
// casting it to any object or evaluating it
$.post("myFormProcessor.php", {username: $("#username").val(),
password: $("#pass").val()},
function(data){
// jQuery has already converted the data response into a JSON object, so
// you can immediately reference its fields, providing cleaner-looking code
// and allowing future changes, and in my opinion, making it easier to work
// with than XML responses
$("#username").text(data.username);
$("#address").text(data.address);
$("#phone").text(data.phone);
},
"json" // here you specify that the return type is JSON
);
|
我想花些时间讲述的另一个 Ajax 函数是 load(), 它允许用户加载一个特定的页面并获取 HTML 作为结果。从这样的描述看起来,它并不那么吸引人。事实上,与在启动时解析 Web 页一样,您还需要解析使用 jQuery 代码返回的信息。这是什么意思呢?简单来说,如果能加载任何 Web 页面,继而又能用 jQuery 对之进行解析,也就实现了一个非常高效且易于编程的页面 scraper,借助这个 scraper,可以从所有页面搜集各类信息。让我们详细看看。
清单 8. load() 函数的示例应用程序
// create a very primitive stock price quote by calling Yahoo's stock quote, and then
// scraping the information from their pages.
// in this case, look up IBM's stock price, and place it in the text field with an ID of
// "stockPrice"
// the span with the ID of "yfs_l90_ibm" contains the stock price
$("#stockPrice").load("http://finance.yahoo.com/q?s=ibm #yfs_l90_ibm").text();
|
接下来我要介绍的 Ajax 模块的最后两个函数是两个真正的实用函数,它们非常有助于 Ajax 的使用。正如我已经多次指出的,客户机和服务器间的很多交互都会涉及到表单以及其中所包含的元素。由于这类通信很常见,所以在 jQuery 内已经有两个实用函数可用来协助构造传递给服务器的参数,参数可以是 HTTP 查询字符串的格式也可以是 JSON 字符串格式。可以使用这两个实用函数来为您的 Ajax 需求提供帮助。这两个函数的使用很方便,因为这二者均能封装整个表单,而不管在开发期间有多少元素被添加/删除/更改。清单 9 给出了这样一个例子。
清单 9. serialize() 和 serializeArray() 函数
// the serialize function will look at every Form element inside the specified element
// and automatically construct an HTTP String that contains all the information
// of the elements, in the form of <element name>=<element value>&
// for example, "firstname=Michael&lastname=Abernethy"
// this can then be attached to the URL to pass the information via an Ajax call
$.post("myFormProcessor.php?" + $("#myForm").serialize());
// further, a similar thing could be done with the serializeArray function
// which will convert a Form into its JSON equivalent
$.post("myFormProcessor.php", {json: $("#myForm").serializeArray()});
|
要将本系列的所有课程综合起来,需要用到在前两个课程中都谈及的这个示例 Web 邮件应用程序。我将添加从客户端到服务器端的多个 Ajax 调用以收集信息。我还将使用 Ajax 来获得您读取消息时的消息信息,并利用 Ajax 方法来删除消息。之后,我会将一些效果融入其中以便当用户删除消息时,这些消息会立即从屏幕中删除,即便用户并没有重新加载此页面,而且实际的删除也是通 过 Ajax 调用异步发生的。在本文结束之际,您将能再次感受到在 Web 应用程序中使用 Ajax 调用是多么容易,并了解到如何利用这些调用来实真实模仿桌面应用程序,以及如何利用 Effects 来增强应用程序的可用性。
清单 10. 示例 Web 应用程序 - 删除消息
// First, let's look at how you handle deleting a message.
// The first step is to create a button that will actually delete messages
<input type=button id="delete" value="Delete">
// next, you'll add a checkbox in each row of the table, so that users can select
// which messages they want to delete. You'll use these checkboxes later, and
// the information contained in them is equally important (that's called
// foreshadowing!)
// Notice how the value of each checkbox is the message.id!
<tr id="<%=message.id %>">
<input type=checkbox name="delId" value="<%=message.id%>">
// Now that the HTML is complete, look at the jQuery code to execute these deletes
// First, attach an event to the delete button, so when it's pressed, it will
// start deleting the checked messages
$("#delete").click(function() {
deleteMessages();
});
// Finally, let's define the deleteMessage() function, because that contains the meat of
// today's lessons.
// Because this is the culmination of every lesson, let's look at everything I did to
// get this working!
// Note 1 - I loop through each of the checkboxes that are checked by passing in a very
// specific search parameter, to find only the members of the "selectable" class that
// are checked.
// Note 2 - because the value of the checkbox is the same as the ID of the table row in
// which it is contained, you can use the Effects module to hide the entire table row,
// by passing in the value of the checkbox, and getting the table row back, and then
// hiding it.
// Note 3 - I make an Ajax call to actually delete the message from the DB. I have
// to pass the messageID to the server so that it knows which one to delete. That
// information is contained in the checkboxes value, which I pass with the Ajax call.
// Because I don't really care if it's successful or not, I ignore any reply from
// the server.
function deleteMessages()
{
$(".selectable:checked").each(function() {
$("#"+$(this).val()).remove();
$.post("<%=HtmlServlet.DELETE_MESSAGES%>.do", {delId: $(this).val()});
});
}
|
在第 2 个示例中,看一看如何读消息,这展示了如何在 jQuery 中使用 Ajax:
// You've seen most of this code previously in the example from last article, so let's
// focus on the Ajax portion of the code.
// Note 1 - I make an Ajax call with all 4 arguments defined. I have to pass two
// variables to the server in order to read the message. The first is the message
// ID number, because I need to know which message I want to read. The second is
// the current view...for reasons I can't recall (not important really).
// Note 2 - The fourth argument into the Ajax function is "json", indicating that
// I expect a JSON object back from the Ajax call. jQuery will automatically
// convert the response String into a JSON object.
// Note 3 - Notice that I handle the JSON object directly, without using an
// eval() function, because jQuery has already created the object. I can
// reference its fields directly.
$(".messageRow").dblclick(function() {
if ($(this).hasClass("mail_unread"))
{
$(this).removeClass("mail_unread");
}
$.post("<%=HtmlServlet.READ_MESSAGE%>.do", {messageId: $(this).attr("id"),
view: "<%=view %>"},
function(data){
if (data != "ERROR")
{
// using JSON objects
$("#subject").val(data.subject);
$("#message").val(data.message);
$("#from").val(data.from);
}
}, "json");
$.blockUI(readMess, {width:'540px', height:'300px'});
});
|
随着应用程序不断从桌面向浏览器转移,像 jQuery 这样的 JavaScript 库的将越来越重要。应用程序越来越复杂,这就使跨浏览器的 jQuery 成为所有 Web 应用项目的必要工具。由于易于使用和功能完备,jQuery 逐渐从其他 JavaScript 库中脱颖而出,成为很多开发人员的最佳选择。
通过这个 jQuery 系列的第三篇文章,您接触到了另外两个模块,利用它们能为应用程序真正添加丰富性,并进一步模糊桌面应用程序和 Web 应用程序间界限。本课程中功能最强大的一个模块是 Ajax 模块,该模块能极大地简化 Ajax 的使用,使用它与进行其他 jQuery 方法调用一样简单直观。您还通过几个例子进一步领略了 Ajax 的强大功能并知道了 Ajax 是个很酷的工具,可在不重载页面和延迟的情况下加快应用程序的响应速度。此外,您了解了 Effects 包。并且知道,如果正确使用动画和隐藏/显示页面元素,将能大大增强 UI 设计。不仅如此,若能有效地综合使用,Ajax 和 Effects 还能极大地增加 Web 站点的动态性。
最后,您再次重温了这个示例 Web 应用程序,并体会了增加了 Ajax 模块后,您将能在无需重载页面的情况下读取和删除消息。然后您还看到了如何在 Web 应用程序中删除消息,并且通过综合 Ajax 和 Effects,还能从用户页面和 DB 删除消息,而这一切均无需重载此页面。对于用户而言,消息的删除是个透明的过程,而且您能通过编程实现此目的。
至此,与 JQuery 发布版一并提供的核心库相关的系列 JQuery 文章就结束了。这三篇文章为您一一展示了 jQuery 所包含的每个模块,让您看到了使用 jQuery 是多么容易和直观。此外,您还应该知道不管您所面对的是哪种 Web 应用程序,jQuery 应该都可以用来将其转变成任何类型的 RIA,因为 jQuery 是所有 JavaScript 需求的坚实基础。通过本系列文章对 jQuery 的介绍,您应该有信心在自己的代码中使用 jQuery。
简介
自我发表了有关 jQuery JavaScript 库的第一个系列文章(参见参考资料) 的这六个月来,在 jQuery 领域发生了很多事情。对我们这些 jQuery 的信徒而言,最令人激动的莫过于 Microsoft® 已经选择在其 Visual Studio 套件中使用 jQuery,并已经决定将 jQuery 作为目前该套件所包含的惟一的 JavaScript 库。这显示了对 jQuery 的极大支持,帮助巩固了 jQuery 作为适用于 Web 应用程序的领先 JavaScript 库的地位。jQuery 受欢迎程度不断攀升的另一个有力说明是更新后的 Google Trends 图。我在之前的一篇文章中介绍过该图表,它展示了 jQuery 已经开始渐渐地从备用的这些 JavaScript 库中脱离出来。而六个月后,这种脱离更为明显,这一点从图 1 和图 2(忽略了 12 份类似股票市场的下跌)所示的这两个更新后的 Google 趋势图中可以看出。
图 1. 2008 年 6 月常用 JavaScript 库的 Google 趋势图

图 2. 2009 年 1 月常用 JavaScript 库的 Google 趋势图

在这个由五篇文章组成的系列文章中,我将更进一步,讨论在使用 jQuery 中涉及到的一些属于中等级别的主题。该系列涵盖的主题包括插件及插件的开发、jQuery UI 程序包、创建 jQuery 小部件的一些较高深的主题、更先进的 Asynchronous JavaScript + XML (Ajax) 技术,最后,还会对比 JavaScript 及其他库来介绍 jQuery 的性能。
本系列中的第一篇文章将讨论 jQuery 中所使用的插件结构。插件是 jQuery 比其他 JavaScript 库发展速度更快的最主要原因。由于插件广受第三方开发人员忠爱,到目前为止,已经有上百个插件被开发出来,极大地丰富了 jQuery 功能。插件,从名字的字面意义不难看出,就是为了扩展 jQuery 的核心下载所固有的功能而 “插入” 到 jQuery 的小部件或代码模块。插件社区已经开发出了上百个插件,这没有丝毫的夸大。无论您的 Web 站点遇到了什么问题,无论您(或您的客户)需要何种小部件,您都有可能在 jQuery 插件库中找到解决的办法。而且,插件库内所有的插件都是可免费下载并能在您自已的 Web 站点上使用的。
插件并不只限于用户界面小部件,此外还包括对 jQuery 语法的扩展、额外的 Ajax 函数以及人们改进其开发过程所需要的其他一些创新。更酷的是人们已经将几个内置的 JavaScript 特性(例如,线程特性setTimeout()及setInterval())转换成了 jQuery 语法。这就为开发人员提供了一个纯 jQuery 的开发环境,使处理与维护都更为轻松。
插件
jQuery 插件结构有很多优点。首先,它让您能够只使用 jQuery 核心以外的那些想要使用的小部件及函数。这在 Web 应用程序中非常重要,因为每附加一个插件都意味着下载和流量的增加。通过允许您只使用那些希望使用的插件,您就可以更好地管理 Web 流量。其次,它使那些热情高涨的第三方开发人员可以创建自已感兴趣的小部件并通过创建他们自已的插件来改进 jQuery 函数,而不必尝试通过 jQuery 核心代码来实现其想法。这使得 jQuery 的使用者能用集体的创造力和创造热情来扩展这个库,这样一来,新想法和新小部件的增长潜力几乎是无限的。这一点与封闭式的结构完全相反,后者需要 jQuery 团队审查和批准每个插件,这无疑会导致创建瓶颈。第三,这个由 jQuery 团队创建的插件架构, 无论是对创建插件还是对使用插件的开发人员而言,用起来都很方便,这也是插件能够飞速发展的重要动力。然而,除了这些优点之外,也有一个不好的方面:这些 插件没有正式的测试结构。因此,虽然可以放心 jQuery 核心已经被仔细测试过,但是如若选择使用了一个插件,也只能依靠第三方对其进行测试了。对于一个十分关键的 Web 应用程序而言,这个缺点应被予以重视。
要使用一个插件,需要将其放在您的页面上,与对任何 JavaScript 文件所做的(包括 jQuery 文件本身)无异。因此,如果需要在页面上使用一个插件,可以将其添加在 jQuery 之后,如清单 1 所示。
清单 1. 如何包含一个插件
<script src="jquery-1.2.6.min.js" type="text/javascript"></script> <script src="jquery.alphanumeric.plus.js" type="text/javascript"></script> <script src="jquery.blockUI.js" type="text/javascript"></script> |
本文不会对如何编写插件以及插件如何工作的细节做过多介绍。这些内容会在将来的一篇文章中介绍,在那篇文章中,您还会编写自已的新插件并将其放到实 际的 jQuery 插件存储库中。本文将着重探讨几个我比较喜欢的插件。这样做的目的不仅是为了向您展示我几乎每天都要使用的这些插件,还为了能够让您对 jQuery 插件存储库中可用的东西有一个大致的概念。我希望,借此能激发起您对插件的兴趣并自已去尝试使用它们。
好,下面就开始介绍我所偏爱的插件。
RightClick、ExtendedClick 与 Wheel
Web 应用程序的最主要目标之一就是让用户感觉他们像是在一个桌面应用程序上工作。换句话说就是让 Web 应用程序在视觉和感觉上都做到尽可能地与桌面应用程序一样。因此,如果一个用户想要一个工具栏显示在其桌面应用程序上的某个位置,那么 Web 应用程序也应尽量把这个工具栏放在同样的位置。这样就可以方便用户从桌面应用程序转移到 Web 应用程序,Web 应用程序的成功机率也会更大。
然而,Web 应用程序中有一个与桌面应用程序不一样的地方,那就是对鼠标事件的响应。也许您会对此心怀疑问,因为您的 Web 应用程序在您单击鼠标时工作得很好。当然,在大多数情况下,很多 Web 应用程序在处理鼠标左键单击事件时都会运行良好。实际上,多数人都习惯了只用鼠标的左键浏览 Web 站点,这时的 Web 应用程序更像是一个 Apple 应用程序,只用一个键。但所有 Windows® 应用程序都会使用两个鼠标键(有时甚至是 3 个键)。鼠标的左键用于激活命令,右键用于给出选项。我们已经习惯了这种用法,那么为什么还有这么多的 Web 应用程序忽略了鼠标右键呢。进一步说,Web 应用程序也忽略了 CTRL+单击、Shift+单击与鼠标滚轮的操作。那么在忽略了这么多鼠标操作的情况下,一个 Web 应用程序如何能真正地仿效桌面应用程序呢?一个真正的 Web 应用程序应该充分利用鼠标的功用。
要想弄清忽略鼠标右键单击的 Web 应用程序和没有忽略这些操作的 Web 应用程序之间的差别,以及额外的鼠标键如何能增强 Web 站点的功能,我们不妨比较一下 GMail 与 Yahoo Mail。这里先对 GMail 的粉丝们说声对不起,但在我看来,Yahoo Mail 在这方面的确比 GMail 做得好。在 GMail 中,对鼠标右键单击的处理与普通 Web 页面没什么区别。因此,当右键单击 GMail 中的一个消息时,它展示给您的只是诸如 “Back”、“Inspect Element” 或 “Select All” 之类的选项。这些动作与处理消息有什么关系呢?当然,这并不是说鼠标右键在 GMail 页面中没有任何作用。但我们与 Yahoo Mail 做一下比较,看看它是如何处理鼠标右键单击的。我们右键单击 Yahoo Mail 页面中的一个消息时,呈现给我的是包括 “打开”、“打印”、“回复” 和 “删除” 在内的选项。这些动作都是邮件应用程序中常用的。这些选项就让一个典型的 Web 应用程序非常类似于您在桌面上经常用到的邮件应用程序(试着右键单击 Outlook 中的一个消息,看看哪个 Web 应用程序所提供的选项更接近这些选项)。图 3 和图 4 显示了右键单击选项的区别。
图 3. Yahoo Mail 中的右键单击

第一个帮助解决 Web 应用程序中鼠标单击问题的 jQuery 插件是 rightClick 插件。它既能捕捉鼠标右键单击,同时还能捕捉鼠标右键按下与松开。它还有一个功用就是让您能够关闭特定于浏览器的右键单击上下文菜单。显示在 FireFox(图 4)中的右键单击菜单在您选择将其关闭后就不会再出现了,这就让您能创建符合自已习惯的右键单击菜单而同时还不会与浏览器的默认行为发生冲突。
清单 2. rightClick 插件
// set up the div that will capture our events
<div id=rightClickSample></div>
// when the right mouse is clicked on this div, increase the width
// by 10 pixels. Also, do not show the browser-specific pop-up
// menu
// This, of course, should be in the $(document).ready function
$("#rightClickSample").rightClick(function(e){
$(this).width($(this).width()+10);
});
$("#rightClickSample").noContext();
现在,让我们来看看能扩展 Web 应用程序中的鼠标功能的另外一个插件。这个插件具有捕捉 Ctrl、Alt 和 Shift 键的能力。一些应用程序(例如 Adobe Photoshop)经常会用到这些键,所以有些人更愿意在使用鼠标时也能使用这些键。然而,借助 jQuery 核心代码来使用这些键非常困难,并且会增加额外的编码负担。为什么不使用预先做好的并经过测试的插件呢?
extendedClick 插件提供了代表不同帮助键组合的函数。这些函数包括 ctrlclick()、 shiftclick()、altclick()、ctrlaltclick()、ctrlshiftclick()、altshiftclick() 及 ctrlaltshiftclick()。但美中不足的是,这些函数只能被关联到鼠标左键单击按钮,正如之前的右键单击示例中所展示的,忽略了鼠标的另一半不是明智之举。然而,到目前为止,还没有一个插件能将帮助键关联到右键单击,将两者相结合以及创建一个新插件的工作就留给有志之士去做了。
让我们将右键单击的示例改掉,让 div 在按下 Shift+左键单击时增加 10 个像素、在按下 Ctrl+左键单击时减少 10 个像素。
清单 3. extendedClick 插件
// set up the div that will capture our events
<div id=extendedClickSample></div>
// when the left mouse is clicked with the shift key held down,
// grow the div by 10 pixels
$("#extendedClickSample").shiftclick(function(e){
$(this).width($(this).width()+10);
});
// when the left mouse is clicked with the ctrl key held down,
// shrink the div by 10 pixels
$("#extendedClickSample").ctrlclick(function(e){
$(this).width($(this).width()-10);
});
|
能够消除桌面应用程序与 Web 应用程序差异的最后一个插件就是能够处理鼠标滚轮的这个插件。我敢打赌多数人都从来没遇到过一个允许使用鼠标滚轮的 Web 站点(除了 HTML 输入元素)。但这也不意味着要在一个应用程序中使用鼠标滚轮完全不可能。鼠标滚轮在应用程序中的应用之一是能让您上传图片的 Web 站点,可以通过鼠标滚轮来对图片进行放大或缩小。诚然,在桌面应用程序上也可以使用这个功能,但是在大多数 Web 应用程序中仍然可以吗?未必。
举个鼠标滚轮的例子,让我们用鼠标滚轮同样地控制 div 值的增加/减少。当向上滚动鼠标滚轮时,div 增加,当向下滚动时,div 的值减少。如果您的鼠标没有滚轮的话,那么就可以假设一下。
清单 4. 鼠标滚轮插件
// set up the div that will capture our events
<div id=wheelSample></div>
// attach an event handler for the wheel to this div
// notice that we use the e.delta to determine how many "notches" the wheel
// was moved. One notch is either 1 (if up), or -1 (if down). So, as
// a result, we can simply add it to the current width, letting the
// sign of the delta grow or shrink the div.
$("#wheelSample").wheel(function(e){
$(this).width($(this).width()+10*(e.delta));
});
|
这三个插件,我感觉,在使 Web 应用程序能在感观上更接近桌面应用程序方面是非常重要的。与 Web 应用程序进行交互时受到的鼠标左健单击的限制相反,这些插件允许 Web 应用程序捕捉所有形式的鼠标交互,正如一个典型的桌面应用程序所做的那样。此外,由于插件的应用将在今后的几年中越来越普遍,开发人员将会肩负起将现有的 桌面应用程序转换成 Web 应用程序的责任。通过在 Web 上提供用户在桌面上已有的所有的鼠标交互,这些插件会使这种转换变得非常容易。
blockUI
blockUI 是提供创建模式对话框功能的几个插件中的一个。也许您会说 JavaScript 已经提供了很多很酷的模式对话框,比如 alert() 和 confirm()。 我不否认,无论是开发人员还是用户都非常喜欢这些模式对话框。它们在一个设计良好的 Web 站点上看起来的确不赖。然而,开发人员还有一个更好的选择,可以让您从用户那里收集信息并使您的模式对话框在 Web 应用程序上看上去也十分自然。注意,喜欢使用警告/确认的读者可以直接跳到下一节了。
言归正传,警告/确认功能在模式对话框方面有很多缺陷。第一个也是最重要的一点就是,除了在确认功能上的 OK 或 Cancel 外,它们只能显示信息却不能收集信息。更理想的情况是弹出一个对话窗,能提供更多的灵活性,且允许向此对话框添加任何想要的元素。换句话说,这个对话窗与 您在其他编程语言中使用的类似。在这方面,BlockUI 是我最钟爱的一个插件,因它易于处理,而且非常灵活,便于在同一个 Web 应用程序上处理很多对话框。不仅如此,它还提供了很多不同类型的对话框供使用。
在本文随附的示例代码(参见 下载)中,我使用 blockUI 创建了一个模式对话框,如下所示,我稍候会对所涉及的代码和选项进行深入的介绍。
图 5. blockUI 示例

正如在图 5 中所看到的,页面上有一个模式对话框。而对页面其他部分的输入则由对话框下方的半透明层所封闭,这有效阻止了用户除对话框本身之外与 Web 应用程序间的交互。实现这种封闭功能的代码使用了一个 IFrame,但实际上问题不大,因为此插件作者已经在所有不同的浏览器上测试过并已经确保此插件工作正常。
创建对话框的第一步是指定其消息。一个消息可以是您想要显示给用户的任何文本(复制 alert() 函数),可以是向用户显示 HTML,也可以是创建一个标记了的消息(使用粗体、颜色等)。不过,我认为此插件最有价值的一点是允许提供 div 作为消息,这就让您能够以自己想要的任何布局、任何数量的输入、按钮和所需信息来创建您自己的对话框。
清单 5. blockUI 插件
// this will create a dialog with our default text,
// effectively replicating the
// alert() function
$.blockUI({message: "This is a sample dialog"});
// however, we can add any HTML we want to the message, making it look closer
// to our own Web site
// $.blockUI({message: "<h2>Sample Dialog</h2><p style='color:green;'>
This is our message</p>"});
// perhaps most importantly, we can add a div to the message, allowing
// us to create our own dialog, with the look, feel, message, and input we want
<div id=loginMessage style="display:none;cursor:default;">
<p><h2>Login</h2>
<p>Username: <input type=text id=user>
<p>Password: <input type=text id=pass>
<p><input type=button value="OK" id=ok>
<input type=button value="Cancel" id=cancel>
</div>
// and the jQuery code to show this is pretty much the same
$.blockUI({ message:$("#loginMessage")});
|
有关 blockUI 只剩下两个很小的问题要讨论,之后,您就可以把它放在您自己站点上了。第一,我还没有关闭此对话框。这也是个小问题,对么?第二,我用 清单 5 中的代码片段创建的所有对话框都具有默认的 blockUI 外观,而这不是我们想要的,因为此插件的一个很重要的卖点就是能为对话框创建个性的观感。不过,blockUI 本身就带一段 CSS 代码,您可以拿来放入到自己的 CSS 文件并随意定制,这样一来,您就重新获得了让对话框以您想要的方式显示的能力。不过,为了覆盖 blockUI 的 CSS 属性,必须要告知此插件去 CSS 文件内查找而不是使用其默认设置。清单 6 所示的是示例代码内的一个代码片段,这些代码显示了如何关闭对话框、如何使用您自己的 CSS 以及此 CSS 内可用的选项。
清单 6. blockUI 插件的更多内容
// These two functions tell the BlockUI that
// you want to use your own CSS
// code to define how the dialogs will work. The first line
// tells it you want to use your own CSS for the dialog,
// the second line tells it you want your own CSS
// for the semi-transparent
// layer between the page and the dialog.
$.blockUI.defaults.css = {};
$.blockUI.defaults.overlayCSS = {};
// when the show button is pressed, we'll display the dialog.
// we want to display our own custom DIV. However, note here
// that we want to override the CSS-defined height and width.
// After all, it would be difficult for an entire site to have
// predefined widths and heights if every dialog is slightly
// different.
$("#show").click(function(){
$.blockUI({ message:$("#waitMessage"), css: {width:'500px', height:'160px'} });
});
// When the cancel button is pressed on the DIV dialog,
// we can close the dialog
$("#cancel").click(function(){
$.unblockUI();
});
// the CSS you can override to make it look good on your site
div.blockOverlay
{
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50);
-moz-opacity:.70;
opacity:.70;
background-color: #228518;
}
div.blockMsg
{
width: 20%;
top: 20%;
left: 30%;
text-align: center;
background-color: #fff;
border: 3px solid #044600;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
-moz-opacity:1;
opacity:1;
padding: 15px;
color: #000;
}
|
以我的经验,此插件非常好用。当我想要替换 JavaScript 内的 confirm() 时,我发现它很容易实现,更不必说它看上去有多好看了。此外,我还在更复杂的环境内使用过它,用它作为一个对话框来将文件上传到一个电子邮件应用程序,或 者在一个照片浏览应用程序内将其作为一个照片 lightbox。如果将其与 Ajax 调用综合在一起,可以节省很多页面重载。借此,就能扩大屏幕区域以便显示信息。最后,它还去掉了警告/确认功能的限制,让您能以对话框的思路来设计您的软 件,正如桌面应用程序那样。
printArea
printArea 插件在 Web 应用程序也有独特的用途,而且我发现它很有用。其核心是让您能够打印一个特定的 HTML 元素。与在 Web 应用程序上进行打印的其他方式相比,它有一定的优势,原来在 Web 应用程序上打印只能简单调用 window.print() 按钮,然后期待最好的结果。正如所有使用过 Internet 浏览器的人会告诉您的一样,要让两个页面在不同的浏览器上打印的结果完全相同不太可能。而且,若您想要在文本区域内打印出所有的文本,而没有任何其他内容的话,就会出问题。一直到现在,这都非常困难。
正如我之前所述,此插件让您能够打印页面上的一些特定元素,而不会打印除了这些元素之外的其他元素。比如,我就曾经在一个电子邮件应用程序中使用过 此插件,在我的应用程序中,我允许用户打印文本区域内所包含的消息。我还在显示报告的页面上使用过它,我不想让这些页面上的各种按钮和图形也与报告一同打 印。人们熟悉的另一种情况就是打印飞机票。Web 页面上的机票通常具有很多信息和图形,而打印出来的机票只需包含一个条形码和基本的登机信息。
清单 7. printArea 插件
<p>An advertisement that you don't want printed out.
<p>Another advertisement that you don't want printed out.
<div id=printable>
<table width=40% cellpadding=3 cellspacing=0 border=1>
<tr><th>Name</th><th>Age</th><th>Height</th></tr>
<tr><td>John Q</td><td>23</td><td>6'1"</td></tr>
<tr><td>Jane Q</td><td>23</td><td>5'1"</td></tr>
<tr><td>Jimmy R</td><td>23</td><td>5'6"</td></tr>
</table>
</div>
<p><input type=button id=printButton value="Print">
<p>All the annoying disclaimer text you don't want printed out.
// Capture the click on the "Print" button. Then, you can call the printArea()
// function on the "printable" div we used to wrap the portion of the page we want
// printed. The only thing that will get printed to the printer will
// be the table, and all the annoying text won't be.
$("#printButton").click(function(){
$("#printable").printArea();
});
|
该插件提供了一种十分简单直观的方式来控制页面上元素的打印。更妙的是,它在 Internet Explorer 和 Firefox 这两种浏览器上都工作得很好,并可用来控制页面如何在这两种浏览器内打印出来,为您去除了许多让人头痛的问题。惟一的一个缺陷是它依赖于用户来按下您设置的 Print 按钮,或者页面上您所能控制的其他区域。如果用户单击了浏览器的 Print 按钮,它就会将用户带回到原来容易出现问题的打印功能。而且麻烦加倍,您将不能停止该打印按钮的执行,甚至会发出有关使用它的警告。所以,您只能期盼用户 足够聪明来按下您自己的 Print 按钮。而其他的一些方式,则是简单地进行某些条件下的自动打印,而完全不用用户参与。
AlphaNumericPlus
通常,在用户界面设计中,要针对较少的用户假设来编写代码。一个减少用户假设的好例子是表单上文本字段的处理。假设,已经设立了一个字段来获取电话号码,但您知道有些人会输入字母。
您可以在三个地方阻止此不正确的输入。第一个地方是在输入进入数据库之前在服务器端检查错误的输入。因此,可以写一些 Java™ 代码或 PHP 代码来检查所有输入字段并决定哪些字段具有有效输入,哪些字段没有,如果它们无效,则向客户机发送回一个消息。第二层错误检查是用 JavaScript 在客户端进行检查。通过在客户端检查这些条件,就能节省网络流量并能提供给用户更为快速的响应。最后一层错误检查是充分利用 “较少的用户假设” 的 设计模式,该模式不允许用户输入任何坏的信息。我们这里所讨论的例子,为什么还要向服务器发送字母作为电话号码呢?或者为什么还要在提交此表单前让 JavaScript 检查有无字母呢?最好的方案就是根本不让用户键入任何字母。
此插件就是构建在这样的一个理念上,即 Web 应用程序的文本字段应该只接受我认为合适的输入。所以,对于一个只能有数字的字段,我想要我的文本字段只能接受数字。如果我想要它只有字母,它应只能接受字母。您知道我的想法了吧?
清单 8. AlphaNumeric 插件
<p>Alpha-only: <input type=text width=20 id=alphaOnly>
<p>Numeric-only: <input type=text width=20 id=numericOnly>
// This code will prevent unworthy characters from being entered
// into our text fields, assuring valid input
$("#alphaOnly").alpha();
$("#numericOnly").numeric();
|
这就是标准 AlphaNumeric 插件的全部了,但是我根据自己的需要对其进行了扩展,所以我将在这里讨论这些附加内容(它们包括在 下载 部分)。我原来想既然我能控制输入只有字母或数字,我应该也能扩展到包括特殊字符,比如 $、% 或 &。有了这种推测,我就能根据它们在表单内的使用将这些特定的规则附加到文本字段。比如,一个被用于电子邮件地址的文本字段应该允许包括标准数字 和字母在内的所有字符。所以,一个电子邮件地址字段应该接受 _、-、 @ 和 “.”。所有其他特殊的字符都是无效的,并且如果这些字符用在了电子邮件内,将会导致一个错误,所以我们禁止这些字符的键入。我们能 100% 保证这是一个有效的电子邮件地址么?不能,用户还是会键入可笑的内容,比如 “a@a@.com”,但我们迈出了第一步。
我向标准的 AlphaNumeric 插件添加的功能是用来处理十进制、货币、电子邮件、电话、时钟和日期的。清单 9 所示的就是我的代码。
清单 9. AlphaNumericPlus 插件
// will only allow numerals and the .
// and , characters (because I'm thinking international
// here, and some countries use a , for a decimal)
$("#decimalOnly").decimal();
// same as the above, but it adds support for the "%"
$("#percentOnly").percent();
// as described above, only allows valid e-mail characters
$("#emailOnly").email();
// allows only numbers and the ( and ) and - characters
$("#phoneOnly").phone();
// allows only numbers and the : character
$("#clockOnly").clock();
// allows only numbers and the / and - characters
$("#dateOnly").date();
// this is the only one that's slightly different, in that
// it requires you to pass in the valid currency symbol, so you can
// pass in a pound sign or euro if you're using this overseas
$("#currencyOnly").currency("$");
|
此 AlphaNumericPlus 插件是防止用户无效输入的第一层保护。通过不允许特定条件下出现无效字符,它不仅能最小化错误的机率,它还迅速向用户强调他们没有输入有效信息。所以,如 果一个用户试图在一个电话号码字段输入一个字母,用户立即就会被通知出错了。这种快速响应通常都会备受用户欢迎,因为用户无需等待到处理结束来修复所有错 误。
防止出错的第二个层面是基于表单提交的客户端检查。在这种情况下,可以检查数据是不是都处于有效格式;比如,检查电话号码是否具有正确数量的数字。 防止出错的最后一个层面是服务器端检查,在此,可以进一步检查格式是否有效,以及针对数据库内已经有的任何内容来检查信息。一个成功的三管齐下的方式才是 最好的解决方案,其中由此插件充当第一道防线。虽然不完美(尽管所有字符都有效,您还是能输入一个无效格式),但它毕竟能将反馈立即呈现给用户。
计算
我发现的另外一个在某些情况下很有用的插件是 Calculation 插件。与大多数优秀的插件一样,它也只是做几件事情,但是却做得很好。正如其名字所暗示的,它能计算字段内的数字并得出这些数的总和、平均数、最大数或最 小数。它还能让您从一个字段解析一个数字。该插件如此棒的灵活性得益于其能对来自任何类型的元素的数字进行求和。所以,如果传递给它一个包含 DIV、INPUT TEXT 和 SPAN 的元素数组,它将会找到这些字段内的数字,将其转变为数字并返回总和。这在某些情况下非常有用,并能省去尝试对每个数字运算函数进行错误检查的大量烦恼。
但是,关于此插件有一点需要注意,即它打破了标准的插件法则,即所有插件均返回此 jQuery 对象本身。在本文所讨论的其他插件以及我处理过的所有其他插件内,函数返回 jQuery 对象,这意味着它没有 “打破链”。而此插件则没有遵守。因为返回了数字,它打破了这条链,这样一来,在调用它之后,就不能再将这些 jQuery 函数串联在一起了。
让我们来看此插件的一个稍加扩展的例子。图 6 所示的小部件具有一组允许用户输入百分比的字段。这个小部件检查所有字段加起来是不是 100%,如果不是,就会将百分比的总和用红色显示以表明错误。在财务计划页面很容易遇到这类小部件,让您决定在哪方面增加投入。
图 6. 401k contribution 小部件

清单 10. Calculation 插件
// Set up the table that contains the widget.
// Note that we add a class to it called
// "percentSimple", which will be used in the jQuery
// code, as well as adding a unique
// ID to the table "sortTable1".
// Also note that each text field that will have a percent
// typed into it has the
// class of "percent" added to it.
// Finally, note in the table footer a field called "percentTotal"
<p><table width=300 cellpadding=0 cellspacing=0 id=sortTable1>
<tbody>
<tr><td>
S&P 500 Index
</td>
<td>
<input type=text> %
</td>
</tr>
.......
<tfoot>
<tr><td>
Total
</td>
<td>
<span></span> %
</td>
</tr>
</tfoot>
// take advantage of our previous plug-in and limit the percent fields
// to just numbers
$(".percent").numeric();
// capture any keyup events from all the "percent" fields
// when these occur, we are going to recalculate the total percent
$("table.percentSimple input.percent").keyup(function(){
// figure out which table this occurred in (in case there's more than
// 1 widget on a page
var table = $(this).parents().filter("table.percentSimple");
var ID = table.attr("id");
// Find the sum of all the "percent" fields, by using
// our calculation plug-in
var sum = $("#" + ID + " input.percent").sum();
// cache the span called "percentTotal"
var totalField = $("#" + ID + " .percentTotal");
// update the total in the "percentTotal" field
totalField.html(sum);
// if the sum != 100%, then we'll attach an error to it
if (sum != 100 && sum != 0)
{
totalField.addClass("error");
}
else
{
totalField.removeClass("error");
}
});
正如您所见,此插件的用途有限,但是它非常有效,而且若能正确使用还能节省很多时间。虽然我只包括了 max/min、average 和 sum 函数,我仍能想象得到有人如何雄心勃勃地想要组合成一个完整的电子表格插件,其中包含 Microsoft Excel 应用程序内的所有函数。想象一下这个电子表格插件可以提供标准的 deviation、payment 以及 future value(因为我经常接触金融问题,所以 我对这些函数很熟悉)函数。此外,再想象一下此电子表格插件如何能被那些希望在其页面上提供基本的电子表格功能,或允许用户无需输入任何复杂公式就能创建 各自类似电子表格页面的 Web 站点使用。
Timeout/Interval
JavaScript 让我最为头痛的事情之一就是其与常见的线程设计的不兼容性。当我从 Swing 的 Java 用户界面转向 JavaScript Web 应用程序时,我发现我在 JavaScript 内找不到 Java 代码的多线程界面的替代物。相反,JavaScript 使用
清单 11 所示的代码其实并不实用,因为在此线程运行之前,您可能就将其关闭了。调用
这段代码应该会工作,因为它将 Timers 插件的目的是通过改变您处理线程的方式来简化 Web 应用程序上的 JavaScript 线程处理,并且依我看来,让其更接近 Java 代码。与依赖于从
Timers 插件确实能让 JavaScript 内的线程处理更为容易。即便这些更改初看上去有些不明显,但是仔细分析后就会发现它如何使您的代码变得更整洁。根本上,您可以在一个地方定义整个线程。这 意味着将要定义启动机制、延迟、要执行的循环的数量以及用来运行每个交互所需的代码。这不仅比传统 JavaScript 线程更易于处理,而且我甚至可以说这比 Java 线程都容易。有了 Timers 插件,整个线程本身都是齐备的。再也无需对处于代码内的任何地方的函数进行引用了。再也无需跟踪全局变量内的 结束语 本文对插件的介绍就到此为止了,但这并不是说您对 jQuery 中所有可用插件的学习也就此结束了。本文中我只介绍了 jQuery 插件站点 200-400 个可用插件中的 8 个插件。虽然只是介绍了 8 个,但我认为我所选择的这 8 个插件是最能帮助消除桌面应用程序与 Web 应用程序之间差异的插件。这些插件填补了 JavaScript 与 jQuery 之间存在的差异。回想一下您从这 8 个插件中学到了什么?它们能对您的工作有怎样的帮助?再想想其余上百个插件能为您做什么?我建议您抽出一些时间浏览一下 jQuery 站点上的插件存储库。我在本文中介绍的 8 个插件就是通过浏览这个站点并认真考察每个插件发掘出来的。另一个发现好插件的途径就是通过您自已的具体工作需求。从此刻起,当遇到问题时,您脑子里应有 的第一个想法就应该是 “有没有一个现成的插件能完成此事?”。总之,别人做过的事不必自已重复再做。 最后,让我们回顾一下本文中介绍过的插件以及它们是怎样改进 Web 应用程序的。其中的 3 个鼠标单击插件可以使 Web 应用程序超越单纯依靠鼠标左键单击才能移动的局限,并且可以捕捉所有来自于鼠标的交互。由于桌面应用程序已经做到了这一点,这些插件真正地填补了存在于 Web 应用程序中的一个用户界面漏洞。我接下来讨论了另一个插件 blockUI,它可以让您创建与您的 Web 站点外观相匹配的对话窗,允许您设计您自己的对话窗。由于 Web 应用程序以前只限于 我在本文中所展示的所有插件的深层主题是,虽然 Web 应用程序看起来可能比桌面应用程序受限很多,比如在外观、感觉和编码的方式上,但实际上通过生成一些代码,这些差异是可以缩小的。借助 jQuery,通过使用插件就可以填补这些差距。本文所展示的这些插件所竭力要实现的就是让 Web 应用程序表现得与桌面应用程序一样,这当然也是其最终的目的。 |
简介
在该系列之前的文章使用 jQuery:UI 项目中,我介绍了使用 jQuery 代码中的插件来提高 web 应用程序的效率。但必须知道,这些插件不是自己凭空产生的,它们是由开发人员编写、测试并完善的,这些人员为 jQuery 社区奉 献了自己的业余时间。我们做这些都是免费的,是出于对自己代码的热爱。本文主要关注您如何回报这个伟大的社区,即如何编写自己的插件并上传到 jQuery 的插件页面。这可以让所有人使用您创建的插件,可以让整个 jQuery 开发社区变得更好。今年您也做出自己的贡献吧。
在编写本文中的插件时,我发现插件的创建过程以及用来创建它的框架非常简单明了。困难的地方在于想一些其他人还没有做过的事情,并编写一些能真正完成某些操作的 JavaScript 代码。由于插件结构简单明了,对于新手它简单易学,对于高手它很灵活,因此插件的数量急速上升。
当然,在研究本文所涉及的内容时,我还发现每个作者编写插件的风格都不同,jQuery 允许好几种不同的插件编写风格。在本文中,我集中介绍最简单的一种风格,以及 jQuery 本身推荐的一种风格,插件弹出后您就会看到差别或不同的选项。
描述插件
创建插件的第 1 步当然是想一个好点子。像大部分点子一样,其他人总会给您创造机会。以我在本文中开发的插件为例,它不是什么新颖的概念,但在我撰写文本的时候,jQuery 插件社区还找不到该插件。我知道我个人会从该插件中受益良多。
我的插件是一个 NumberFormatter 插件。处理服务器端代码(比如 Java™ 或 PHP)和国际化的用户应 该很熟悉数字格式化。众所周知,并非每个人都用相同的方式格式化数字。例如,并非每个人都使用 “里” 来度量距离。在美国,数字的写法可能是 “1,250,500.75”(这个数字是从我的税收表上抄来的),但在其他国家的写法可能完全不同:德国是 “1.250.500,75”,法国是 “1 250 500,75”,瑞士是 “1′250′500.75”,日本是 “125,0500.75”。数字完全相同,但是在向 web 应用程序用户展示时使用不同的格式。
因此,问题归结到,当编写一个国际化应用程序时,如何向不同国家的人展示这些数字?当然,解决方案是使 用服务器端格式化,这种解决办法非常常见。Java 有一个健壮的格式化库,使数字的格式化变得非常简单。当使用数字在服务器上设置页面时,服务器负责处理这些数字。但是,很多时候数字可能不在服务器上,因 此您需要一种方法在客户机上格式化数字,而不需要与服务器会话。
我在这里描述的典型用例如下。您的 web 应用程序中有一个输入字段,要求用户输入他们的薪水。在美国,用户可能以各种格式输入 “$65000”、“65,000”、“65000” 和 “65,000.00”。所有这些数字都是相同的,但是您需要控制这些数字在屏幕上的显示方式,这样才能提供更好的用户体验。您可以在输入数字之后调用服务器,但是如果有许多使用不同格式的数字字段就太麻烦了。此外,如果您可以在客户端处理该问题,并向用户提供即时反馈,那么就不需要这样做了。
因此,我建立了一个空缺,之后我将尝试使用 JavaScript/jQuery 功能填 补这一空缺。我的插件将在客户机上提供数字格式化,为其他人提供一种国际化 web 应用程序的方式,且无需与服务器会话。作为额外的功能,我的插件还可以提供反向操作;该插件使开发人员能够解析数字,从格式化的文本字符串中获取数字。这 还可以应用于客户机上的数字操作。此外,我将模拟 JavaDecimalFormatter类中的功能,以维护执行数字格式化的客户端代码和标准服务器端方法之间的通用性。
第 1 步结果:我发现了一个插件需求,然后定义了对于该需求我可以填补的空缺。
插件规则
jQuery 团队建立了许多希望插件作者都能遵守的通用规则,为插件用户创建一个通用而可信的环境。考虑到 jQuery 团队比我聪明多了,我没有理由违背这些规则,对不对?出于该原因,我在此列出这些规则,并且在插件的每一步都尽量遵守这些规则。
jquery.<your plug-in name>.js”jquery.numberformatter.js”。this” 用于引用 jQuery 对象this” 时都知道将从 jQuery 收到哪个对象。检查完毕。我将仅使用 “this” 引用 jQuery 对象。format()方法将返回 jQuery 对象,虽然我的parse()方法没有返回 jQuery 对象,但我在很多地方都注明该函数打破了链条。(毕竟,它不可能返回一个 Number 对象而不打破链条)。this.each()迭代匹配的元素,这是一种可靠而有效地迭代对象的方式。var JQ = jQuery.noConflict();” 函数更改他们的 jQuery 别名(pseudonym)。但是,在我查看许多插件时,我发现该规则常常得不到遵守,这太不幸了。如果开发人员需要更改 jQuery 别名,那么很可能意味着该插件要被弃用了。检查完毕。在我的插件中,我将仅使用 jQuery 而不是它的别名 “$”。好 了,这些就是在插件代码中必须遵守的规则和建议。真正的问题在于,它们实际上是强制性的,因为如果您不遵守这些插件规则,那么您的插件就得不到广泛应用, 而且还会得到不好的反馈。结果该插件很快就没有人使用了,您所花费的时间都将白费。因此,遵守这些规则非常重要。这不仅能帮助您鹤立鸡群,保证您代码的统 一性,还能增加插件的成功几率。
第 2 步结果:我将遵守创建 jQuery 插件的所有规则
编写插件
现在可以开始编写代码了!开始编写插件的第一步是确定如何组织您的插件。开始有两种选择:您希望它是一个方法还是一个函数?“它们有区别吗?”您可能会这样问。
正如我上面提到的,方法需要附加到 jQuery.fn 对象,函数需要附加到 jQuery 对象。这样一切都 清楚了,不是吗?如果您对 jQuery 相对不太了解,那么可能还不是很清楚。您可以这样考虑。方法使代码能够迭代所有传入插件的选定元素。因此,插件可以接收任何类型的 HTML 元素,由插件决定如何处理每个元素。因此,插件方法可以接收任何 jQuery 选择器,所有从 “p” 到 “#mySpecificPageElement” 的内容。如果您希望插件更加灵活,允许用户传入任何类型的页面元素,那么最好使用方法。插件开发人员应该负责正确地处理所有内容。相比之下,函数不使用任何选定元素作为参数。函数可以简单地应用于整个页面。这也由插件开发人员负责处理,开发人员必须定义他们希望与插件交互的页面元素,并忽略其他元素。让我们在代码中看看不同之处。
清单 1. jQuery 插件方法/函数
// This is a method because you can pass any type of selector to the method and it
// will take some action on the results of the selector. In this case, it will
// doSomething() on the page element with an ID of myExample
$("#myExample").doSomething();
// This is also a method, even though you are passing the entire page body to
// the method, because you are still passing a selector
$("body").doSomethingElse();
// This is a function, because you are NOT passing any selector to the function
// The plug-in developer must determine what page elements they want to take action on.
// This is usually accomplished by the plug-in developer requiring the page elements
// to contain a certain class name.
<div>
// This hypothetical plug-in developer would document that his plug-in only works
// on elements with the class "anotherThing"
$.anotherThing();
|
从这些描述中判断,插件使用的似乎是方法,因为您需要让用户告诉您他们希望格式化哪些页面元素。清单 2 展示了现在插件的代码。
清单 2. 方法定义
jQuery.fn.format = function();
// You would call your plug-in like this (at this point)
$("#myText").format();
|
当然,您的函数不可能是放之四海而皆准的插件,因为您处理的是国际化情况,无法自动指出希望格式化文本的国家或者需要的格式。因此,您必须稍微修改插件以接收某些选项。格式化方法中需要两个选项:数字应该使用的格式(例如,#,### 以及 #,###.00)和本地语言环境(本地语言环境是一个简单的 2 字符国家代码,用于确定要使用的国际数字格式)。
您 还需要让插件尽可能的易于使用,因为您必须提高插件的成功几率。这意味着您应该继续定义一些默认的选项,使用户在不想传入选项时不需要这样做。我编写插件 的所在地是美国,这里使用的是世界上最常见的数字格式,我的默认语言环境是 “us”,格式默认为 “#,###.00”,因此货币自然要使用该默认值。
清单 3. 允许在插件中使用选项
jQuery.fn.format = function(options) {
// the jQuery.extend function takes an unlimited number of arguments, and each
// successive argument can overwrite the values of the previous ones.
// This setup is beneficial for defining default values, because you define
// them first, and then use the options passed into the method as the
// second argument. This allows the user to override any default values with their
// own in an easy-to-use setup.
var options = jQuery.extend( {
format: "#,###.00",
locale: "us"
}, options);
|
创建插件框架的最后一步是正确处理传入方法的选定元素。回想一下上例您会发现,选定元素可以是单页面元素,或者是多页面元素。您必须等效地处理它们。同样,回想一下 jQuery 插件规则,"this" 对象只能引用 jQuery 对象。因此,您有一个对传入方法的 jQuery 选定元素的引用,现在需要迭代它们。同样,回顾规则让我们知道,每个插件方法都应该返回 jQuery 对象。当然,您知道 jQuery 对象就是 "this",因此在方法中返回 this 完全没有问题。让我们看看如何在代码片段中实现迭代每个选定元素并返回 jQuery 对象。
清单 4. 处理 jQuery 对象
jQuery.fn.format = function(options) {
var options = jQuery.extend( {
format: "#,###.00",
locale: "us"
}, options);
// this code snippet will loop through the selected elements and return the jQuery object
// when complete
return this.each(function(){
// inside each iteration, you can reference the current element by using the standard
// jQuery(this) notation
// the rest of the plug-in code goes here
});
|
由于实际插件本身不是本文的重点,我不对此进行详细阐述,但是您可以在本文的插件代码附件中看到全部内容(请参见 下载)。如果您决定编写函数而不是方法,我还将向您展示一个样例,介绍如何设置插件架构。
清单 5. 使用函数的示例插件
jQuery.exampleFunction = function(options) {
var options = jQuery.extend( {
// your defaults
}, options);
jQuery(".exampleSelector").each(function(){
});
});
|
调优插件
网上关于初级插件的大部分文章都到此为止了,这时它们会让您采用基本的插件格式并运行。但是,这种基本架构也太 “基本” 了。在编写插件时还必须考虑另一件重要的事情,给您插件增色所需要的内容远不止一个初级插件那么简单。再多增加两个步骤,您就能将初级插件转换为中级插 件。
调优 #1 – 让内部方法私有化
在任何面向对象的编程语言中,您会发现创建运行重复代码的外部函数非常方便。在我创建的 NumberFormatter 插件中,有一个这种代码的样例 —— 该代码决定向函数传递哪个地理位置,以及要使用哪些字符作为小数点和分组符。format() 方法和 parse() 方法中都需要该代码,任何一个初级程序员都会告诉您这属于它自己的方法。但是,这会出现一个问题,因为您处理的是 jQuery 插件:如果您使用 JavaScript 中的定义将它作为自己的函数,那么任何人都可以为任何目的使用脚本调用该方法。这不是该函数的目的,我更倾向于不调用它,因为它仅用于内部工作。那么,让 我们看看如何将该函数私有化。
这种私有方法问题的解决方案称为 Closure,它可以有效地从外部调用关闭整个插件代码,附加到 jQuery 对象的除外(那些是公共方法)。通过这种设计,您可以将任何代码放入插件中,不用担心被外部脚本调用。通过将插件方法附加到 jQuery 对象,您可以有效地将它们变为公共方法,而让其他的函数/类私有化。清单 6 展示了实现该操作所需的代码。
清单 6. 私有化函数
jQuery UI 项目是一个令人激动的 jQuery 子项目,它许诺在下一年实现快速增长。UI 库的扩展速度非常快,远远超过了 jQuery 核心库的增长,因为 UI 开发人员在每个发布版中都添加了许多特性和修复包。然而,有趣的是,UI 包是一个真正的关于用户界面的集合,并且可以划分为 3 个主要模块:小部件(Widget),它们包含预构建的可定制皮肤的用户界面,这些界面可以直接部署到 Web 站点;效果(Effect),可以在页面上操作的非常简单直观的东西(例如,颤动和爆炸等);UI 包的最后一个方面是可以为预构建的小部件创建自己的 “主题”,让您下载的这些小部件就像专门为您的 Web 站点制作的一样。
jQuery UI 项目的历史是很有趣的,并且或多或少有助于解释为什么以这种方式构建这个项目,以及为什么该库的 3 个部分差别如此大。这个 UI 库在最初的时候(即 2007 年年末)仅是几个不同的插件。这些当时很流行、下载次数很多的插件是由 jQuery 社区的高手创建的。他们决定将这些流行的插件合并到一个更大的插件中,并提升为 jQuery 核心代码的 官方扩展。这导致了在 2008 年年初发布 UI 1.5 版。然而,在这个合并的 UI 插件发布之后,尝试在自己的代码中使用该插件的开发人员认为(或抱怨)它虽然很好,但彼此的差别过大。毕竟这些插件是由不同的开发人员编写的,他们都有各 自的风格。最后,jQuery 社区决定调整这 3 个插件,让它们具有统一的风格。这导致 1.5.x 版本的发布,您可以在 UI 库历史中看到它们。
不过,随着该 UI 库的 1.6 版本发布,所有代码重构工作将结束,并且兑现曾经的许诺,即实现一个风格和代码统一的 UI 库。这个发布版的一个缺点是 UI 团队决定从其代码库中删除几个小部件,以让 1.6 版本早日出炉。因此,虽然 1.5 版本多了 3 个小部件,但 1.6 版本应该更快,并且更简单。您可以考虑这种折衷是否适合您,但不要期望已删除的小部件再出现在未来的 UI 库中。
我们已经回顾了 UI 库的历史,现在可以探索它的优点了,以及了解为什么 jQuery 团队对此兴奋不已。我个人觉得比较令人激动的是该库的潜力。对于曾经使用过 Java™ UI 中的 AWT 的开发人员,他们肯定记得引入 Swing 时的兴奋情景,随着一组全新的小部件的引入,与用户界面打交道的开发人员能够适应吗?我将 UI 库的潜能与 Swing 进行对比,因为它能够将一组更新的用户界面添加到 Web 设计中(即向 Web 设计器添加更多的复杂小部件),同时对它们的行为进行规范化。因此,尽管目前 UI 库包含的小部件数量还不是很多,但它有望在发布 1.6 版本之后的一年内添加 10 至 20 个新的小部件,而到时开发人员可以在 Web 应用程序中使用它们。
下载和安装 UI 库与安装 jQuery 核心代码或插件不一样。为了降低对网络流量和连接的需求,UI 团队创建了一个 Web 应用程序,您可以通过这个应用程序预先选择需要使用的 UI 库部分和压缩方式。然后,您可以直接下载该文件。这样做的好处是可以仅提供您需要使用的 UI 库部分,从而避免对应用程序性能产生影响。这样做的缺点是,如果您需要使用一个未包含在定制文件中的部分,那么就需要重新下载整个文件。这就像我们的生活一样,每个银币都有正反两面。
UI 库中的 Effects 模块包含可以操作页面元素的 “有趣” 东西。我用 “有趣” 形容它们,是因为从总体上看它们确实是一个随机的效果集合。这类似于将 Microsoft® PowerPoint 的所有效果浏览一遍,然后将一小部分制成 JavaScript 效果。这一小部分效果是如何选择的,我们完全不知道。这些很酷的效果对专业 Web 应用程序有多大作用还有待评定,但我要问的问题是应该在什么地方,以及什么时候使用它们。
不过好消息是它们易于使用。第一组效果帮助您隐藏/显示页面元素。它们只是扩展了 jQuery 库的内置hide()和show()函数,让您能够指定隐藏/显示效果的类型。您的脑海中可能已经出现我正在谈论的效果,我将在图 1 中通过过程图演示 Puff 效果,分别显示前、中、后 3 个阶段。
图 1. Puff 示例

因为很难描述动画效果的外观,所以您需要从本文下载演示稿,查看实际的效果。不过,我可以给出生成图 1 中的静态图片的代码。您可以从代码中了解它是如何实现的。
清单 1. Puff 效果
$("#puffSample").click(function(){
$(this).hide("puff");
});
$("#puffReappear").click(function(){
$("#puffSample").show("puff");
});
|
如您所见,现在的代码还没有什么新奇的地方。hide/show 效果的工作原理还是相当简单的。真正的挑战是找出如何在恰当时间以恰当的方式使用它们,而不是让用户感慨 “喔,这毫无必要!”
我们接下来查看 Effects 模块中包含的其他效果。这些效果更加实用,我能想象到如何在应用程序中使用它们,以改善用户体验(至少一部分效果能改善用户体验)。这里的效果也很难直接描述,因此我建议您从本文下载一些示例,以观察效果的实际运行情况。
这 个模块中包含的效果确实能够引起用户对页面元素的关注,其中一些效果在这方面表现不错。这些效果分别是 “bounce”、“highlight”、“pulsate”、“scale”、“shake” 和 “size”。“bounce” 会让页面元素弹跳起来,就像它落在虚拟的 HTML 蹦床上一样;“highlight” 在黄色的背景中闪现元素;“pulsate” 让元素在可见和不可见之间切换;“scale” 将元素收缩 50%;而 “size” 让元素变长变平。它们就像 hide/show 一样容易使用。
清单 2. Bounce 效果
$("#bounce").click(function(){
$("#loginSample").effect("bounce");
});
|
它们构成了 UI 库中的 Effects 模块,虽然它们在文字描述中并无新奇之处,但是也有一些非常酷的效果,如果您需要让页面产生爆炸效果,就可以使用它们。
最后,我将介绍比页面爆炸更令人兴奋的东西。Interactions 非常强大,您可以通过它决定拖动一个元素越过另一个元素会发生什么,将一个元素置于另一个元素之上会发生什么,以及使用两个 HTML 元素可以实现的其他有趣的事情。
通过 dragging/dropping 支持交互是 Web 应用程序设计的新领域。因为大部分用户都习惯于静态页面和静态页面元素,所以支持拖放交互的 Web 应用程序非常少。甚至支持这种独特交互的桌面应用程序也不多。但是这样的 Web 应用程序还是存在的,其中一个例子就是:Yahoo Fantasy Sports 页面几乎完美地利用了 drag/drop 模型,使用它们可以刷新队员、在挑选之前对运动员进行排序,并将运动员移动到裁员列表。即使您没有使用过 Fantasy Sports,我还是鼓励您试用它,看看它们的独特用户界面设计。这是我见过的在 Web 上使用 drag/drop 模型的最佳典范。
Interactions 模块包含 5 种类型的交互。前 3 种是:
在这个小节中,我主要关注两个我认为对未来的 Web 应用程序比较有用的交互:
在接下来的例子中,我将同时使用它们,尽管这不是必须的。
在这个示例 Web 应用程序中,我将改变人们在线购物的方式。这里不是通过按下一个按钮来将商品放到购物车中,我希望通过 Draggable/Droppable 模拟人们购物时的动作,即将商品放到购物车中。用户将把需要购买的商品拖放到购物车中。当商品进入购物车之后,将自动更新购物车中的商品数量和总价。
图 2. 购物车示例

我们看看完成这个交互所需的少量代码。(本文的末尾包含完整的样例代码)。
清单 3. 购物车
// First, set up the shopping cart
// Give the cart an ID that we can reference in the jQuery code
// Give each span an ID so we can easily update the text in it
<img src="cart.jpg" id=cart>
<p><span id=numItems>0</span> items in your cart.
<p>Your total is $<span id=totalPrice>0</span>.
// Each product has code that looks like this. The entire product
// is wrapped in a DIV with a class of "product" and given a
// unique ID. It also has a span that defines the price of the object.
<p><div id=ban><img src="banana.jpg" align=left> Bananas
<p>$<span>1.99</span></div><br><br>
// define each DIV that has a "product" as "draggable"
// The draggable function has many options that define how the object
// will look and feel when dragged. There are many options, most not
// covered here, but I put a sampling in to whet your appetite.
$(".product").draggable({
'opacity': 0.3, // make the object semi-transparent when dragged
'revert': "valid", // snap the object back after it's been dropped
'delay': 200, // delay 200 ms before starting to drag it
'distance':4, // wait till it's been dragged 4 pixels before starting
'helper':"clone" // keep the object where it is and use a helper to show dragging
});
// just like the draggable has many options, the droppable has many
// options as well. These options provide a variety of ways
// to offer reinforcement to the user about how the draggable/droppable
// relationship is to work
$("#cart").droppable({
'accept':".product", // define which elements will trigger a 'drop'
'activeClass':"border", // what class to add to the droppable while dragging
'drop': function(e,ui){ // this function gets called when something is dropped
// update the number of items in the cart
$("#numItems").text(new Number($("#numItems").text())+1);
var ID = $(ui.draggable).attr("id");
// get the price from the object just placed in the cart
var price = new Number($("#"+ID + " .price").text());
// update the total price in the cart
$("#totalPrice").text(new Number($("#totalPrice").text())+price);
}
});
|
这就是所需的代码。创建这个示例非常轻松,我自己都感到惊奇。毫无疑问,由于使用这种交互创建用户界面非常简单直观,所以它将流行起来。这个小节提到的其他交互也同样很简单,并且您可以在 Web 应用程序中利用它们各自的特点。
在介绍 UI 代码的最后一个小节中,我可以通过图片描述它们。对我而言,UI 库最令人兴奋的是它的 Widgets 模块,该模块包含预构建的、经过测试的小部件,它们涉及到开发人员需要经历的 Web 应用程序的各个方面。这些小部件节省了我们的时间,让我们能够上网冲浪或阅读像本文一样优秀的文章。我还建议您告诉您的老板,您正在苦心研究这些小部件, 这样他就会认为您今年工作特别努力,并且给您升迁的机会。
这些屏幕截图获得 UI 主页的授权使用。
Accordian
图 3. Accordian

accordian 插件将一个列表转变为图形表示,从而允许用户在列表的各个部分之间移动,每次仅能查看一个部分。在某些 UI 库中,这个小部件称为 “Outlook Bar”,因为它在几年前起源于该程序。使用这个小部件的一个最大误区就是查看多个部分。提供该功能的是另一个小部件。这个小部件一次仅能显示一个部分, 并且能够在一个部分中包含许多信息,只要这些信息不依赖于另一个部分。
要使用这个小部件,必须正确设置 HTML。换句话说,在使用 accordian 小部件之前要先设置 HTML。这还考虑到所谓的 “渐进衰退” 理论,即使用户不使用 JavaScript,或使用 Internet Explorer 5 都仍然能够正确查看页面。在清单 4 的例子中,注意 HTML 是如何构造的,因为任何想使用这个小部件的用户都必须了解它。
清单 4. Accordian
// The accordian relies on the UL, LI, and A tags to properly configure itself
<ul id="accordianExample" style="width:400px;">
<li>
<a href="#">Title 1</a>
<div>Your text would go here</div>
</li>
<li>
<a href="#">Title 2</a>
<div>More text</div>
</li>
</ul>
// yes, it's this simple
$("#accordianExample").accordian();
|
活动条
图 4. 活动条

<div id="sliderExample">
<div></div>
</div>
// this will set up the slider
$("#sliderExample").slider();
// this will set the maximum and minimum values of the slider
$("#sliderExample").slider({
min: 10,
max: 20
});
// this will get the value of the slider
$("#sliderExample").slider("value");
|
DatePicker
图 5. DatePicker

<input type=text id=dateField>
// this will create a date picker, which pops up when the textfield gets focus
$("#dateField").datepicker();
// show the drop-down fields to let the user jump around months and years
$("#dateField").datepicker({
changeMonth: true,
changeYear: true
});
// show two months instead of one
$("#dateField").datepicker({
numberOfMonths: 2
});
// because this widget has to support internationalization, it has a TON of
// functions that let you set every aspect of the calendar, to support any
// possible calendar settings. (Although it doesn't look like the jQuery team
// felt it was necessary to support the Mayan calendar. Guess 2012 is right
// around the corner.)
//
// This function lets you set the date format that will appear in the textfield.
$("#dateField").datepicker({
dateFormat: 'dd/mm/yyyy'
});
// Gets the date back from the datepicker
var date = $("#dateField").datepicker("getDate");
|
进度条
图 6. 进度条

<div id="progressbarExample"></div>
// make this div a progress bar
$("#progressbarExample").progressbar();
// start the progress bar
$("#progressbarExample").start();
// stop the progress bar
$("#progressbarExample").stop();
|
Dialog
图 7. 对话框

对于一直学习本系列的人而言,Dialog 小部件和在 “使用 jQuery,第 1 部分:中级 jQuery:使用插件创建和扩展 jQuery 函数” 中见到的 BlockUI 类似。它们都执行对话功能,您可以根据需要选择其中之一。BlockUI 在控制外观上具有更大的灵活性,而这个 Dialog 小部件能够帮助您完成更多工作。
<div id="dialogExample" title="Example Dialog">Text here. Warning!!</div>
// turn the DIV into a dialog
$("#dialogExample").dialog();
// open the dialog, then close the dialog
$("#dialogExample").dialog("open").dialog("close");
// make a dialog that the user can't drag around, is modal, and in the center of the page
$("#dialogExample").dialog({
draggable: false,
modal: true,
position: "center"
});
|
选项卡
图 8. 选项卡

Tabs 可能是这些小部件中最不受欢迎的一个。我从来没能让它很好地发挥过作用,并且发现它严重依赖于 CSS 才能正常工作。由于关于它的示例很冗长复杂,加上我对它有点偏见,所以就不在这里回顾它。就我个人而言,我认为自己创建一个 DIV 与使用这个小部件差不多一样容易。
这个 UI 库的最后一个方面是 “ThemeRoller”。这是一个奇怪的名字,但是如果您选择在自己的代码中使用这个库的小部件,它就非常有用。我猜测这个 UI 库的小部件的观感与您的页面当前的观感不协调。我还这样猜测,如果您打算使用这些小部件,那么肯定希望它们能融入到您的站点中。所以,这个 UI 库的创建者还创建了一个 CSS 生成器,并且不断改进它,从而让您可以修改这些小部件,使它们能够融入到您的站点中,然后保存 CSS 文件。生成的 CSS 让所有小部件与您的站点协调一致,让您能够更轻松的使用它们。
ThemeRoller 的许多好处之一就是允许您为一些东西创建 CSS,这个创建过程原来只有 CSS 专家才能完成。例如,它让您在小部件的任何部分指定圆形角,以及创建下拉阴影。它已经将 CSS 文件最棘手的代码编写部分移出 UI 库,让不熟悉图像艺术的开发人员能够轻松使用和修改小部件。ThemeRoller 并没有让您处理复杂的 CSS 文件,并最终得到一个难看的小部件,而是让您不需要编写代码就能轻松创建外观漂亮的小部件。
最后,ThemeRoller 附带了许多预构建的主题,您可以使用它们让小部件变得更生动。以我的经验为例,我发现预构建主题的外观比自己设计的要好得多。我不是将站点的 CSS 放入到 ThemeRoller 中,而是将 ThemeRoller CSS 放到您的站点中。考虑使用这种便捷的方式实现专业的 Web 应用程序外观!
现在,我们已经介绍了 UI 库。这个 UI 库是一个发展非常快的项目,并且通过不断改进提供更好的小部件。在上一年中,它取得了一个巨大的进步,即组合了 3 个流行的插件。我希望这种快速发展的势头能够继续保持下去,因为我盼望着看到这个库到底能够为开发人员提供多少东西。尽管我对 Effects 不是很有信心,但是 Interaction 和 Widget 模块为您在 Web 应用程序中使用这个 UI 库提供了大量让人信服的解释。
我在本文中介绍了 UI 库 1.6 版本的所有小部件。这 6 个小部件只不过是 UI 团队的第一批成果,它还将提供更多的小部件。您将有希望看到一些常见的桌面小部件,比如颜色选择器、时间选择器、自动下拉、菜单栏和工具栏等。正如我在许 多文章中强调的一样,jQuery 团队的主要目标之一是让创建 Web 应用程序像创建桌面应用程序一样简单。我认为,用户很希望 Web 应用程序类似于他们的桌面应用程序,因为这样才符合他们的观感和使用习惯。