knockout. js 是不是一个动画库。每当底层数据发生变化时,所有 knockout. js 的自动更新都会立即应用于*。为了动画化它的任何变化,我们需要深入到敲除. js 的内部,并使用另一个像 jQuery 或 MooTools 这样的 JavaScript 框架手动创建动画化的转换。本章坚持使用 jQuery 的动画例程,但是所展示的概念也适用于其他动画库。*
*## 购物车的返回
在本章中,我们将回到购物车示例的简化版本。用以下内容创建一个新的 HTML 文件。我们不会发出任何 AJAX 请求,所以请随意将它放在您计算机的任何地方。然而,我们将使用 jQuery 的动画例程,所以一定要包含一个到 jQuery 库副本的链接。
<html lang='en'> <head> <title>Animating Knockout.js</title> <meta charset='utf-8' /> <link rel='stylesheet' href='style.css' /> </head> <body>
<h1>Animating Knockout.js</h1>
<table>
<thead><tr>
<th>Product</th>
<th>Price</th>
<th></th>
</tr></thead>
<tbody data-bind='foreach: items'>
<tr>
<td data-bind='text: name'></td>
<td data-bind='text: price'></td>
<td><button data-bind='click: $root.removeProduct'>Remove</button></td>
</tr>
</tbody>
</table>
<button data-bind='click: addProduct'>Add Beer</button>
<script type='text/javascript' src='knockout-2.1.0.js'></script>
<script src='jquery-1.7.2.js'></script>
<script type='text/javascript'> function Product(name, price, tags, discount, details) { this.name = ko.observable(name); this.price = ko.observable(price); } function ShoppingCart() { var self = this; this.instructions = ko.observable(""); this.hasInstructions = ko.observable(false); this.items = ko.observableArray([ new Product("Beer", 10.99), new Product("Brats", 7.99), new Product("Buns", 1.49) ]); this.addProduct = function() { this.items.push(new Product("More Beer", 10.99)); };
this.removeProduct = function(product) { self.items.destroy(product); }; }; ko.applyBindings(new ShoppingCart()); </script> </body> </html>
希望现在这些都是回顾。我们有一个包含一堆产品的可观测数组,一个显示每个产品的foreach
绑定,以及一个向购物车添加更多商品的按钮。
gentle . js 本身是一个强大的用户界面库,但是一旦您将其与 jQuery 或 MooTools 等框架的动画功能相结合,您就可以用最少的标记创建真正令人惊叹的 ui。首先,我们将看一下动画列表,然后下一节介绍一种更通用的动画视图组件的方法。
foreach
绑定有两个名为beforeRemove
和afterAdd
的回调。这些功能分别在项目从列表中移除之前或添加到列表之后执行。这给了我们一个机会,在敲除操作 DOM 之前制作每个项目的动画。向<tbody>
元素添加回调,如下所示:
<tbody data-bind='foreach: {data: items, beforeRemove: hideProduct, afterAdd: showProduct}'>
现在,我们的foreach
绑定不再是一个属性,而是将一个对象文字作为其参数。参数的data
属性指向您想要渲染的数组,beforeRemove
和afterAdd
属性指向所需的回调函数。接下来,我们应该在ShoppingCart
视图模型上定义这些回调:
this.showProduct = function(element) { if (element.nodeType === 1) { $(element).hide().fadeIn(); } };
this.hideProduct = function(element) { if (element.nodeType === 1) { $(element).fadeOut(function() { $(element).remove(); }); } };
showProduct()
回调使用 jQuery 让新列表项逐渐淡入,hideProduct()
回调淡出,然后从 DOM 中移除。这两个函数都将受影响的 DOM 元素作为它们的第一个参数(在本例中,它是一个<tr>
元素)。条件语句确保我们使用的是完整的元素,而不仅仅是文本节点。
最终的结果应该是列出那些可以平稳地进出列表的项目。当然,您可以自由使用 jQuery 的任何其他转换,或者在任一回调中执行自定义后处理。
foreach
回调对于动画列表非常有用,但是不幸的是其他绑定不提供这个功能。因此,如果我们想要动画用户界面的其他部分,我们必须创建自定义绑定,将动画嵌入其中。
自定义绑定的工作方式就像敲除. js 的默认绑定一样。例如,考虑以下表单域:
<div>
<p>
<input data-bind='checked: hasInstructions' type='checkbox' />
Requires special handling instructions
</p>
<div>
<textarea data-bind='visible: hasInstructions, value: instructions'>
</textarea>
</div>
</div>
该复选框充当了<textarea>
的切换开关,但是由于我们使用的是visible
绑定,knockout. js 会突然在 DOM 中添加或删除它。为了给<textarea>
提供一个平稳的过渡,我们将创建一个名为visibleFade
的自定义绑定:
<textarea data-bind='visibleFade: hasInstructions, value: instructions'>
当然,在我们将自定义绑定添加到 knockout. js 之前,这是行不通的,我们可以通过将定义绑定的对象添加到ko.bindingHandlers
来实现,如下面的代码示例所示。这也恰好是定义所有内置绑定的地方。
ko.bindingHandlers.visibleFade = { init: function(element, valueAccessor) { var value = valueAccessor(); $(element).toggle(value()); }, update: function(element, valueAccessor) { var value = valueAccessor(); value() ? $(element).fadeIn() : $(element).fadeOut(); } }
init
属性指定了第一次遇到绑定时要调用的函数。这个回调应该定义视图组件的初始状态,并执行必要的设置操作(例如,注册事件侦听器)。对于visibleFade
,我们所要做的就是根据视图模型的状态显示或隐藏元素。我们使用 jQuery 的toggle()
方法实现了这一点。
element
参数是被绑定的 DOM 元素,valueAccessor
是将返回所讨论的视图模型属性的函数。在我们的示例中,element
引用了<textarea>
,而valueAccessor()
返回了对hasInstructions
的引用。
update
属性指定了一个函数,每当相关的可观测到的变化时执行,我们的回调使用hasInstructions
的值将<textarea>
转换到适当的方向。请记住,您需要调用可观测值来获取其当前值(即value()
,而不是value
)。然而,如果hasInstructions
是一个普通的 JavaScript 属性而不是一个可观测的属性,情况就不是这样了。
在这一章中,我们发现了两种制作 Quicken . js 视图组件动画的方法。首先,我们向foreach
绑定添加了回调方法,这让我们可以将项目的添加和移除委托给用户定义的函数。这让我们有机会将 jQuery 的动画转换集成到我们的 knockout. js 模板中。然后,我们探索了自定义绑定作为动画任意元素的手段。
本章介绍了自定义绑定的一个常见用例,但它们绝不仅限于动画用户界面组件。自定义绑定还可以用来过滤收集的数据,监听自定义事件,或者创建可重用的小部件,如网格和分页内容。如果你能把一个行为封装成一个init
和一个update
函数,你就能把它变成一个自定义绑定。*