Ajax 改造:用 jQuery、Ajax 选项卡和照片 carousel 改进现有的站点
既然已经在单页面版本的 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:
<div class="tabContent" id="productImages">
<h2>Product Images</h2>
<div id="imageCarousel" class="jcarousel-skin-tango">
<ul class="productImages">
</ul>
</div>
</div>
|
最后,在 HTML 文档头中这些文件下面的脚本块中添加定制的 JavaScript 代码。这个脚本块见 清单 12:
清单 12. detailB1.html 中的 JavaScript 代码
<script type="text/javascript">
window.alert = function() {
return;
};
$(document).ready(function() {
/*
create an image slideshow from a JS array of URLs using
jcarousel
*/
var itemLoadCallback = function(carousel, state) {
if (state != 'init') {
return;
}
jQuery.getJSON("detailB5-fragment.html", function(data){
itemAddCallback(carousel, carousel.first,
carousel.last, data.items);
});
};
var itemAddCallback = function(carousel, first, last, data) {
for (i = 0, j = data.length; i < j; i++) {
carousel.add(i, getItemHTML(data[i]));
}
carousel.size(data.length);
};
var getItemHTML = function(d) {
return '<img class="product" alt="product photo" width="' +
d.width + '" height="' +
d.height + '" src="../img/' +
d.url + '" />Photo credit: <a target="_blank"' +
' href="http://www.flickr.com/photos/' +
d.creditURL + 's/">' +
d.creditLabel + '</a>, Flickr, ' +
'Create Commons Attribution License'
;
};
jQuery('#imageCarousel').jcarousel({
itemLoadCallback: itemLoadCallback,
scroll: 1
});
jQuery('ul.productImages').css("width","3012px");
});
</script>
|
这段 JavaScript 比前面创建的代码都复杂。它包含以下函数:
itemLoadCallback。使用 Ajax 从前面创建的 JSON 文档读取数据,然后把数据传递给itemAddCallback。itemAddCallBack。解析itemLoadCallback装载的 JSON 数据并把每个图像添加到 carousel 中。getItemHTML。把 JSON 数据转换成插入 DOM 所需的 jCarousel 标记。
为了调用这些函数,要把 itemLoadCallback 作为选项散列的一部分传递给 jcarousel 方法。这告诉 jCarousel,它应该用 Ajax 数据动态地构建幻灯片,而不是使用 DOM 中现有的标记。itemLoadCallback 和它的辅助函数会自动完成这个任务。但是,jCarousel 在采用这种方式时会产生两个奇怪的现象。
首先,jCarousel 似乎会错误地计算动态装载的内容的 CSS 属性。结果是在幻灯片中有几个图像根本不出现。调试表明,jCarousel 弄错了包含图像的 ul 的宽度。您可以花时间调试这个问题并扩展 jCarousel 代码来纠正它,但是有一个更简便的方法:在构建 carousel 之后,用 jQuery 的 css 方法调整它的宽度,使它与 Product Details 版本 A 中非动态生成的 carousel 的宽度匹配。
jCarousel 的第二个问题与第一个问题相关:因为 jCarousel 对 DOM 元素宽度的计算不正确,所以每当用户调整浏览器窗口大小时,它会显示一个错误消息。这个错误消息以 JavaScript 警告框的形式出现,这绝对不是很好的用户体验。要想纠正这种行为,可以用一个哑函数替换 JavaScript 内置的 window.alert 方法。
要想在多页面的 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 调用,自动地对相同的内容应用不同的包装器。但是,因为这个示例只使用客户端代码,所以我们用单独的文件模拟这种效果。
<div class="tabContent" id="moreDetails"> <h2>More Details</h2> <!--paragraphs of text content here--> </div> |
接下来,需要修改 detailB1.html 中的一些标记。在 Customize Me Now 1.1 中,这个页面包含一个辅助导航菜单,帮助用户在页面之间移动,见清单 14。对这些标记做一些修改,让 jQuery UI Tabs 把它转换为选项卡界面。修改后的标记见清单 15。
清单 14. detailB1.html 中原来的导航菜单标记
<ul class="nav"> <li><a href="detailB1.html">Introduction</a></li> <li><a href="detailB2.html">More Details</a></li> <li><a href="detailB3.html">User Reviews</a></li> <li><a href="detailB4.html">Technical Specifications</a></li> <li class="last"><a href="detailB5a.html">Photos</a></li> </ul> |
清单 15. detailB1.html 中修改后的 Ajax 选项卡标记
<ul class="nav">
<li><a href="detailB1.html"><span>Introduction</span></a></li>
<li><a href="detailB2.html"><span>More Details</span></a></li>
<li><a href="detailB3.html"><span>User Reviews</span></a></li>
<li><a href="detailB4.html"><span>Technical Specifications</span></a></li>
<li class="last"><a href="detailB5a.html"><span>Product Images</span></a></li>
</ul>
|
接下来,在页面上的内联脚本块中添加 JavaScript 代码。完成之后,脚本块类似于清单 16:
$(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:
#CMN .tabContent.noTop {
border-top: 0;
}
|
清单 19. 应用于 HTML 片段文件的 CSS 类
<div class="tabContent noTop" id="moreDetails"> <h2>More Details</h2> <!--paragraphs of text content here--> </div> |
现在,可以在 Web 浏览器中查看 2.1 版的 Product Details 页面版本 B。它的外观和表现应该很像版本 A(图 5 和 图 6)。但是在幕后,版本 B 的效率高得多。在用户单击对应的选项卡之前,并不装载 Ajax 内容,这会节省带宽。但是,这并不会节省传输图像所需的带宽。按照定制 Ajax 代码的工作方式,会默认装载 jCarousel 幻灯片中的所有图像。尽管如此,页面的装载时间仍然得到了改进。直到显示页面的其余部分之后,浏览器才会向服务器请求这些图像。对于使用慢速连接的用户,这会显著改进用户体验。
还可以进一步改进这个页面:让浏览器在用户选择 Product Images 选项卡之前不自动装载图像。这样的话,如果用户不打算查看图像,就不必浪费带宽来装载它们。这样的解决方案会增加 JavaScript 代码的复杂性,但是如果产品的图像比较多,这样做就是值得的。
当关闭 JavaScript 支持时,Product Details 的版本 A 和 B 之间的根本差异就会表现出来。版本 A 会退化为单一滚动页面,而版本 B 退化为多页面版本。
在本系列的第 3 部分中,学习了如何应用渐进式改进原理实现现代的便于使用的 Ajax 界面。还进一步了解了 jQuery 及其插件。可以使用在本文中学到的技能进一步改进您的站点。例如,可以使用 jQuery Cookie 插件让浏览器在用户下一次访问页面时记住原来选择的选项卡。还可以在 Purchase Confirmation 页面上构建选项卡式界面:交叉销售、订单重放和帐单重放各有一个选项卡。改进的机会是无限的。
在真实环境中,可能根本用不到构建 Product Details 页面的三个版本。对于这样的简单功能,这样做花的时间太多了。另外,混合使用不同的界面会把用户弄糊涂。尽管如此,本文说明了在 jQuery 等开放源码工具的帮助下,很容易实现 Ajax、渐进式改进和以用户为中心的设计。




