原文:Pro jQuery 2.0
协议:CC BY-NC-SA 4.0
十、使用 jQuery 效果
在很大程度上,jQuery UI 包含了与 jQuery 相关的用户界面(UI)功能,但是核心库包含了一些基本的效果和动画,这些是本章的主题。虽然我把它们描述为基本的,但是它们可以用来实现一些非常复杂的效果。主要的焦点是动画元素的可见性,但是您可以使用这些特性以多种方式动画一系列 CSS 属性。表 10-1 对本章进行了总结。
表 10-1 。章节总结
问题 | 解决办法 | 列表 |
---|---|---|
显示或隐藏元素 | 使用显示或隐藏的方法 | one |
切换元素的可见性 | 使用切换方法 | 2, 3 |
动画元素的可见性 | 为显示、隐藏或切换方法提供一个 timespan 参数 | four |
在动画结束时调用函数 | 为显示、隐藏或切换方法提供一个回调参数 | 5–7 |
沿垂直方向设置可见性动画 | 使用向下滑动、向上滑动或滑动切换的方法 | eight |
使用不透明度制作可见性动画 | 使用淡入、淡出、淡出切换或淡出到的方法 | 9–11 |
创建自定义效果 | 使用动画方法 | 12–14 |
检查效果队列 | 使用队列方法 | 15, 16 |
停止并清除效果队列 | 使用停止或结束的方法 | 17, 18 |
在效果队列中插入延迟 | 使用延迟方法 | Nineteen |
将自定义函数插入队列 | 使用带有函数参数的 queue 方法,并确保执行队列中的下一个函数 | 20, 21 |
禁用效果动画 | 将 $.fx.off 属性设置为 true | Twenty-two |
自上一版以来,JQUERY 发生了变化
jQuery 1.9/2.0 定义了一个新的finish
方法,用于完成当前效果和清除事件队列。详见“停止效果和清除队列”一节。
使用基本效果
最基本的效果只是显示或隐藏元素。表 10-2 描述了你可以使用的方法。
表 10-2 。基本特效方法
方法 | 描述 |
---|---|
hide() | 隐藏 jQuery 对象中的所有元素 |
hide(time) hide(time, easing) | 使用可选的缓动样式在指定的时间段内隐藏 jQuery 对象中的元素 |
hide(time, function) hide(time, easing, function) | 使用可选的缓动样式和效果完成时调用的函数,在指定的时间段内隐藏 jQuery 对象中的元素 |
show() | 显示了一个 jQuery 对象中的所有元素 |
show(time) show(time, easing) | 使用可选的缓动样式显示指定时间段内 jQuery 对象中的元素 |
show(time, function) show(time, easing, function) | 显示指定时间段内 jQuery 对象中的元素,带有可选的缓动样式和效果完成时调用的函数 |
toggle() | 切换 jQuery 对象中元素的可见性 |
toggle(time) toggle(time, easing) | 使用可选的缓动样式切换指定时间段内 jQuery 对象中元素的可见性 |
toggle(time, function) toggle(time, easing, function) | 使用可选的缓动样式和效果完成时调用的函数,在指定时间段内切换 jQuery 对象中元素的可见性 |
toggle(boolean) | 单向切换 jQuery 对象中的元素 |
清单 10-1 展示了这些效果中最简单的一个,那就是不带任何参数地使用show
和hide
方法。
清单 10-1 。使用不带参数的 Show 和 Hide 方法
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
<script type="text/javascript">
$(document).ready(function() {
$("<button>Hide</button><button>Show</button>").appendTo("#buttonDiv")
.click(function(e) {
if ($(e.target).text() == "Hide") {
$("#row1 div.dcell").hide();
} else {
$("#row1 div.dcell").show();
}
e.preventDefault();
});
});
</script>
</head>
<body>
<h1>Jacqui's Flower Shop</h1>
<form method="post">
<div id="oblock">
<div class="dtable">
<div id="row1" class="drow">
<div class="dcell">
<img src="aster.png"/><label for="aster">Aster:</label>
<input name="aster" value="0" required />
</div>
<div class="dcell">
<img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
<input name="daffodil" value="0" required />
</div>
<div class="dcell">
<img src="rose.png"/><label for="rose">Rose:</label>
<input name="rose" value="0" required />
</div>
</div>
<div id="row2"class="drow">
<div class="dcell">
<img src="peony.png"/><label for="peony">Peony:</label>
<input name="peony" value="0" required />
</div>
<div class="dcell">
<img src="primula.png"/><label for="primula">Primula:</label>
<input name="primula" value="0" required />
</div>
<div class="dcell">
<img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
<input name="snowdrop" value="0" required />
</div>
</div>
</div>
</div>
<div id="buttonDiv"><button type="submit">Place Order</button></div>
</form>
</body>
</html>
我操纵 DOM(域对象模型)来添加两个button
元素,并提供一个当它们中的任何一个被单击时被调用的函数。这个函数使用text
方法来计算哪个button
被使用了,并调用hide
或show
方法。在这两种情况下,我都使用选择器#row1 div.dcell
在jQuery
对象上调用这个方法,这意味着那些dcell
类中的div
元素是具有row1
的id
的元素的后代,它们将变得不可见或可见。图 10-1 显示了当我点击Hide
按钮时会发生什么。
图 10-1 。隐藏元素用 hide 元素
点击Show
按钮调用show
方法,恢复隐藏的元素,如图图 10-2 所示。
图 10-2 。用 show 方法显示元素
很难用图来显示过渡,但是有几点需要注意。首先是过渡是即时的:没有延迟或影响,元素只是出现和消失。第二,在已经隐藏的元素上调用hide
没有效果;在可见的元素上调用show
也不行。最后,当您隐藏或显示一个元素时,您也可以显示或隐藏它的所有后代。
提示你可以使用:visible
和:hidden
选择器来选择元素。有关 jQuery 扩展 CSS 选择器的详细信息,请参见第五章。
切换元素
您可以使用toggle
方法将元素从可见或隐藏翻转回来。清单 10-2 给出了一个例子。
清单 10-2 。使用切换方法切换元素可见性
...
<script type="text/javascript">
$(document).ready(function() {
$("<button>Toggle</button>").appendTo("#buttonDiv")
.click(function(e) {
$("div.dcell:first-child").toggle();
e.preventDefault();
});
});
</script>
...
在清单 10-2 中,我在文档中添加了一个按钮,当它被点击时,我使用toggle
元素来改变div.dcell
元素的可见性,这些元素是它们的父元素的第一个子元素。你可以在图 10-3 中看到效果。
图 10-3 。切换元素的可见性
提示注意,文档的结构在隐藏的元素周围折叠。如果您想隐藏元素并在屏幕上留出空间,那么您可以将 CSS visibility
属性设置为hidden
。
向一个方向切换
您可以向toggle
方法传递一个boolean
参数来限制可见性切换的方式。如果您将true
作为参数传递,那么将只显示隐藏的元素(可见的元素不会被隐藏)。如果您将false
作为参数传递,那么您会得到相反的效果。可见元素将被隐藏,但隐藏的元素将不可见。清单 10-3 显示了这种风格的toggle
方法的使用。清单 10-3 中的脚本创建了如图 10-3 所示的效果。
清单 10-3 。在一个方向上使用切换方法
...
<script type="text/javascript">
$(document).ready(function() {
$("<button>Toggle</button>").appendTo("#buttonDiv")
.click(function(e) {
$("div.dcell:first-child").toggle(false);
e.preventDefault();
});
});
</script>
...
制作元素可见性的动画
您可以通过向show
、hide
或toggle
方法传递一个时间跨度来动画显示和隐藏元素的过程。然后,在指定的时间段内,逐渐执行显示和隐藏元素的过程。表 10-3 显示了你可以使用的不同时间跨度参数。
表 10-3 。时间跨度参数
方法 | 描述 |
---|---|
<number> | 规定的 |
slow | 相当于 600 毫秒的简写 |
fast | 相当于 200 毫秒的速记 |
清单 10-4 展示了如何动画显示和隐藏元素。
清单 10-4 。动画元素的可见性
...
<script type="text/javascript">
$(document).ready(function() {
$("<button>Toggle</button>").appendTo("#buttonDiv")
.click(function(e) {
$("img").toggle("fast", "linear");
e.preventDefault();
});
});
</script>
...
在清单 10-4 中,我使用了fast
值来指定切换文档中img
元素的可见性应该在 600 毫秒内完成。
提示当指定以毫秒为单位的持续时间时,确保该值没有被引用。也就是用$("img").toggle(500)
而不用$("img").toggle("500")
。如果您使用引号,那么该值将被忽略。
我还提供了一个额外的参数,它指定了动画的样式,称为缓动样式或缓动函数。有两种缓动样式可用,swing
和linear
。使用swing
风格制作动画时,动画开始缓慢,加速,然后在动画结束时再次减速。linear
风格在整个动画中保持不变的节奏。如果省略参数,则使用swing
。你可以看到动画隐藏图 10-4 中元素的效果。用这种方式展示动画很难,但是你会感觉到发生了什么。
图 10-4 。动画隐藏元素
如图所示,动画效果在两个维度上缩小了图像的大小,并降低了不透明度。动画结束时,img
元素不可见。图 10-5 显示了如果再次点击Toggle
按钮使img
元素可见会发生什么。
图 10-5 。动画显示元素
使用效果回调
您可以提供一个函数作为show
、hide
和toggle
方法的参数,当这些方法完成它们的效果时,这个函数将被调用。这对于更新其他元素以反映状态的变化很有用,如清单 10-5 所示。
清单 10-5 。使用事件回调
...
<script type="text/javascript">
$(document).ready(function() {
var hiddenRow = "#row2";
var visibleRow = "#row1";
$(hiddenRow).hide();
function switchRowVariables() {
var temp = hiddenRow;
hiddenRow = visibleRow;
visibleRow = temp;
}
function hideVisibleElement() {
$(visibleRow).hide("fast", showHiddenElement);
}
function showHiddenElement() {
$(hiddenRow).show("fast", switchRowVariables);
}
$("<button>Switch</button>").insertAfter("#buttonDiv button")
.click(function(e) {
hideVisibleElement();
e.preventDefault();
});
});
</script>
...
提示如果你想在一个元素上执行多个连续效果,那么你可以使用常规的 jQuery 方法链接。有关详细信息,请参见“创建和管理效果队列”一节。
为了让清单 10-5 更清晰,我将效果活动分解成了独立的函数。为了进行设置,我隐藏了一个在 CSS 表格布局中充当一行的div
元素,并定义了两个变量来跟踪哪一行可见,哪一行不可见。我在文档中添加了一个button
元素,当这个元素被点击时,我调用hideVisibleElement
函数,它使用hide
方法来显示隐藏可见行的动画。
...
$(visibleRow).hide("fast",showHiddenElement);
...
当效果完成时,我指定我想要执行的功能的名称,在这个例子中是showHiddenElement
。
提示回调函数没有传递任何参数,但是this
变量被设置为被动画化的 DOM 元素。如果多个元素被动画化,那么回调函数将为每个元素调用一次。
此函数使用 show 方法显示元素的动画,如下所示:
...
$(hiddenRow).show("fast",switchRowVariables);
...
我再次指定了一个在效果结束时执行的函数。在这种情况下,是switchRowVariables
函数,它打乱了跟踪可见性的变量,以便您在下次单击button
时对正确的元素执行效果。结果是,当单击按钮时,当前行被隐藏的行替换,并有一个快速的动画来减少过渡对用户的干扰。图 10-6 显示了这个效果(尽管,同样,只有当你在浏览器中加载这个例子时,真正的效果才变得明显)。
图 10-6 。使用回调函数链接效果
你通常不需要像我这样分解单个函数,所以清单 10-6 显示了使用一组更简洁的内联函数重写的相同例子。
清单 10-6 。使用内联回调函数
...
<script type="text/javascript">
$(document).ready(function() {
var hiddenRow = "#row2";
var visibleRow = "#row1";
$(hiddenRow).hide();
$("<button>Switch</button>").insertAfter("#buttonDiv button")
.click(function(e) {
$(visibleRow).hide("fast", function() {
$(hiddenRow).show("fast", function() {
var temp = hiddenRow;
hiddenRow = visibleRow;
visibleRow = temp;
});
});
e.preventDefault();
});
});
</script>
...
创建循环效果
您可以使用回调函数产生在循环中执行的效果。清单 10-7 展示了。
清单 10-7 。使用回调函数创建循环效果
...
<script type="text/javascript">
$(document).ready(function() {
$("<button>Toggle</button>").insertAfter("#buttonDiv button")
.click(function(e) {
performEffect();
e.preventDefault();
});
function performEffect() {
$("h1").toggle("slow", performEffect)
}
});
</script>
...
在本例中,点击按钮导致执行performEffect
功能。这个函数使用toggle
方法来改变文档中h1
元素的可见性,并将自己作为回调参数传递。结果是h1
元素在可见和隐藏之间循环,如图图 10-7 所示。
图 10-7 。在循环中执行效果
提示当使用当前函数作为回调函数时需要小心。最终,您将耗尽 JavaScript 调用堆栈,您的脚本将停止工作。解决这个问题最简单的方法是使用setTimeout
函数,它将调度对目标函数的回调,而不嵌套函数调用,就像这样:$("h1").toggle("slow", setTimeout( performEffect, 1))
。耗尽调用堆栈实际上是相当困难的,这通常意味着让动画页面运行很长时间,但这一点值得记住——然而,在使用finish
方法时需要小心,我在“停止效果和清除队列”一节中对此进行了描述
使用效果负责
在我看来,像这样的循环应该谨慎使用,并且只在它们服务于某个目的时使用(我指的是为用户服务的目的,而不是炫耀您出色的 jQuery 效果技能)。一般来说,任何一种效应的影响都应该仔细考虑。它在开发过程中可能看起来很棒,但是不明智地使用效果会破坏用户对 web 应用的享受,特别是如果这是一个他或她每天都在使用的应用。
举个简单的例子,我是一个热衷跑步的人(热衷但没有任何好处)。我曾经有一个跑步者的手表,它收集关于我的心率、速度、距离、燃烧的卡路里和 100 个其他数据点的数据。在运行结束时,我会将数据上传到制造商的网站进行存储和分析。
这是疼痛开始的地方。每当我点击页面上的一个按钮,我想要的内容就会通过一个长长的效果显示出来。我知道浏览器已经收到了我想要的数据,因为我可以看到它被逐渐显示出来,但在我可以读取它之前还有几秒钟。几秒钟听起来可能不是很多,但确实是很多,尤其是当我想随时查看五到十个不同的数据项时。
我确信应用的设计者认为效果很好,并且增强了体验。但是他们没有。它们太糟糕了,以至于使用该应用成为一种令人咬牙切齿的体验——以至于从这本书的上一版开始,我就购买了竞争对手的产品。
这款手表(现已废弃)的网络应用有一些有用的数据分析工具,但它让我如此恼火,以至于我愿意支付数百美元来更换。如果没有这些影响(也许还有我发现自己以惊人的频率消费的啤酒和披萨),我现在可能是马拉松冠军。
如果你认为我夸大了效果。。。你可以相信我关于比萨饼的话),然后选择本章中的一个列表,将时间跨度设置为两秒。然后感受一下你等待效果完成的时间有多长。
我的建议是,所有的效果都应该少用。我倾向于只在对 DOM 进行不和谐的更改时使用它们(元素突然从页面中消失)。当我使用它们时,我保持时间跨度很短,通常是 200 毫秒。我从不使用无限循环。这只是让用户头疼的一个原因。我敦促你花时间去思考你的用户是如何参与到你的应用或网站中的,并去掉那些不会让手头的任务更容易执行的东西。有光泽的网站是好的,但有用的有光泽的网站是伟大的。
使用幻灯片效果
jQuery 有一组在屏幕上滑动元素的效果。表 10-4 描述了这种方法。
表 10-4 。幻灯片效果方法
方法 | 描述 |
---|---|
slideDown()``slideDown((time, function) | 通过向下滑动来显示元素 |
slideUp()``slideUp(time, function) | 通过向上滑动来隐藏元素 |
slideToggle()``slideToggle(time, function) | 通过上下滑动来切换元素的可见性 |
这些方法使垂直轴上的元素具有动画效果。这些方法的论据是关于基本效果的。您可以选择提供一个时间跨度、一种放松方式和一个回调函数。清单 10-8 显示了使用中的幻灯片效果。
清单 10-8 。使用幻灯片效果
...
<script type="text/javascript">
$(document).ready(function() {
$("<button>Toggle</button>").insertAfter("#buttonDiv button")
.click(function(e) {
$("h1").slideToggle("fast");
e.preventDefault();
});
});
</script>
...
在这个脚本中,我使用slideToggle
方法来切换h1
元素的可见性。你可以在图 10-8 中看到效果。
图 10-8 。使用幻灯片效果显示元素
该图显示了可见的h1
元素。元素被剪裁,而不是缩放,因为 jQuery 通过操纵元素的高度来创建效果。你可以在图 10-9 中看到我的意思。
图 10-9 。jQuery 通过操纵元素的高度来创建效果
该图显示了h1
元素可见时的特写。您可以看到文本的大小没有改变,只是显示的数量有所变化。然而,这对于图像来说是不正确的,因为浏览器会自动缩放它们。如果你仔细看,你可以看到整个背景图像总是显示,但它被缩小以适应高度。
使用渐变效果
渐变效果方法通过降低元素的不透明度(或者,如果您喜欢,增加其透明度)来显示和隐藏元素。表 10-5 描述了渐变效果的方法。
表 10-5 。渐变效果方法
方法 | 描述 |
---|---|
fadeOut()``fadeOut(timespan)``fadeOut(timespan, function) | 通过降低不透明度来隐藏元素 |
fadeIn()``fadeIn(timespan)``fadeIn(timespan, function) | 通过增加不透明度来显示元素 |
fadeTo(timespan, opacity) fadeTo(timespan, opacity, easing, function) | 将不透明度更改为指定的级别 |
fadeToggle()``fadeToggle(timespan)``fadeToggle(timespan, function) | 使用不透明度切换元素的可见性 |
fadeOut
、fadeIn
、fadeToggle
方法与其他效果方法一致。您可以提供时间跨度、放松方式和回调函数,就像前面的清单一样。清单 10-9 演示了如何使用淡入淡出。
清单 10-9 。通过淡化显示和隐藏元素
...
<script type="text/javascript">
$(document).ready(function() {
$("<button>Toggle</button>").insertAfter("#buttonDiv button")
.click(function(e) {
$("img").fadeToggle();
e.preventDefault();
});
});
</script>
...
我将fadeToggle
方法应用于文档中的img
元素,部分是为了演示这种效果的局限性之一。图 10-10 显示了当你隐藏元素时会发生什么。
图 10-10 。使用渐变效果
淡入淡出效果只对不透明度起作用,不像其他效果那样也会改变所选元素的大小。这意味着在元素完全透明之前,您会得到一个很好的平滑淡入淡出效果,此时 jQuery 会隐藏它们,并且页面会捕捉到新的布局。如果不小心使用,这最后一个阶段可能会有些不和谐。
渐隐到特定的不透明度
您可以使用fadeTo
方法将元素渐变到特定的不透明度。不透明度值的范围是在0
(完全透明)到1
(完全不透明)之间的数字。元素的可见性没有改变,所以避免了我提到的页面布局的快照。清单 10-10 展示了fadeTo
方法的使用。
清单 10-10 。渐隐到特定的不透明度
...
<script type="text/javascript">
$(document).ready(function() {
$("<button>Fade</button>").insertAfter("#buttonDiv button")
.click(function(e) {
$("img").fadeTo("fast", 0);
e.preventDefault();
});
});
</script>
...
在这个例子中,我已经指定了img
元素应该被淡化,直到它们完全透明。这与fadeOut
方法有相同的效果,但是不会在过渡结束时隐藏元素。图 10-11 显示效果。
图 10-11 。用 fadeTo 方法淡出元素
你不必将元素淡化到不透明度范围的极端。你也可以指定中间值,如清单 10-11 所示。
清单 10-11 。渐隐到特定的不透明度
...
<script type="text/javascript">
$(document).ready(function() {
$("<button>Fade</button>").insertAfter("#buttonDiv button")
.click(function(e) {
$("img").fadeTo("fast", 0.4);
e.preventDefault();
});
});
</script>
...
你可以在图 10-12 中看到效果。
图 10-12 。渐隐到特定的不透明度
创建自定义效果
jQuery 不会将您局限于基本的滑动和渐变效果。你也可以创造你自己的。表 10-6 显示了你在这个过程中使用的方法。
表 10-6 。自定义特效方法
方法 | 描述 |
---|---|
animate(properties)``animate(properties, time)``animate(properties, time, function) | 动画显示一个或多个 CSS 属性,具有可选的时间跨度、缓动样式和回调函数 |
animate(properties, options) | 动画显示一个或多个 CSS 属性,将选项指定为贴图 |
jQuery 可以动态显示任何接受简单数值的属性(例如,height
属性)。
注意能够动画显示数字 CSS 属性意味着你不能动画显示颜色。有几种方法可以解决这个问题。第一个(也是我认为最好的)解决方案是使用 jQuery UI,我在本书的第四部分对此进行了描述。如果您不想使用 jQuery UI,那么您可能会考虑使用原生浏览器对 CSS 动画的支持。这些浏览器的性能相当好,但是目前的支持是不完整的,并且在旧版本的浏览器中不存在。关于 CSS 动画的细节,请参见我的书《HTML5 的权威指南》,这本书也是由 Apress 出版的。我最不喜欢的方法是使用 jQuery 插件。制作颜色动画很难,我还没有找到一个我完全满意的插件,但是我找到的最可靠的插件是从https://github.com/jquery/jquery-color
获得的。
您可以提供一组要作为地图对象制作动画的属性,如果需要,您可以对要设置的选项进行同样的操作。清单 10-12 显示了一个自定义动画。
清单 10-12 。使用自定义动画
...
<script type="text/javascript">
$(document).ready(function() {
$("form").css({"position": "fixed", "top": "70px", "z-index": "2"});
$("h1").css({"position": "fixed", "z-index": "1", "min-width": "0"});
$("<button>Animate</button>").insertAfter("#buttonDiv button")
.click(function(e) {
$("h1").animate({
height: $("h1").height() + $("form").height() + 10,
width: ($("form").width())
});
e.preventDefault();
});
});
</script>
...
在清单 10-12 的中,我想要动画显示h1
元素的尺寸,这样它的背景图像就会延伸到form
元素的后面。在我这样做之前,我需要对受影响的元素的 CSS(层叠样式表)进行一些更改。我可以使用我在第三章中定义的样式表来做这件事,但是因为这是一本关于 jQuery 的书,所以我选择使用 JavaScript。为了使动画更容易管理,我使用fixed
模式定位了form
和h1
元素,并使用了z-index
属性来确保h1
元素显示在form
的下方。
我在文档中添加了一个button
,当单击它时调用animate
方法。我选择使用通过其他 jQuery 方法获得的信息来制作height
和width
属性的动画。你可以在图 10-13 中看到动画的效果。
图 10-13 。执行自定义动画
我在图中只显示了开始和结束状态,但是 jQuery 提供了平滑的过渡,就像其他效果一样,您可以通过指定时间跨度和放松样式来控制过渡。
使用绝对目标属性值
请注意,您只为动画指定了最终值。jQuery 自定义动画的起点是正在制作动画的属性的当前值。我使用了从其他 jQuery 方法中获得的值,但是您还有其他选择。首先,也是最明显的,你可以使用绝对值,如清单 10-13 所示。
清单 10-13 。使用绝对值执行自定义动画
...
<script type="text/javascript">
$(document).ready(function() {
$("form").css({"position": "fixed", "top": "70px", "z-index": "2"});
$("h1").css({"position": "fixed", "z-index": "1", "min-width": "0"});
$("<button>Animate</button>").insertAfter("#buttonDiv button")
.click(function(e) {
$("h1").animate({
left: 50,
height: $("h1").height() + $("form").height() + 10,
width: ($("form").width())
});
e.preventDefault();
});
});
</script>
...
在清单 10-13 的中,我给动画添加了left
属性,指定了50
的绝对值(将被视为 50 像素)。这将把h1
元素向右移动。图 10-14 显示了动画的结果。
图 10-14 。使用固定的最终属性值创建自定义动画
使用相对目标属性值
也可以使用相对值指定动画目标。您可以通过在值前加上前缀+=
来指定增加,并在值前加上前缀-=
来指定减少。清单 10-14 展示了相对值的使用。
清单 10-14 。在自定义动画效果中使用相对值
...
<script type="text/javascript">
$(document).ready(function() {
$("form").css({"position": "fixed", "top": "70px", "z-index": "2"});
$("h1").css({"position": "fixed", "z-index": "1", "min-width": "0"});
$("<button>Animate</button>").insertAfter("#buttonDiv button")
.click(function(e) {
$("h1").animate({
height: "+=100",
width: "-=700"
});
e.preventDefault();
});
});
</script>
...
创建和管理效果队列
当您使用效果时,jQuery 会创建一个它必须执行的动画队列,并以自己的方式处理它们。有一组方法可以用来获取队列信息或控制队列,如表 10-7 中所述。
表 10-7 。效果队列方法
方法 | 描述 |
---|---|
queue() | 返回将在 jQuery 对象中的元素上执行的效果队列 |
queue(function) | 将函数添加到队列的末尾 |
dequeue() | 删除并执行队列中的第一个项目,该项目对应于 jQuery 对象中的元素 |
stop()``stop(clear) | 停止当前动画 |
finish() | 停止当前动画并清除任何排队的动画 |
delay(time) | 在队列中的效果之间插入延迟 |
你可以通过链接对效果相关方法的调用来创建一个效果队列,如清单 10-15 所示。
清单 10-15 。创建效果队列
...
<script type="text/javascript">
$(document).ready(function() {
$("form").css({"position": "fixed", "top": "70px", "z-index": "2"});
$("h1").css({"position": "fixed", "z-index": "1", "min-width": "0"});
var timespan = "slow";
cycleEffects();
function cycleEffects() {
$("h1")
.animate({left: "+=100"}, timespan)
.animate({left: "-=100"}, timespan)
.animate({height: 223,width: 700}, timespan)
.animate({height: 30,width: 500}, timespan)
.slideUp(timespan)
.slideDown(timespan, cycleEffects);
}
});
</script>
...
清单 10-15 中的脚本使用常规的 jQuery 方法链接将h1
元素上的一系列效果串在一起。最后一个效果使用cycleEffects
函数 作为回调,再次开始这个过程。这是一个相当恼人的序列。它有一会儿催眠作用,然后有一点刺激,然后就变成了引起头痛的那种效果。但是它确实创建了一个效果队列,这是我演示队列特性所需要的。
注意我本可以使用回调函数来达到同样的效果,但是这并没有创建效果队列,因为启动下一个动画的函数直到前一个动画完成后才被执行。当您使用常规方法链接时,如本例所示,所有的方法都被计算,动画效果被放入队列中。使用方法链接的限制是您只能使用当前选择。使用回调时,您可以将涉及完全不相关元素的序列串在一起。
显示效果队列中的项目
您可以使用queue
方法来检查效果队列的内容。这并不完全有帮助,因为队列包含两种类型的数据对象之一。如果一个效果正在被执行,那么队列中相应的项就是字符串值inprogress
。如果效果没有被执行,队列中的项是将被调用的函数(尽管该函数没有透露将执行哪个动画函数)。也就是说,检查内容是从队列开始的好地方,清单 10-16 展示了如何做到这一点。
清单 10-16 。检查效果队列的内容
...
<script type="text/javascript">
$(document).ready(function () {
$("h1").css({ "position": "fixed", "z-index": "1", "min-width": "0" });
$("form").remove();
$("<table border=1></table>")
.appendTo("body").css({
position: "fixed", "z-index": "2",
"border-collapse": "collapse", top: 100
});
var timespan = "slow";
cycleEffects();
printQueue();
function cycleEffects() {
$("h1")
.animate({ left: "+=100" }, timespan)
.animate({ left: "-=100" }, timespan)
.animate({ height: 223, width: 700 }, timespan)
.animate({ height: 30, width: 500 }, timespan)
.slideUp(timespan)
.slideDown(timespan, cycleEffects);
}
function printQueue() {
var q = $("h1").queue();
var qtable = $("table");
qtable.html("<tr><th>Queue Length:</th><td>" + q.length + "</td></tr>");
for (var i = 0; i < q.length; i++) {
var baseString = "<tr><th>" + i + ":</th><td>";
if (q[i] == "inprogress") {
$("table").append(baseString + "In Progress</td></tr>");
} else {
$("table").append(baseString + q[i] + "</td></tr>");
}
}
setTimeout(printQueue, 500);
}
});
</script>
...
在这个例子中我不需要form
元素,所以我已经将它从 DOM 中移除,并用一个简单的table
来替换它,我将用它来显示效果队列的内容。我添加了一个名为printQueue
的重复函数,该函数调用queue
方法,并在table
中显示项目的数量和每个项目的一些细节。正如我所说的,队列中的项目本身并不是特别有用,但是它们确实让您对正在发生的事情有了一个总体的了解。图 10-15 显示了 jQuery 如何在效果队列中前进。
图 10-15 。检查队列的内容
清单 10-16 很难用静态图像来描绘。我建议您将文档加载到浏览器中自己查看。当第一次调用cycleEffects
函数时,效果队列中有六个项目,其中第一个显示为正在进行中。其他的是管理动画的匿名函数的实例。每个效果完成后,jQuery 会从队列中删除该项目。最后一个效果结束时,再次调用cycleEffects
函数,再次将 6 个项目放入队列。
停止效果并清除队列
您可以使用stop
和finish
方法来中断 jQuery 当前正在执行的效果。对于stop
方法,您可以为此方法提供两个可选参数,这两个参数都是boolean
值。如果您将true
作为第一个参数传递,那么所有其他效果都将从队列中移除,并且不会被执行。如果您将true
作为第二个参数传递,那么被当前动画激活的 CSS 属性将立即被设置为它们的最终值。
两个参数的缺省值都是false
,这意味着只有当前的效果从队列中移除,而正在被动画化的属性保持在效果被中断时的设置值。如果不清除队列,jQuery 将继续处理下一个效果,并开始正常执行。清单 10-17 提供了一个使用stop
方法的例子。
清单 10-17 。使用停止方法
...
<script type="text/javascript">
$(document).ready(function () {
$("h1").css({ "position": "fixed", "z-index": "1", "min-width": "0" });
$("form").remove();
$("<table border=1></table>")
.appendTo("body").css({
position: "fixed", "z-index": "2",
"border-collapse": "collapse", top: 100
});
$("<button>Stop</button><button>Start</button>")
.appendTo($("<div/>").appendTo("body")
.css({
position: "fixed", "z-index": "2",
"border-collapse": "collapse", top: 100, left: 200
})).click(function (e) {
$(this).text() == "Stop" ? $("h1").stop(true, true) : cycleEffects();
});
var timespan = "slow";
cycleEffects();
printQueue();
function cycleEffects() {
$("h1")
.animate({ left: "+=100" }, timespan)
.animate({ left: "-=100" }, timespan)
.animate({ height: 223, width: 700 }, timespan)
.animate({ height: 30, width: 500 }, timespan)
.slideUp(timespan)
.slideDown(timespan, cycleEffects);
}
function printQueue() {
var q = $("h1").queue();
var qtable = $("table");
qtable.html("<tr><th>Queue Length:</th><td>" + q.length + "</td></tr>");
for (var i = 0; i < q.length; i++) {
var baseString = "<tr><th>" + i + ":</th><td>";
if (q[i] == "inprogress") {
$("table").append(baseString + "In Progress</td></tr>");
} else {
$("table").append(baseString + q[i] + "</td></tr>");
}
}
setTimeout(printQueue, 500);
}
});
</script>
...
提示当你调用stop
方法时,任何与当前效果相关的回调都不会被执行。当您使用stop
方法清除队列时,不会执行与队列中任何效果相关联的回调。
为了演示stop
方法,我在文档中添加了两个按钮。当点击Stop
按钮时,我调用stop
方法,传入两个true
参数。这样做的效果是清除效果队列的其余部分,并将元素与正在被动画化的属性的目标值对齐。由于使用stop
方法时没有调用回调函数,因此cycleEffects
方法调用的循环被中断,动画暂停。当点击Start
按钮时,调用cycleEffects
方法,动画恢复。
提示在动画运行时点击Start
按钮不会混淆 jQuery。它只是将由cycleEffects
方法使用的效果添加到效果队列中。回调的使用意味着队列的大小会有一点跳跃,但是就动画而言,一切都照常进行。
finish
方法 具有与调用stop(true, true)
相似的效果,但是在处理被动画化的 CSS 属性的方式上有所不同。当调用stop(true, true)
时,被当前效果激活的 CSS 属性跳转到它们的最终值,但是当使用finish
方法时,被当前效果激活的 CSS 属性和所有排队的效果跳转到它们的最终值。你可以在清单 10-18 中看到我是如何应用finish
方法的。
清单 10-18 。使用完成方法
...
<script type="text/javascript">
$(document).ready(function () {
$("h1").css({ "position": "fixed", "z-index": "1", "min-width": "0" });
$("form").remove();
$("<table border=1></table>")
.appendTo("body").css({
position: "fixed", "z-index": "2",
"border-collapse": "collapse", top: 100
});
var finishAnimations = false;
$("<button>Stop</button><button>Start</button>")
.appendTo($("<div/>").appendTo("body")
.css({
position: "fixed", "z-index": "2",
"border-collapse": "collapse", top: 100, left: 200
})).click(function (e) {
if ($(this).text() == "Stop") {
finishAnimations = true;
$("h1").finish();
} else {
finishAnimations = false;
cycleEffects();
}
});
var timespan = "slow";
cycleEffects();
printQueue();
function cycleEffects() {
$("h1")
.animate({ left: "+=100" }, timespan)
.animate({ left: "-=100" }, timespan)
.animate({ height: 223, width: 700 }, timespan)
.animate({ height: 30, width: 500 }, timespan)
.slideUp(timespan)
.slideDown(timespan, function () {
if (!finishAnimations) {
cycleEffects();
}
});
}
function printQueue() {
var q = $("h1").queue();
var qtable = $("table");
qtable.html("<tr><th>Queue Length:</th><td>" + q.length + "</td></tr>");
for (var i = 0; i < q.length; i++) {
var baseString = "<tr><th>" + i + ":</th><td>";
if (q[i] == "inprogress") {
$("table").append(baseString + "In Progress</td></tr>");
} else {
$("table").append(baseString + q[i] + "</td></tr>");
}
}
setTimeout(printQueue, 500);
}
});
</script>
...
当使用finish
方法时,必须小心效果循环,比如清单 10-18 中的那个。为了确定所有动画属性的最终 CSS 值,finish
方法需要执行所有的效果——尽管没有任何时间延迟——这意味着任何回调函数也要执行。
在清单 10-18 中,cycleEffects
函数中定义的最后一个效果设置下一个效果循环,如下所示:
...
.slideDown(timespan, cycleEffects);
...
finish
方法不会阻止新的效果被添加到队列中,并且当它被调用时,它不会跟踪队列的状态。这意味着finish
方法将调用cycleEffects
方法,该方法将效果添加到队列中,然后finish
方法执行该方法,触发回调,该回调将效果添加到队列中。。。等等。总体效果是立即耗尽 JavaScript 调用堆栈。
为了避免这种情况,我添加了一个名为finishAnimations
的变量,我设置这个变量来响应被点击的button
元素,并在将下一组效果添加到队列之前进行检查,如下所示:
...
.slideDown(timespan, function () {
if (!finishAnimations) {
cycleEffects();
}
});
...
在队列中插入延迟
您可以使用delay
方法 在队列中的两个效果之间引入暂停。此方法的参数是延迟应该持续的毫秒数。清单 10-19 展示了使用这种方法在效果队列中引入一秒钟的延迟。
清单 10-19 。使用延迟方法
...
function cycleEffects() {
$("h1")
.animate({ left: "+=100" }, timespan)
.animate({ left: "-=100" }, timespan)
.delay(1000)
.animate({ height: 223, width: 700 }, timespan)
.animate({ height: 30, width: 500 }, timespan)
.delay(1000)
.slideUp(timespan)
.slideDown(timespan, function () {
if (!finishAnimations) {
cycleEffects();
}
});
}
...
将函数插入队列
您可以使用queue
方法将自己的函数添加到队列中,它们将像标准效果方法一样被执行。您可以使用此功能启动其他动画,根据外部变量优雅地退出动画链,或者做任何您需要的事情。清单 10-20 包含了一个例子。
清单 10-20 。将自定义函数插入队列
...
function cycleEffects() {
$("h1")
.animate({ left: "+=100" }, timespan)
.animate({ left: "-=100" }, timespan)
.queue(function () {
$("body").fadeTo(timespan, 0).fadeTo(timespan, 1);
$(this).dequeue();
})
.delay(1000)
.animate({ height: 223, width: 700 }, timespan)
.animate({ height: 30, width: 500 }, timespan)
.delay(1000)
.slideUp(timespan)
.slideDown(timespan, function () {
if (!finishAnimations) {
cycleEffects();
}
});
}
...
this
变量被设置为调用该方法的jQuery
对象。这很有用,因为你必须确保在函数中的某个点调用dequeue
方法,以便将队列移动到下一个效果或函数。在这个例子中,我使用了queue
方法来添加一个函数,该函数将body
元素渐变为完全透明,然后返回。
提示我在自定义函数中添加的效果被添加到body
元素的效果队列中。每个元素都有自己的队列,您可以独立地管理它们。如果您想在您正在操作的队列的相同元素上制作多个属性的动画,那么您只需使用animate
方法。否则,你的效果将会按顺序添加到队列中。
或者,您可以接受该函数的单个参数,它是队列中的下一个函数。在这种情况下,你必须调用函数将队列移动到下一个效果,如清单 10-21 所示。
清单 10-21 。使用传递给自定义函数的参数
...
function cycleEffects() {
$("h1")
.animate({ left: "+=100" }, timespan)
.animate({ left: "-=100" }, timespan)
.queue(function (nextFunction) {
$("body").fadeTo(timespan, 0).fadeTo(timespan, 1);
nextFunction();
})
.delay(1000)
.animate({ height: 223, width: 700 }, timespan)
.animate({ height: 30, width: 500 }, timespan)
.delay(1000)
.slideUp(timespan)
.slideDown(timespan, function () {
if (!finishAnimations) {
cycleEffects();
}
});
}
...
注意如果你不调用下一个函数或者调用dequeue
方法,效果序列将会停止。
启用和禁用效果动画
您可以通过将$.fx.off
属性的值设置为true
来禁用效果的动画,如清单 10-22 所示。
清单 10-22 。禁用动画
...
<script type="text/javascript">
$(document).ready(function () {
$.fx.off = true;
$("h1").css({ "position": "fixed", "z-index": "1", "min-width": "0" });
$("form").remove();
// ...
*statements omitted for brevity*...
});
</script>
...
当动画被禁用时,对 effect 方法的调用会导致元素立即与它们的目标属性值对齐。时间跨度被忽略,并且没有中间动画。当动画被禁用时,循环效果集将很快达到调用堆栈限制。为了避免这种情况,使用setTimeout
方法,如本章前面所述。
摘要
在本章中,我向您展示了如何使用 jQuery effect 特性。内置的效果方法主要是让元素以不同的方式可见和不可见,但您可以超越这一点,将任何数字 CSS 属性动画化。您还可以深入效果队列,对应用于元素的效果序列进行更多控制。在第十一章的中,我重构了示例文档,向您展示如何将本章和前面章节中涉及的一些基本 jQuery 特性结合起来。
十一、重构示例:第一部分
在前面的章节中,我单独展示了每个功能区域:如何处理事件,如何操作 DOM(域对象模型)等等。当您组合这些特性时,jQuery 的真正威力和灵活性就会显现出来,在这一章中,我将通过重构花店示例文档来演示这种组合。
我在这一章中所做的所有修改都在script
元素中。我没有改变示例文档的底层 HTML。与大多数 jQuery 特性一样,有许多不同的途径可以达到相同的结果。我在本章中采用的方法反映了我最喜欢的 jQuery 部分以及我倾向于考虑 DOM 的方式。你可能有不同的思维模式,喜欢不同的方法组合。这真的无关紧要,而且没有唯一正确的 jQuery 使用方法。
查看示例文档
我从一个简单的示例文档开始写这本书,一个基本的花店页面。在接下来的章节中,我使用 jQuery 从文档中选择元素,探索并重新排列它的 DOM,监听事件,并对元素应用效果。在我开始重构这个例子之前,让我们回顾一下我开始的地方。清单 11-1 显示了基本文档。
清单 11-1 。基本示例文档
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
<script type="text/javascript">
$(document).ready(function() {
// jQuery statements will go here
});
</script>
</head>
<body>
<h1>Jacqui's Flower Shop</h1>
<form method="post">
<div id="oblock">
<div class="dtable">
<div id="row1" class="drow">
<div class="dcell">
<img src="aster.png"/><label for="aster">Aster:</label>
<input name="aster" value="0" required />
</div>
<div class="dcell">
<img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
<input name="daffodil" value="0" required />
</div>
<div class="dcell">
<img src="rose.png"/><label for="rose">Rose:</label>
<input name="rose" value="0" required />
</div>
</div>
<div id="row2"class="drow">
<div class="dcell">
<img src="peony.png"/><label for="peony">Peony:</label>
<input name="peony" value="0" required />
</div>
<div class="dcell">
<img src="primula.png"/><label for="primula">Primula:</label>
<input name="primula" value="0" required />
</div>
<div class="dcell">
<img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
<input name="snowdrop" value="0" required />
</div>
</div>
</div>
</div>
<div id="buttonDiv"><button type="submit">Place Order</button></div>
</form>
</body>
</html>
我强调了script
元素,因为这是你在本书中花费时间的地方。我为ready
事件添加了无处不在的 jQuery 处理程序,但仅此而已。没有其他 JavaScript 语句。你可以在图 11-1 中看到未经修饰的文档是如何出现在浏览器中的。
图 11-1 。基本示例文档
添加额外的花卉产品
我做的第一个改变是给商店增加一些额外的花。我想这样做是为了演示如何在一个循环中创建元素。清单 11-2 显示了添加的script
元素。
清单 11-2 。向页面添加产品
...
<script type="text/javascript">
$(document).ready(function() {
var fNames = ["Carnation", "Lily", "Orchid"];
var fRow = $("<div id=row3 class=drow/>").appendTo("div.dtable");
var fTemplate = $("<div class=dcell><img/><label/><input/></div>");
for (var i = 0; i < fNames.length; i++) {
fTemplate.clone().appendTo(fRow).children()
.filter("img").attr("src", fNames[i] + ".png").end()
.filter("label").attr("for", fNames[i]).text(fNames[i]).end()
.filter("input").attr({name: fNames[i], value: 0, required: "required"})
}
});
</script>
...
我已经定义了另外三种类型的花(Carnation
、Lily
和Orchid
),并创建了一个新的div
元素,它被分配给了drow
类,我将它附加到现有的div
元素上,该元素在 CSS(级联样式表)表格布局模型中充当表格。
...
var fNames = ["Carnation", "Lily", "Orchid"];
var fRow = $("<div id=row3 class=drow/>").appendTo("div.dtable");
...
然后我定义了一个元素的框架集;这些描述了我想要的每个产品的元素结构,但不包含任何区分一朵花和另一朵花的属性。
...
var fTemplate = $("<div class=dcell><img/><label/><input/></div>");
...
我使用骨骼元素作为一个简单的模板,为我想要添加的每朵花克隆它们,并使用花的名称来添加属性和值。
...
for (var i = 0; i < fNames.length; i++) {
fTemplate.clone().appendTo(fRow).children()
.filter("img").attr("src", fNames[i] + ".png").end()
.filter("label").attr("for", fNames[i]).text(fNames[i]).end()
.filter("input").attr({name: fNames[i], value: 0, required: "required"})
}
...
我使用filter
和end
方法来缩小和扩大选择范围,使用attr
方法来设置属性值。最后,我为每朵新的花填充了一组完整的元素,插入到行级的div
元素中,然后再插入到表级的元素中。你可以在图 11-2 中看到效果。
图 11-2 。向页面添加新的花朵
本例中一个很好的 jQuery 特性是,您可以选择和导航没有附加到主文档的元素。当我克隆模板元素时,它们不是文档的一部分,但是我仍然可以使用children
和filter
方法来缩小选择范围。
添加旋转按钮
我将创建一个简单的旋转木马,让用户通过花集页面。首先,我需要分页的左右按钮。清单 11-3 显示了我如何将它们添加到文档中。
清单 11-3 。添加转盘按钮
...
<script type="text/javascript">
$(document).ready(function() {
var fNames = ["Carnation", "Lily", "Orchid"];
var fRow = $("<div id=row3 class=drow/>").appendTo("div.dtable");
var fTemplate = $("<div class=dcell><img/><label/><input/></div>");
for (var i = 0; i < fNames.length; i++) {
fTemplate.clone().appendTo(fRow).children()
.filter("img").attr("src", fNames[i] + ".png").end()
.filter("label").attr("for", fNames[i]).text(fNames[i]).end()
.filter("input").attr({name: fNames[i], value: 0, required: "required"})
}
$("<a id=left></a><a id=right></a>").prependTo("form")
.css({
"background-image": "url(leftarrows.png)",
"float": "left",
"margin-top": "15px",
display: "block", width: 50, height: 50
}).click(handleArrowPress).hover(handleArrowMouse)
$("#right").css("background-image", "url(rightarrows.png)").appendTo("form");
$("#oblock").css({float: "left", display: "inline", border: "thin black solid"});
$("form").css({"margin-left": "auto", "margin-right": "auto", width: 885});
function handleArrowMouse(e) {
}
function handleArrowPress(e) {
}
});
</script>
...
我定义了一对a
元素,将它们添加到form
元素的前面,并使用css
方法为许多不同的属性应用值。
...
$("<a id=left></a><a id=right></a>").prependTo("form")
.css({
"background-image": "url(leftarrows.png)",
"float": "left",
"margin-top": "15px",
display: "block",width: 50, height: 50
}).click(handleArrowPress).hover(handleArrowMouse)
...
关键属性是background-image
,我设置为leftarrows.png
。你可以在图 11-3 中看到这个图像。
图 11-3 。leftarrows.png 形象
该图像在组合图像中包含三个不同的箭头。每个单独的箭头都是 50 像素宽,通过将width
和height
属性设置为50
,我确保在任何时候都只有一个单独的箭头显示。我使用click
和hover
方法来定义click
、mouseenter
和mouseexit
事件的处理函数。
...
$("<a id=left></a><a id=right></a>").prependTo("form")
.css({
"background-image": "url(leftarrows.png)",
"float": "left",
"margin-top": "15px",
display: "block", width: 50, height: 50
}).click(handleArrowPress).hover(handleArrowMouse)
...
handleArrowPress
和handleArrowMouse
函数是空的,但是我将在稍后填充它们。此时,我有两个a
元素,它们都显示向左的箭头,并且在form
元素中彼此相邻。我一起创建并格式化了a
元素,因为大部分配置都是通用的,但是现在是时候移动并定制正确的按钮了,我的做法如下:
...
$("#right").css("background-image", "url(rightarrows.png)").appendTo("form");
...
我使用append
方法将元素移动到form
元素的末尾,并使用css
方法将background-image
属性更改为使用rightarrows.png
。你可以在图 11-4 中看到这个图像。
图 11-4 。rightarrows.png 形象
像这样使用组合图像是一种常见的技术,因为它避免了浏览器向服务器发出三个不同的请求来获取三个密切相关的图像的开销。当我很快填充handleArrowMouse
函数时,您将看到如何使用这种图像。你可以在图 11-5 中看到页面的样子。
图 11-5 。示例文档的中间状态
处理提交按钮
从图 11-5 可以看出,我的例子处于中间状态。新的特性出现了,但是我还没有恰当地处理一些现有的元素。其中最重要的是提交表单的 Place Order 按钮。清单 11-4 显示了脚本中处理这个元素的附加内容(并添加了一个新特性)。
清单 11-4 。处理提交按钮的
...
<script type="text/javascript">
$(document).ready(function() {
var fNames = ["Carnation", "Lily", "Orchid"];
var fRow = $("<div id=row3 class=drow/>").appendTo("div.dtable");
var fTemplate = $("<div class=dcell><img/><label/><input/></div>");
for (var i = 0; i < fNames.length; i++) {
fTemplate.clone().appendTo(fRow).children()
.filter("img").attr("src", fNames[i] + ".png").end()
.filter("label").attr("for", fNames[i]).text(fNames[i]).end()
.filter("input").attr({name: fNames[i], value: 0, required: "required"})
}
$("<a id=left></a><a id=right></a>").prependTo("form")
.css({
"background-image": "url(leftarrows.png)",
"float": "left",
"margin-top": "15px",
display: "block", width: 50, height: 50
}).click(handleArrowPress).hover(handleArrowMouse)
$("#right").css("background-image", "url(rightarrows.png)").appendTo("form");
$("h1").css({"min-width": "0", width: "95%",});
$("#row2, #row3").hide();
$("#oblock").css({float: "left", display: "inline", border: "thin black solid"});
$("form").css({"margin-left": "auto", "margin-right": "auto", width: 885});
var total = $("#buttonDiv")
.prepend("<div>Total Items: <span id=total>0</span></div>")
.css({clear: "both", padding: "5px"});
$("<div id=bbox />").appendTo("body").append(total).css("clear: left");
function handleArrowMouse(e) {
}
function handleArrowPress(e) {
}
});
</script>
...
为了适应轮播按钮引起的布局变化,我将包含button
元素的div
(它有一个buttonDiv
的id
)移动到一个新的div
元素中,这个元素又被追加到了body
元素中。这会将按钮移动到返回页面底部的位置。我还添加了一个div
和一个span
元素。这些将用于显示用户选择的产品总数。
...
var total = $("#buttonDiv")
.prepend("<div>Total Items: <span id=total>0</span></div>")
.css({clear: "both", padding: "5px"});
$("<div id=bbox />").appendTo("body").append(total).css("clear: left");
...
这一部分的下一个变化是隐藏两行产品。这样,当用户单击轮播按钮时,您就可以向用户展示它们。
...
$("#row2, #row3").hide();
...
我还调整了h1
元素的样式,以匹配修改后的布局样式。
...
$("h1").css({"min-width": "0", width: "95%",});
...
你可以在图 11-6 中看到这些变化的效果。
图 11-6 。处理提交按钮和整理 CSS
实现轮播事件处理函数
下一步是实现处理转盘按钮事件的函数。我将处理由handleArrowMouse
函数处理的mouseenter
和mouseexit
事件。清单 11-5 显示了这个函数的实现。
清单 11-5 。处理箭头按钮鼠标事件
...
function handleArrowMouse(e) {
var propValue = e.type == "mouseenter" ? "-50px 0px" : "0px 0px";
$(this).css("background-position", propValue);
}
...
处理组合图像的技巧是使用background-position
属性来移动图像,这样只有我想要的部分可见。虽然在我的箭头组中有三个图像,但我将只使用其中的两个。正常情况下会显示最暗的图像,当鼠标悬停在元素上时会显示中间的图像。你可以用剩下的箭头来表示一个按钮被点击或者被禁用,但是我想保持简单。你可以在图 11-7 中看到图像代表的两种状态。
图 11-7 。箭头按钮的两种状态
handleArrowPress
函数负责创建旋转木马效果,允许用户翻阅一排排鲜花。清单 11-6 显示了这个函数的实现。
清单 11-6 。实现 handleArrowPress 功能
...
function handleArrowPress(e) {
var elemSequence = ["row1", "row2", "row3"];
var visibleRow = $("div.drow:visible");
var visibleRowIndex = jQuery.inArray(visibleRow.attr("id"), elemSequence);
var targetRowIndex;
if (e.target.id == "left") {
targetRowIndex = visibleRowIndex - 1;
if (targetRowIndex < 0) {targetRowIndex = elemSequence.length -1};
} else {
targetRowIndex = (visibleRowIndex + 1) % elemSequence.length;
}
visibleRow.fadeOut("fast", function() {
$("#" + elemSequence[targetRowIndex]).fadeIn("fast")});
}
...
该函数中的前三条语句设置了您需要的基本数据。
...
var elemSequence = ["row1", "row2", "row3"];
var visibleRow = $("div.drow:visible");
var visibleRowIndex = jQuery.inArray(visibleRow.attr("id"), elemSequence);
...
第一条语句为行元素定义了一组id
属性值。第二条语句使用 jQuery 来获取可见行,然后我使用它来确定行id
值数组中可见行的索引。(我使用了inArray
效用方法,我在第三十四章中解释过。)所以,我知道哪一行是可见的,以及我在行序列中的位置。我的下一步是找出下一个将要显示的行的索引。
...
var targetRowIndex;
if (e.target.id == "left") {
targetRowIndex = visibleRowIndex - 1;
if (targetRowIndex < 0) {targetRowIndex = elemSequence.length -1};
} else {
targetRowIndex = (visibleRowIndex + 1) % elemSequence.length;
}
...
在几乎任何其他编程语言中,我都可以使用模操作符来计算出要显示的下一行的索引,但是模数学的 JavaScript 实现不能正确地支持负值。所以,如果用户点击左键,我手动检查数组边界;如果用户点击了右边的按钮,我就使用%
操作符。一旦我确定了当前可见的元素和接下来要显示的元素,我就使用 jQuery effects 来制作从一个元素到另一个元素的动画。
...
visibleRow.fadeOut("fast", function() {
$("#" + elemSequence[targetRowIndex]).fadeIn("fast")});
...
我使用了fadeOut
和fadeIn
方法,因为它们很适合我的 CSS 表格样式布局。我在第一个效果中使用回调来触发第二个效果,并使用fast
时间跨度来执行这两个效果。页面的静态布局没有变化,但是箭头按钮现在将用户从一行花带到下一行花,如图图 11-8 所示。
图 11-8 。提供产品行的传送带
合计产品选择
最后一个更改是连接项目 total,以便在各个输入字段中选择的鲜花总数显示在产品传送带下。清单 11-7 显示了对脚本的修改。
清单 11-7 。为产品总计布线
...
<script type="text/javascript">
$(document).ready(function() {
var fNames = ["Carnation", "Lily", "Orchid"];
var fRow = $("<div id=row3 class=drow/>").appendTo("div.dtable");
var fTemplate = $("<div class=dcell><img/><label/><input/></div>");
for (var i = 0; i < fNames.length; i++) {
fTemplate.clone().appendTo(fRow).children()
.filter("img").attr("src", fNames[i] + ".png").end()
.filter("label").attr("for", fNames[i]).text(fNames[i]).end()
.filter("input").attr({name: fNames[i], value: 0, required: "required"})
}
$("<a id=left></a><a id=right></a>").prependTo("form")
.css({
"background-image": "url(leftarrows.png)",
"float": "left",
"margin-top": "15px",
display: "block", width: 50, height: 50
}).click(handleArrowPress).hover(handleArrowMouse)
$("#right").css("background-image", "url(rightarrows.png)").appendTo("form");
$("h1").css({"min-width": "0", width: "95%",});
$("#row2, #row3").hide();
$("#oblock").css({float: "left", display: "inline", border: "thin black solid"});
$("form").css({"margin-left": "auto", "margin-right": "auto", width: 885});
var total = $("#buttonDiv")
.prepend("<div>Total Items: <span id=total>0</span></div>")
.css({clear: "both", padding: "5px"});
$("<div id=bbox />").appendTo("body").append(total).css("clear: left");
$("input").change(function(e) {
var total = 0;
$("input").each(function(index, elem) {
total += Number($(elem).val());
});
$("#total").text(total);
});
function handleArrowMouse(e) {
var propValue = e.type == "mouseenter" ? "-50px 0px" : "0px 0px";
$(this).css("background-position", propValue);
}
function handleArrowPress(e) {
var elemSequence = ["row1", "row2", "row3"];
var visibleRow = $("div.drow:visible");
var visibleRowIndex = jQuery.inArray(visibleRow.attr("id"), elemSequence);
var targetRowIndex;
if (e.target.id == "left") {
targetRowIndex = visibleRowIndex - 1;
if (targetRowIndex < 0) {targetRowIndex = elemSequence.length -1};
} else {
targetRowIndex = (visibleRowIndex + 1) % elemSequence.length;
}
visibleRow.fadeOut("fast", function() {
$("#" + elemSequence[targetRowIndex]).fadeIn("fast")});
}
});
</script>
...
在这次添加中,我选择了文档中的input
元素,并注册了一个处理函数,该函数从每个元素中获取值,对其求和,并将其设置为我之前添加的span
元素的内容。你可以在图 11-9 中看到效果。
图 11-9 。显示产品选择总数
总计显示了所有input
元素的总和,而不仅仅是当前可见的元素(尽管使用其他方法也很简单)。
禁用 JavaScript
我对示例文档做了一些彻底的修改,但都是用 jQuery 做的。这意味着我已经有效地创建了两层文档,一层用于支持 JavaScript 的浏览器,一层用于不支持 JavaScript 的浏览器,图 11-10 显示了当你禁用 JavaScript 并查看示例文档时会发生什么。
图 11-10 。禁用 JavaScript 并查看示例文档
我又回到了起点。通过一点规划和预先考虑,我可以为非 JavaScript 客户机提供一组功能,让它们仍然可以与您的页面或应用进行交互。这通常是一个好主意;有许多大公司集中管理 IT(信息技术),并禁用 JavaScript 作为安全预防措施。(嗯,算是吧。在为这样的组织工作多年后,我开始相信这些政策实际上并没有阻止员工使用 JavaScript 他们只是创造了寻找漏洞和变通办法的动机。)
摘要
在本章中,我向您展示了如何结合前几章的技术来重构示例文档。我以编程方式添加了新内容,创建了一个简单的产品转盘,并创建了一个显示所选项目的总数的汇总。在这个过程中,我调整了 DOM 和 CSS 以适应这些变化,所有这些都是为了让非 JavaScript 浏览器退回到仍然有用的文档。
在本书的下一部分,我将继续构建这个例子,引入更多的 jQuery 特性来充实功能。在大多数情况下,我会将这些应用到原始的示例文档中,以便依次关注每个特性,但是在第十六章中,我将再次重构示例以引入更多的特性。
十二、使用数据模板
在本书的前一版本中,我使用了 jQuery Templates 插件介绍了数据模板。这个插件的历史相当奇怪。微软和 jQuery 团队宣布,微软开发的三个插件已经被接受为“官方”插件,这是任何其他插件都没有的地位。过了一会儿,jQuery 团队宣布这些插件已被废弃,官方状态已被移除,并计划用其他功能替换它们。模板插件的替换将作为 jQuery UI 的一部分创建(我将在本书的第四部分描述)。
那是不久前的事了,新的官方 jQuery 模板引擎还没有完成,它被命名为 jsViews 。有一个测试版本,但它有粗糙的边缘,仍然不稳定。我不是一个粉丝,但是你可以在https://github.com/BorisMoore/jsviews
了解更多信息并获得该库的最新测试版。
与此同时,我在上一版本中使用的不推荐使用的 jQuery Templates 包并没有很好地老化。不久前,我在自己的项目中停止使用它,选择了一个叫做Handlebars
的替代品,它可以从http://handlebarsjs.com
获得。它没有附带任何 jQuery 集成——事实上,它根本不是一个 jQuery 插件——但是很容易通过 jQuery 语法编写少量代码来支持模板,我将向您展示这是如何实现的。
手柄——就像之前的 jQuery 模板插件一样——支持小胡子模板,它们被表示为包含特殊小胡子指令的 HTML。我解释了这些是什么,但是术语小胡子的使用来自于这样一个事实,即它们是用括号字符({
和}
)来表示的,看起来有点像侧面的小胡子。表 12-1 提供了本章的总结。
表 12-1 。章节总结
问题 | 解决办法 | 列表 |
---|---|---|
使用模板生成元素。 | 安装 Handlebars 库,创建一个 jQuery 插件,并使用template 方法。 | 1–6 |
将从模板生成的元素分配给不同的父元素。 | 或者拆分源数据并应用模板两次,或者使用 slice filter 和 end 方法分割生成的元素。 | 7–10 |
如果定义了数据属性并且不为空,则更改模板的输出。 | 使用内置的#if 或#unless 模板助手。 | 11, 12 |
枚举数组的内容或对象的属性。 | 使用#each 模板助手。 | 13, 14 |
引用模板中数据对象的另一部分。 | 使用#with 模板助手,或者使用../ 路径。 | 15–17 |
创建自定义模板助手。 | 使用Handlebars.registerHelper 方法注册助手的名称和返回模板内容的助手函数。 | 18–22 |
在模板帮助函数中接收可选参数。 | 使用options.hash 属性。 | 23, 24 |
定义可以在自定义模板帮助器的块中使用的特殊属性。 | 使用options.data 属性。 | 25, 26 |
了解模板解决的问题
数据模板解决了一个特定的问题:它们允许您以编程方式从 JavaScript 对象的属性和值生成元素。这是你可以用其他方式做的事情,事实上,我在第十一章中做了类似的事情,我在示例文档中创建了一些元素来表示额外的花。清单 12-1 显示了该章的相关陈述。
清单 12-1 。以编程方式创建元素
...
<script type="text/javascript">
$(document).ready(function() {
var fNames = ["Carnation", "Lily", "Orchid"];
var fRow = $("<div id=row3 class=drow/>").appendTo("div.dtable");
var fTemplate = $("<div class=dcell><img/><label/><input/></div>");
for (var i = 0; i < fNames.length; i++) {
fTemplate.clone().appendTo(fRow).children()
.filter("img").attr("src", fNames[i] + ".png").end()
.filter("label").attr("for", fNames[i]).text(fNames[i]).end()
.filter("input").attr({name: fNames[i], value: 0, required: "required"})
}
});
</script>
...
清单中的语句很难阅读,对于更复杂的元素,难度会急剧增加。正如我将解释的那样,数据模板很方便地将重点放回到 HTML 上,并最大限度地减少了从数据生成元素所需的代码量。
从更广的角度来看,将数据集成到文档中是一个需要解决的常见问题。在我的项目中,它通过两种情况出现。第一个原因是因为我正在使用一些预先存在的系统,这些系统包含驱动我的 web 应用的数据。我可以获得数据,并在服务器上将其集成到文档中——有一些很好的技术可以做到这一点——但这意味着我的服务器群要花很多时间做我可以让浏览器为我做的工作。如果您曾经构建和运行过一个大容量的 web 应用,您就会知道成本是巨大的,并且任何减少所需处理量的机会都会被认真考虑。
我需要将数据集成到文档中的第二个原因是,我的 web 应用通过 Ajax 获取数据来响应用户操作。我将在第十四章和第十五章中全面解释 jQuery 对 Ajax 的支持,但简单来说,你可以从服务器获取并显示数据,而无需在浏览器中重新加载整个页面。这是一种广泛使用的强大技术,数据模板与它配合得很好。
设置模板库
在使用模板之前,您必须下载模板库并在文档中链接到它。您可以从http://handlebarsjs.com
下载这个库,我将 JavaScript 代码保存到一个名为handlebars.js
的文件中,与 jQuery JavaScript 文件放在一起。
handlebars 库与 jQuery 没有任何集成,但是创建一个允许我使用 jQuery 语法生成模板的 jQuery 插件很简单。我创建了一个名为handlebars-jquery.js
的文件,并用它来定义清单 12-2 中的代码。
清单 12-2 。改编 handlerbars.js 以使用 handler bars-jQuery . js 文件中的 jQuery 语法
(function ($) {
var compiled = {};
$.fn.template = function (data) {
var template = $.trim($(this).first().html());
if (compiled[template] == undefined) {
compiled[template] = Handlebars.compile(template);
}
return $(compiledtemplate);
};
})(jQuery);
创建自定义 JQUERY 插件
正如清单 12-2 所示,为 jQuery 创建插件很简单,尤其是如果你只是在一个已有库的基础上创建一个包装器。我没有在本书中描述定制插件是如何工作的,因为这是很少有开发者需要做的事情,但是你可以在http://learn.jquery.com/plugins
获得完整的细节。
这段代码定义了一个名为template
的方法,可以在 jQuery 对象上调用该方法,使用 handlebars 将模板应用于数据对象。结果是一个 jQuery 对象,包含从模板生成的 HTML 元素。为了生成模板,我需要为示例文档中的handlebars.js
和handlebars-jquery.js
文件添加script
元素,如清单 12-3 所示。
清单 12-3 。将库添加到示例文档中
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<script src="handlebars.js" type="text/javascript"></script>
<script src="handlebars-jquery.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
<script type="text/javascript">
$(document).ready(function () {
// ...
*example code will go here*...
});
</script>
</head>
<body>
<h1>Jacqui's Flower Shop</h1>
<form method="post">
<div id="oblock">
<div class="dtable">
<div id="row1" class="drow"></div>
<div id="row2"class="drow"></div>
</div>
</div>
<div id="buttonDiv"><button type="submit">Place Order</button></div>
</form>
</body>
</html>
我将使用这个清单作为本章的示例文档。除了添加模板库和我的简单插件,您会注意到我已经删除了描述单朵花的元素——我这样做是为了探索使用模板添加它们的不同技术。你可以在图 12-1 中看到这个初始的 HTML 文档是如何出现在浏览器中的。
图 12-1 。起始示例文档
第一数据模板示例
开始学习数据模板的最好方法是直接进入。清单 12-4 展示了基本的模板特性。我在这个清单中包含了完整的 HTML 文档,因为模板是用一个script
元素表示的,但是我将只展示后续清单中需要的更改。
清单 12-4 。第一数据模板示例
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<script src="handlebars.js" type="text/javascript"></script>
<script src="handlebars-jquery.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#each flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" data-price="{{price}}" data-stock="{{stock}}"
value="0" required />
</div>
{{/each}}
</script>
<script type="text/javascript">
$(document).ready(function () {
var data = {
flowers: [
{ name: "Aster", product: "aster", stock: "10", price: 2.99 },
{ name: "Daffodil", product: "daffodil", stock: "12", price: 1.99 },
{ name: "Rose", product: "rose", stock: "2", price: 4.99 },
{ name: "Peony", product: "peony", stock: "0", price: 1.50 },
{ name: "Primula", product: "primula", stock: "1", price: 3.12 },
{ name: "Snowdrop", product: "snowdrop", stock: "15", price: 0.99 }]
};
var template = $("#flowerTmpl").template(data).appendTo("#row1");
});
</script>
</head>
<body>
<h1>Jacqui's Flower Shop</h1>
<form method="post">
<div id="oblock">
<div class="dtable">
<div id="row1" class="drow"></div>
<div id="row2"class="drow"></div>
</div>
</div>
<div id="buttonDiv"><button type="submit">Place Order</button></div>
</form>
</body>
</html>
在接下来的小节中,我将分解这个例子并解释每一部分。
提示当数据是文档的一部分时,称为内联数据。另一种方法是远程数据,这种方法可以从独立于文档的服务器上获取数据。我将在本章后面演示远程数据,但它涉及到了 Ajax 的 jQuery 支持,这是第十四章和第十五章的主题。
定义数据
该示例的起点是数据,在本例中是一个具有单个属性的对象,该属性被设置为一个对象数组。每个对象描述一个花卉产品,清单 12-5 显示了示例 HTML 文档中的相关语句。
清单 12-5 。定义花卉数据
...
var data = {
flowers: [
{ name: "Aster", product: "aster", stock: "10", price: 2.99 },
{ name: "Daffodil", product: "daffodil", stock: "12", price: 1.99 },
{ name: "Rose", product: "rose", stock: "2", price: 4.99 },
{ name: "Peony", product: "peony", stock: "0", price: 1.50 },
{ name: "Primula", product: "primula", stock: "1", price: 3.12 },
{ name: "Snowdrop", product: "snowdrop", stock: "15", price: 0.99 }]
};
...
手柄模板作用于对象和属性,这就是为什么我必须在 flower 对象数组周围有一个对象包装器。对于这个示例,数组包含六个对象,每个对象都有一组描述花店产品的属性:显示名称、产品名称、库存水平和价格。
定义模板
正如您所想象的,数据模板库的核心是数据模板。这是一组包含占位符的 HTML 元素,这些占位符对应于数据对象的各个方面。清单 12-6 显示了这个例子的模板。
清单 12-6 。定义数据模板
...
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" data-price="{{price}}" data-stock="{{stock}}"
value="0" required />
</div>
{{/flowers}}
</script>
...
关于模板要注意的第一点是,它包含在具有text/x-handlebars-template
的type
属性的script
元素中。这是为了阻止浏览器解释模板的内容。第二点需要注意的是,我已经使用id
属性为我的脚本元素指定了一个名称:在这个例子中,模板名为flowerTmpl
。这个名称很重要,因为我将需要它来将模板应用于数据。
模板的内容将应用于数据数组中的对象,以便为每个对象生成一组 HTML 元素。您可以看到模板的结构与我在前几章中用于花卉产品的元素集相对应。
当然,关键的区别是我在清单中强调的部分。这些是双小胡子指令(之所以这么叫是因为用来表示它们的括号字符看起来像小胡子——因此有了术语小胡子模板)。在这个例子中有两种类型的指令(我将在本章后面介绍更多的类型)。
第一种指令是一个部分,它定义了一个模板区域,该区域将为数据对象中具有相同名称的每个属性值生成。Section 指令以一个#
字符开始({{#flowers}}
,在本例中),以一个/
字符结束({{/flowers}}
)。我使用的 section 指令将为分配给flowers
属性的每个对象生成它所包含的模板部分。
另一种类型的指令是一个变量,它将被数据对象中相应的属性值替换。例如,当模板库遇到{{product}}
变量时,它用正在处理的对象的product
属性的值替换它,这意味着模板的一部分是这样的:
...
<img src="{{product}}.png"/>
...
转化成了这样:
...
<img src="aster.png"/>
...
应用模板
我使用清单 12-2 中的 jQuery 插件中定义的template
方法将模板应用于数据。下面是示例 HTML 文档中对template
方法的调用:
...
var template = $("#flowerTmpl").template(data).appendTo("#row1");
...
我使用 jQuery $
函数选择包含模板的script
元素,然后调用结果jQuery
对象上的template
方法,传入我想要处理的数据。
template
方法返回一个标准的jQuery
对象,其中包含从模板中产生的元素。在这种情况下,我得到了一组div
元素,每个元素包含一个img
、label
和input
元素,它们是为我的数据数组中的一个对象定制的。我使用appendTo
方法将整个集合作为子元素插入到row1
元素中。你可以在图 12-2 中看到结果。
图 12-2 。使用数据模板
调整结果
我没有得到我想要的结果——所有的花都在一行,而不是像前几章那样分成两行。但是,因为我正在处理一个 jQuery 对象,所以我可以使用第二部分中描述的技术来分割元素。清单 12-7 展示了如何通过对template
方法返回的jQuery
对象进行操作来做到这一点。
清单 12-7 。处理来自模板的结果
...
<script type="text/javascript">
$(document).ready(function () {
var data = {
flowers: [
{ name: "Aster", product: "aster", stock: "10", price: 2.99 },
{ name: "Daffodil", product: "daffodil", stock: "12", price: 1.99 },
{ name: "Rose", product: "rose", stock: "2", price: 4.99 },
{ name: "Peony", product: "peony", stock: "0", price: 1.50 },
{ name: "Primula", product: "primula", stock: "1", price: 3.12 },
{ name: "Snowdrop", product: "snowdrop", stock: "15", price: 0.99 }]
};
$("#flowerTmpl").template(data)
.slice(0, 3).appendTo("#row1").end().end().slice(3).appendTo("#row2")
});
</script>
...
在这个例子中,我使用slice
和end
方法来缩小和扩大选择,使用appendTo
方法将从模板生成的元素子集添加到不同的行。
请注意,我必须连续调用两次end
方法来消除由slide
和appendTo
方法引起的收缩。你可以在图 12-3 中看到效果——我更接近了,但是我仍然没有得到我想要的结果。
图 12-3 。试图调整从数据生成的 HTML 的布局
问题是 Handlebars 在我的模板内容被拆分成多行的地方向它生成的 HTML 添加了文本节点——由于我对slice
方法的使用适用于模板生成的所有元素(包括文本节点),所以我在错误的地方拆分了内容。
有几种方法可以解决这个问题。第一种方法是调整模板,使所有的内容都在一行上——但是我不喜欢这种方法,我更喜欢让模板尽可能的易读。
另一种方法是调整传递给slice
方法的索引,以考虑文本节点——但我也不喜欢这样,因为不是所有的文本编辑器都以创建文本节点的方式表示新行,这意味着用不同的编辑器编辑 HTML 文件会改变 JavaScript 代码的行为,这远非理想。
我的首选方法是在将模板添加到 DOM 之前,使用 jQuery 从模板生成的 HTML 中删除文本节点。不幸的是,jQuery 没有包含一个有用的方法来完成这个任务,所以最好的方法是使用带有*
选择器的filter
方法,它匹配所有的 HTML 标签类型,但是不包括文本节点。您可以在清单 12-8 的中看到我的 jQuery 中添加了 filter 方法。
清单 12-8 。使用 filter 方法删除文本节点
...
$("#flowerTmpl").template(data).filter("*")
.slice(0, 3).appendTo("#row1").end().end().slice(3).appendTo("#row2")
...
你可以在图 12-4 中看到结果:花被正确地分成两行。
图 12-4 。使用 filter 方法删除文本节点
我仍然不满意我处理模板生成的 HTML 的方式。我通常喜欢使用end
方法来创建单语句操作,但是我发现end().end()
序列令人讨厌。相反,我通常会将这些步骤分解成单独的操作,如清单 12-9 所示,它产生的结果与清单 12-8 相同,但是更容易阅读。
清单 12-9 。使用多条语句拆分元素
...
var templateHtml = $("#flowerTmpl").template(data).filter("*");
templateHtml.slice(0, 3).appendTo("#row1");
templateHtml.slice(3).appendTo("#row2");
...
调整输入
另一种方法是调整传递给template
方法的数据。清单 12-10 展示了如何做到这一点。
清单 12-10 。使用数据调整模板的输出
...
<script type="text/javascript">
$(document).ready(function () {
var data = {
flowers: [
{ name: "Aster", product: "aster", stock: "10", price: 2.99 },
{ name: "Daffodil", product: "daffodil", stock: "12", price: 1.99 },
{ name: "Rose", product: "rose", stock: "2", price: 4.99 },
{ name: "Peony", product: "peony", stock: "0", price: 1.50 },
{ name: "Primula", product: "primula", stock: "1", price: 3.12 },
{ name: "Snowdrop", product: "snowdrop", stock: "15", price: 0.99 }]
};
var tElem = $("#flowerTmpl");
tElem.template({ flowers: data.flowers.slice(0, 3) }).appendTo("#row1");
tElem.template({ flowers: data.flowers.slice(3) }).appendTo("#row2");
});
</script>
...
在这个脚本中,我通过两次使用模板来解决将花分配给行的问题——每行一次。我使用了split
方法,这样我每次都可以向模板提供一系列数据对象。手法不同,但结果是一样的,如图图 12-4 。请注意,我必须注意保留我传递给template
方法的对象的形状,以便它匹配我的节声明——我必须确保该对象有一个名为flowers
的属性,该属性被设置为我要处理的数据对象的数组。
使用模板逻辑
区分各种 JavaScript 模板引擎的一种方法是查看模板的输出如何根据不同的数据值而变化。
一个极端是无逻辑模板 ,它不包含任何逻辑,改变模板的输出意味着在使用模板引擎之前仔细准备数据。另一个极端是全逻辑模板 ,这就像有一个简单的编程语言专用于定义和执行模板,内置对条件语句、循环、数组处理和管理数据集合的支持。
对于模板中应该包含多少逻辑,人们意见不一,整个话题都是有争议的。我喜欢处于中间的某个位置——我喜欢能够在我的模板中使用逻辑,但是我希望保持简单,并且不需要在我的 HTML 文档中添加另一种语言。我为这一章选择 Handlebars 库的原因之一是,它允许你在一个模板中使用尽可能少或尽可能多的逻辑——而且,正如你将在这一章的后面看到的,它使定义定制逻辑来解决特定问题变得容易。手柄库包含一些内置的助手 ,在表 12-2 中描述,它们是简单的逻辑操作符,可以用来根据数据值改变模板的输出。
表 12-2 。内置车把助手
助手 | 描述 |
---|---|
#if | 一个if / then / else 条件,如果指定的属性存在并且不是null ,则计算结果为true 。 |
#unless | #if 辅助者的逆;如果指定的属性不存在或者是null ,则计算结果为true 。 |
#each | 迭代对象数组或对象的属性。 |
#with | 为模板的一部分设置上下文。 |
创建条件内容
为了演示如何在 Handlebars 模板中使用逻辑,我将根据数据对象的flowers
数组中每个对象的stock
属性的值来设置模板中input
元素的value
属性。我的目标是当相应的股票属性大于零时,将value
设置为1
。通过应用清单 12-11 中的#if
助手,你可以看到我是如何做到这一点的。
清单 12-11 。使用模板逻辑改变模板的输出
...
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" data-price="{{price}}" data-stock="{{stock}}"
value="{{#if stock}}1{{else}}0{{/if}}"required />
</div>
{{/flowers}}
</script>
<script type="text/javascript">
$(document).ready(function () {
var data = {
flowers: [
{ name: "Aster", product: "aster", stock: "10", price: 2.99 },
{ name: "Daffodil", product: "daffodil", stock: "12", price: 1.99 },
{ name: "Rose", product: "rose", stock: "2", price: 4.99 },
{ name: "Peony", product: "peony", stock: "0", price: 1.50 },
{ name: "Primula", product: "primula", stock: "1", price: 3.12 },
{ name: "Snowdrop", product: "snowdrop", stock: "15", price: 0.99 }]
};
for (var i = 0; i < data.flowers.length; i++) {
if (data.flowers[i].stock == 0) {
data.flowers[i].stock = null;
}
}
var tElem = $("#flowerTmpl");
tElem.template({ flowers: data.flowers.slice(0, 3) }).appendTo("#row1");
tElem.template({ flowers: data.flowers.slice(3) }).appendTo("#row2");
});
</script>
...
助手的每一部分都用双胡子表示:第一部分由#if
组成,后面是我想要检查的属性,在本例中是stock
。如果由#flowers
段指令处理的当前对象定义了一个stock
属性并且该属性不是null
,那么这将被评估为true
。如果是这种情况,模板引擎将把条件的第一部分之后的值插入到 HTML 中,在本例中是1
。
可选的else
部分的工作方式就像在 JavaScript 中一样,它允许我提供一个替代值,如果当前对象没有stock
属性或(如果有stock
属性并且是null
),那么这个替代值将被使用。在这种情况下,模板引擎会将0
插入到模板生成的 HTML 中。最后一段是/if
,表示条件块的结束。
#if
助手背后的逻辑是基本的,根据属性是否存在和是否被定义返回true
,迫使我在将数据传递给template
方法之前处理数据。我使用一个 JavaScript for
循环来枚举 flower 对象,并设置任何值为0
到null
的stock
属性。结果是,从模板生成的所有input
元素的值都是1
,除了Peony
的元素,如图 12-5 所示。
图 12-5 。使用模板中的逻辑来改变生成的 HTML
提示我不喜欢处理数据来适应模板引擎的限制,在本章的后面,我将向您展示如何创建不需要数据处理的定制逻辑。
#unless
辅助对象的工作方式与#if
辅助对象相同,但是如果它所应用的属性不存在或者为空,那么它将被评估为true
。在清单 12-12 中,您可以看到我是如何在没有可选的else
指令的情况下,将#if
和#unless
助手一起应用于设置模板中input
元素的value
属性的。
清单 12-12 。在模板中应用#if 和#unless 助手
...
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" data-price="{{price}}" data-stock="{{stock}}"
value="{{#if stock}}1{{/if}}{{#unless stock}}0{{/unless}}"required />
</div>
{{/flowers}}
</script>
...
#if
和#unless
助手彼此完全独立,但是以这种方式使用它们展示了如何在不使用else
指令的情况下测试属性的存在或不存在。该清单产生的结果与清单 12-11 中的相同,如图 12-5 中的所示。
枚举数组和属性
我在清单 12-12 中使用的 section 指令是 Handlebars 库支持更广泛使用的 mustache 模板的一部分,而#each
助手是一个更复杂的替代工具,它提供了一些可以在模板中使用的特殊属性。这些属性在表 12-3 中描述。
表 12-3 。#each Helper 提供的特殊属性
名字 | 描述 |
---|---|
this | 返回正在处理的对象。 |
@index | 当在数组上使用#each 辅助对象时,返回当前对象的索引。 |
@key | 当用于对象时,返回当前属性的名称。 |
在清单 12-13 中,您可以看到我是如何在现有数据上使用#each
助手以及@index
属性的。
清单 12-13 。使用#each 助手和@index 属性
...
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#each flowers}}
<div class="dcell">
<label>Position: {{@index}}</label>
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" data-price="{{price}}" data-stock="{{stock}}"
value="{{#if stock}}1{{/if}}{{#unless stock}}0{{/unless}}" required />
</div>
{{/each}}
</script>
...
我把要枚举的对象的源指定为#each
助手的参数,在这种情况下是传递给template
方法的数据对象的flowers
属性的值。你可以在图 12-6 中看到结果(索引为每一行重新开始,因为我将数据分成两部分并调用模板方法两次,如清单 12-11 所示)。
图 12-6 。使用#each 助手和@index 属性
当将对象传递给template
方法而不是数组时,this
和@key
属性很有用。Handlebars 库将枚举对象的属性——@key
属性用于获取当前属性名,this
属性用于获取当前值。您可以在清单 12-14 中看到这两个属性一起使用。
清单 12-14 。用#each 助手枚举对象的属性
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<script src="handlebars.js" type="text/javascript"></script>
<script src="handlebars-jquery.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
<script id="flowerListTmpl" type="text/x-handlebars-template">
<ul>
{{#each stockData}}
<li>{{@key}} ({{this}} in stock)</li>
{{/each}}
</ul>
</script>
<script type="text/javascript">
$(document).ready(function () {
var data = {
stockData: {
Aster: 10, Daffodil: 12, Rose: 2,
Peony: 0, Primula: 1, Snowdrop: 15
}
};
$("#flowerListTmpl").template(data).appendTo("form");
});
</script>
</head>
<body>
<h1>Jacqui's Flower Shop</h1>
<form method="post">
</form>
</body>
</html>
我用一个更简单的结构替换了这个例子中的数据——data.stockData
属性返回一个对象,该对象的属性名描述鲜花,其值描述库存数量。对于模板,我在stockData
属性上使用了#each
助手(记住,我必须将一个对象传递给template
方法,并将助手和指令应用到它的属性上)。本例中的模板创建了一个列表。我用this
属性获取每个属性的值,用@key
属性获取属性的名称,你可以在图 12-7 中看到结果。
图 12-7 。将@键和 this 属性与#each 帮助器一起使用
更改数据上下文
数据上下文 是助手和变量应用到的数据对象的一部分。当模板处理开始时,上下文是整个数据对象,但它会被 helpers 和 section 指令更改,以使编写模板更容易,如我在上一个示例中使用的模板所示:
...
{{#each stockData}}
<li>{{@key}} ({{this}} in stock)</li>
{{/each}}
...
#each
助手依次将上下文转移到数据对象的每个属性——这意味着在#each
助手块中定义的变量和助手都将相对于当前对象进行计算。你可以在清单 12-15 中更清楚地看到这一点,在那里我更改了数据和模板以使上下文的效果更清晰。
清单 12-15 。强调模板中上下文的作用
...
<script id="flowerListTmpl" type="text/x-handlebars-template">
<ul>
<h3>{{title}}</h3>
{{#each stockData}}
<li>{{description.Name}} ({{description.Stock}} in stock)</li>
{{/each}}
</ul>
</script>
<script type="text/javascript">
$(document).ready(function () {
var data = {
title: "Stock List",
stockData: {
aster: {
description: { Name: "Aster", Stock: 10 }
},
daffodil: {
description: { Name: "Daffodil", Stock: 12 }
},
rose: {
description: { Name: "Rose", Stock: 2 }
}
}
};
$("#flowerListTmpl").template(data).appendTo("form");
});
</script>
...
我已经为数据对象添加了一个title
属性,并为单朵花添加了结构。模板的第一个指令依赖于默认的数据上下文,即整个数据对象:
...
<h3>{{title}}</h3>
...
您可以理解我所说的与上下文相关的被评估的指令的意思——我只需要指定属性名,而不需要任何限定来从数据对象中获取属性的值。模板中的下一条指令改变了上下文:
...
{{#each stockData}}
...
#each
帮助器枚举stockData
属性返回的对象的属性,并依次将上下文更改为每个属性值。您可以在模板的下一行看到效果:
...
<li>{{description.Name}} ({{description.Stock}} in stock)</li>
...
我访问相对于当前上下文的Name
和Stock
属性——这意味着使用一个路径来浏览description
对象,遵循数据对象的结构。结果如图 12-8 中的所示。
图 12-8 。访问与上下文相关的属性
使用#with 助手
在前面的例子中,我必须指定description.Name
和description.Stock
来访问模板的属性值。#with
助手可以通过改变它包含的所有指令的数据上下文来消除复制公共属性名的需要,如清单 12-16 所示。
清单 12-16 。使用#with Helper 更改数据上下文
...
<script id="flowerListTmpl" type="text/x-handlebars-template">
<ul>
<h3>{{title}}</h3>
{{#each stockData}}
{{#with description}}
<li>{{Name}} ({{Stock}} in stock)</li>
{{/with}}
{{/each}}
</ul>
</script>
...
在#with
块的范围内,上下文被缩小到description
属性。当结合由#each
助手所做的数据上下文的改变时,我能够访问每个花对象的Name
和Stock
属性,而不必限定名称。
访问父数据上下文
数据上下文的变化并不总是有用的,有时您需要访问数据对象的另一部分来获得一个公共属性值。您可以通过在变量前加前缀../
来导航到父上下文,如清单 12-17 所示。
清单 12-17 。访问父数据上下文
...
<script id="flowerListTmpl" type="text/x-handlebars-template">
<ul>
<h3>{{title}}</h3>
{{#each stockData}}
{{#with description}}
<li>{{Name}}{{../../prefix}}{{Stock}}{{../../suffix}}</li>
{{/with}}
{{/each}}
</ul>
</script>
<script type="text/javascript">
$(document).ready(function () {
var data = {
title: "Stock List",
prefix: " (",
suffix: " in stock)",
stockData: {
aster: {
description: { Name: "Aster", Stock: 10 }
},
daffodil: {
description: { Name: "Daffodil", Stock: 12 }
},
rose: {
description: { Name: "Rose", Stock: 2 }
}
}
};
$("#flowerListTmpl").template(data).appendTo("form");
});
</script>
...
我已经在数据对象的顶层定义了prefix
和suffix
属性。要从模板中访问这些,我需要向上导航两个上下文级别,如下所示:
...
{{../../prefix}}
...
这个指令出现在#with
助手的范围内,所以应用一次../
将会把上下文改变到模板中的下一级,也就是#each
助手。#each
助手已经将上下文设置为stockData
属性的内容,所以我需要再升一级才能到达prefix
属性,这意味着我必须引用../../prefix
来获得我想要的值。
提示../
序列在模板而不是数据对象中向上导航一级。
创建自定义模板助手
一些助手中的逻辑非常简单,这意味着经常需要处理数据以适应助手的工作方式。你可以在清单 12-18 的中看到一个例子,它重复了我在本章前面用来演示#if
助手的例子。
清单 12-18 。准备数据供模板助手使用
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<script src="handlebars.js" type="text/javascript"></script>
<script src="handlebars-jquery.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" data-price="{{price}}" data-stock="{{stock}}"
value="{{#if stock}}1{{else}}0{{/if}}"required />
</div>
{{/flowers}}
</script>
<script type="text/javascript">
$(document).ready(function () {
var data = {
flowers: [
{ name: "Aster", product: "aster", stock: "10", price: 2.99 },
{ name: "Daffodil", product: "daffodil", stock: "12", price: 1.99 },
{ name: "Rose", product: "rose", stock: "2", price: 4.99 },
{ name: "Peony", product: "peony", stock: "0", price: 1.50 },
{ name: "Primula", product: "primula", stock: "1", price: 3.12 },
{ name: "Snowdrop", product: "snowdrop", stock: "15", price: 0.99 }]
};
for (var i = 0; i < data.flowers.length; i++) {
if (data.flowers[i].stock == 0) {
data.flowers[i].stock = null;
}
}
var tElem = $("#flowerTmpl");
tElem.template({ flowers: data.flowers.slice(0, 3) }).appendTo("#row1");
tElem.template({ flowers: data.flowers.slice(3) }).appendTo("#row2");
});
</script>
</head>
<body>
<h1>Jacqui's Flower Shop</h1>
<form method="post">
<div id="oblock">
<div class="dtable">
<div id="row1" class="drow"></div>
<div id="row2"class="drow"></div>
</div>
</div>
<div id="buttonDiv"><button type="submit">Place Order</button></div>
</form>
</body>
</html>
我不喜欢在将数据传递给模板引擎之前处理它,因为这意味着我有逻辑在两个地方显示我的数据:模板和for
循环。
这就是模板的使用变得有争议的地方,因为有两种方法可以确保从模板生成内容的逻辑只在一个地方:从模板中移除逻辑并全部用 JavaScript 定义,或者移除for
循环并向模板添加额外的逻辑。有许多论据支持这两种方法,但本质上这是由偏好和您正在处理的数据的性质驱动的个人选择。我更喜欢在模板中添加逻辑,这是Handlebars
库使之变得简单和容易的事情。
创建条件模板助手
在清单 12-19 中,您可以看到我对handlebars-jquery.js
文件所做的添加,为我的模板创建一个定制的助手。
提示你可以在任何脚本元素或 JavaScript 文件中定义自定义逻辑。我喜欢把我添加到 Handlebar.js 的东西放在一个地方——但这只是个人偏好。
清单 12-19 。在 handlebars-jquery.js 文件中为车把定义自定义条件逻辑
(function ($) {
Handlebars.registerHelper('gt', function (a, b, options) {
return (a > b) ? options.fn(this) : options.inverse(this);
});
var compiled = {};
$.fn.template = function (data) {
var template = $.trim($(this).first().html());
if (compiled[template] == undefined) {
compiled[template] = Handlebars.compile(template);
}
return $(compiledtemplate);
};
})(jQuery);
Handlebars 库定义了一个名为Handlebars
的全局对象,该对象又定义了一个名为registerHelper
的方法。registerHelper
有两个参数——您想给助手取的名字和一个助手函数,当在模板中遇到助手时将调用这个函数。我已经调用了我的自定义助手gt
(大于的的缩写),但是演示助手函数如何工作的最简单的方法是演示我的自定义助手,然后解释它的行为如何与其定义相关联。在清单 12-20 中,你可以看到我是如何将我的gt
助手应用到示例 HTML 文档中的。
清单 12-20 。应用自定义模板助手
...
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" data-price="{{price}}" data-stock="{{stock}}"
value="{{#gt stock 0}}1{{else}}0{{/gt}}" required />
</div>
{{/flowers}}
</script>
<script type="text/javascript">
$(document).ready(function () {
var data = {
flowers: [
{ name: "Aster", product: "aster", stock: "10", price: 2.99 },
{ name: "Daffodil", product: "daffodil", stock: "12", price: 1.99 },
{ name: "Rose", product: "rose", stock: "2", price: 4.99 },
{ name: "Peony", product: "peony", stock: "0", price: 1.50 },
{ name: "Primula", product: "primula", stock: "1", price: 3.12 },
{ name: "Snowdrop", product: "snowdrop", stock: "15", price: 0.99 }]
};
var tElem = $("#flowerTmpl");
tElem.template({ flowers: data.flowers.slice(0, 3) }).appendTo("#row1");
tElem.template({ flowers: data.flowers.slice(3) }).appendTo("#row2");
});
</script>
...
提示你可能需要在浏览器中重新加载网页才能看到正确的效果。JavaScript 文件有时会被大量缓存,这可能会阻止更改生效。
我的 #gt
助手检查stock
属性的值是否大于零。如果是,则将1
插入模板,否则插入0
。
在从模板生成内容之前,我必须调用Handlebars.registerHelper
方法,以便 Handlebars 库知道#gt
助手。当遇到 helper 指令时,Handlebars 将把指令中所有跟在#gt
后面的值作为参数传递给我的函数,替换掉当前数据上下文中任何可以解析为变量的值。
在清单中,我将对#gt
助手的引用放在一个 section 指令中,这意味着 Handlebars 将枚举data.flowers
数组,而stock
属性将被当前 flower 的值所替换——这意味着,例如,对于 Aster,传递给我的助手函数的参数将是10
、Aster stock
属性的值和1
,它们不能被解析为数据值,因此不做任何更改。
提示你可以根据需要给你的帮助函数提供或多或少的参数。两个正是我需要进行基本比较的数字。
这些是我在助手函数中收到的a
和b
值。我还收到一个由车把提供的options
物体:
...
Handlebars.registerHelper('gt', function (a, b, options) {
return (a > b) ? options.fn(this) : options.inverse(this);
});
...
options
对象提供了对编写助手有用的特性,如表 12-4 所述。
表 12-4 。由 Options 对象定义的属性和方法
名字 | 描述 |
---|---|
fn(data) | 调用以获取为条件帮助器的true 结果定义的内容,或者没有else 指令的帮助器的唯一内容。 |
inverse(data) | 被调用以获取已经在助手的else 子句中定义的内容。 |
Hash | 用于将可选参数接收到 helper 函数中。 |
Data | 用于提供具有特殊属性的模板。 |
在我的#gt
助手中,如果a
大于b
(在本例中,这意味着当前 flower 的股票属性的值大于零),我调用options.fn
方法来获取应该插入到 HTML 中的内容。我传递了this
变量,这个变量被设置为当前的数据上下文。如果a
不大于b
,那么我改为调用options.inverse
方法。对于这个例子,options.fn
方法将返回1
,而options.inverse
方法将返回0
。
返回更复杂的内容
插入 HTML 的内容是助手函数的结果,这意味着我可以通过插入更大的 HTML 片段来增加助手函数的复杂性,从而简化模板。在清单 12-21 中,您可以看到我是如何在handlebars-jquery.js
文件中定义一个名为 #gtValAttr
的助手的。
清单 12-21 。在 handlebars-jquery.js 文件中定义更复杂的助手
(function ($) {
Handlebars.registerHelper('gt', function (a, b, options) {
return (a > b) ? options.fn(this) : options.inverse(this);
});
Handlebars.registerHelper("gtValAttr", function () {
return "value='" + (this.stock > 0 ? "1" : "0") + "'";
});
var compiled = {};
$.fn.template = function (data) {
var template = $.trim($(this).first().html());
if (compiled[template] == undefined) {
compiled[template] = Handlebars.compile(template);
}
return $(compiledtemplate);
};
})(jQuery);
新的助手根本不接受任何参数——它通过this
属性获得所需的值(正如我提到的,该属性被设置为当前数据上下文)。helper 函数的结果是一个对value
属性的完整定义,它是为使用它的模板定制的。在清单 12-22 的中,你可以看到我是如何在模板中应用#gtValAttr
助手的。
清单 12-22 。在模板中应用#gtValAttr 助手
...
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" data-price="{{price}}" data-stock="{{stock}}"
{{#gtValAttr}}{{/gtValAttr}}required />
</div>
{{/flowers}}
</script>
...
注意我已经向您展示了这个助手来演示 Handlebars 库提供的灵活性,但是我并没有在实际项目中使用这种助手。它太依赖于数据和模板的结构,对其中任何一个的改变都会破坏代码。我更喜欢创建像#gt
这样的小而集中的助手,它们可以在任何模板中使用,并且生成尽可能少的内容,最好是通过参数提供。
在帮助函数中接收可选参数
您可以在模板中定义传递给 helper 函数的参数。这些可选参数的用途完全取决于助手的创建者,但最常见的用途是将属性值传递给生成完整 HTML 元素的助手。在清单 12-23 中,你可以看到我已经定义了一个助手,它从一个花数据对象中创建完整的input
元素。同样,这也不是我喜欢在自己的项目中使用模板的方式,但是读者可能更喜欢助手中的复杂性,而不是模板中的复杂性。
清单 12-23 。在 handlebars-jquery.js 文件中定义一个接受可选参数的助手
(function ($) {
Handlebars.registerHelper('gt', function (a, b, options) {
return (a > b) ? options.fn(this) : options.inverse(this);
});
Handlebars.registerHelper("gtValAttr", function () {
return "value='" + (this.stock > 0 ? "1" : "0") + "'";
});
Handlebars.registerHelper("inputElem", function (product, stock, options) {
options.hash.name = product;
options.hash.value = stock > 0 ? "1" : "0";
options.hash.required = "required";
return $("<input>", options.hash)[0].outerHTML;
});
var compiled = {};
$.fn.template = function (data) {
var template = $.trim($(this).first().html());
if (compiled[template] == undefined) {
compiled[template] = Handlebars.compile(template);
}
return $(compiledtemplate);
};
})(jQuery);
#inputElem
助手为一朵花生成了一个完整的input
元素——但是再一次,通过查看它在模板中的应用将更容易理解它是如何工作的,如清单 12-24 所示。
清单 12-24 。在模板中应用#inputElem 助手
...
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
{{#inputElem product stock data-stock=stock data-price=price}}{{/inputElem}}
</div>
{{/flowers}}
</script>
...
正如你在清单 12-23 中看到的,#inputElem
助手函数有两个参数,当我在模板中应用助手时,我用它们来传递product
和stock
属性的值。附加参数的形式是key=value
,它们通过options.hash
属性传递给帮助函数,首先根据当前数据上下文进行解析。对于我的例子,这意味着options.hash
属性为Aster
花返回一个类似这样的对象:
{"data-stock": 10, "data-price": 2.99}
在第二部分中,我解释了 jQuery $
函数的一个版本,它从 HTML 字符串和属性的 map 对象中生成一个jQuery
对象,而option.hash
对象正是我创建想要的input
元素所需的格式。但是它不包含我需要的所有属性,所以我使用下面的语句来完成这个集合:
...
options.hash.name = product;
options.hash.value = stock > 0 ? "1" : "0";
options.hash.required = "required";
...
我可以使用 jQuery $
函数创建我的input
元素,应用属性,并返回模板所需的 HTML 字符串,如下所示:
...
return $("<input>", options.hash)[0].outerHTML;
...
为了获取 HTML 字符串,我使用一个数组索引器获取索引0
处的HTMLElement
对象,并使用outerHTML
属性获取如下字符串:
<input data-stock="10" data-price="2.99" name="aster" value="1" required="required">
Handlebars 和 jQuery 配合得很好,使得从助手中生成完整的 HTML 元素变得很容易——#inputElem
助手演示了这一点。
提示使用 jQuery 获取元素的 HTML 没有便捷的方法,这就是我使用底层HTMLElement
对象的outerHTML
属性的原因。最接近的方法是html
,但是它返回一个元素的 HTML 内容,而不是元素本身的 HTML,这意味着在使用html
方法之前,我必须将我的input
元素附加到另一个元素上,就像这样:$("<div />").append($("<input>", options.hash)).html();
我发现使用 HTML 元素更简单,更容易阅读。
提供自定义模板属性
我在本章前面解释过,#each
助手定义了在它定义的块中可用的特殊属性。这也是您可以在自定义帮助器中完成的事情,这是简化模板结构的好方法。作为示范,我已经创建了#stockValue
助手,如清单 12-25 所示。
清单 12-25 。在 handlebars-jquery.js 文件中创建#stockValue 助手
(function ($) {
Handlebars.registerHelper('gt', function (a, b, options) {
return (a > b) ? options.fn(this) : options.inverse(this);
});
Handlebars.registerHelper("gtValAttr", function () {
return "value='" + (this.stock > 0 ? "1" : "0") + "'";
});
Handlebars.registerHelper("inputElem", function (product, stock, options) {
options.hash.name = product;
options.hash.value = stock > 0 ? "1" : "0";
options.hash.required = "required";
return $("<input>", options.hash)[0].outerHTML;
});
Handlebars.registerHelper("stockValue", function (options) {
options.data.attributeValue = this.stock > 0 ? "1" : "0";
return options.fn(this);
});
var compiled = {};
$.fn.template = function (data) {
var template = $.trim($(this).first().html());
if (compiled[template] == undefined) {
compiled[template] = Handlebars.compile(template);
}
return $(compiledtemplate);
};
})(jQuery);
这是一个简单的助手,它所做的就是在options.data
对象上创建一个名为attributeValue
的属性,并为它分配我想要的值,作为input
元素的value
属性。我可以使用名为@attributeValue
的特殊属性在模板中的#stockValue
助手包含的模板块中访问这个值,如清单 12-26 所示。
清单 12-26 。访问模板中的特殊属性
...
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
{{#stockValue}}
<input name="{{product}}" data-price="{{price}}" data-stock="{{stock}}"
value="{{@attributeValue}}" required />
{{/stockValue}}
</div>
{{/flowers}}
</script>
...
摘要
在这一章中,我介绍了 Handlebars 模板库,它提供了一组很好的特性,可以将 JavaScript 数据转换成 HTML 元素,而不会陷入大量讨厌的代码中。我喜欢使用这个库的原因是,它提供了灵活性,可以决定在模板中定义多少逻辑,通过处理数据处理多少逻辑,以及在处理程序中隐藏多少逻辑。在下一章中,我将向您展示 jQuery 如何支持 HTML 表单,以及如何应用一个广泛使用的插件来验证用户输入的数据。