这篇文章主要涉及control flow部分的binding。html
foreach binding主要做用于lists或是tables内数据单元的动态绑定。下面是一个简单的例子:node
js部分:jquery
1 ko.applyBindings({ 2 people: [ 3 { firstName: "Chiaki", lastName: "Izumi" }, 4 { firstName: "Kazusa", lastName: "Touma" }, 5 { firstName: "Haruki", lastName: "Murakami" } 6 ] 7 });
html部分:git
1 <table> 2 <thead> 3 <tr> 4 <th>First Name</th> 5 <th>Last Name</th> 6 </tr> 7 </thead> 8 <tbody data-bind="foreach: people"> 9 <tr> 10 <td data-bind="text: firstName"></td> 11 <td data-bind="text: lastName"></td> 12 </tr> 13 </tbody> 14 </table>
页面显示效果以下:github
这里的一个疑问在于如何利用thead显示一列标题,留做之后研究。数组
在上述示例中,咱们简单的在ko.applybindings中添加了一个数组并将其绑定在一个tbody元素中,咱们也能够自定义一个view model来实现这一效果,下面是一个更为复杂一些的例子:app
js部分:less
1 function MyViewModel() { 2 var self = this; 3 4 self.people = ko.observableArray([ 5 { name: "Chiaki" }, 6 { name: "Yuki" }, 7 { name: "Kazusa" } 8 ]); 9 10 self.addPerson = function() { 11 self.people.push({ name: "New name at " + new Date() }); 12 }; 13 14 self.removePerson = function() { 15 self.people.remove(this); 16 }; 17 } 18 19 ko.applyBindings(new MyViewModel());
html部分:ide
1 <h4>People</h4> 2 <ul data-bind="foreach: people"> 3 <li> 4 Person at position <span data-bind="text: $index"></span>: 5 <span data-bind="text: name"></span> 6 <a href="#" data-bind="click: $parent.removePerson">Remove</a> 7 </li> 8 </ul> 9 <button data-bind="click: addPerson">Add</button>
显示的效果以下:函数
因为这里并不支持添加script,不可以动态地显示页面。
注意一:以上的两个例子中,在每一个foreach binding内部,咱们均可以直接调用绑定的数组内每一项的属性,好比firstName和lastName,对于绑定的数组自己,也是能够经过使用$data来调用。这就意味着$data.firstName和firstName的效果是同样的,固然,前者势必会显得更为冗余。
注意二:在第二个例子中,咱们使用了$index来表示绑定的数组中每一项的索引,$index是一个observable,每当绑定的数组中的元素索引产生变化时,UI便会对相应的$index进行更新。另外,咱们也能经过$parent来获取foreach外部的数据。有关$index和$parent的更为详尽的内容,可参考binding context properties,留做之后研究。
注意三:在注意一中提到,咱们可以经过$data来调用foreach绑定的数组自己,可是当咱们使用嵌套的foreach,内层foreach如何可以调用外层foreach绑定的数组呢?这时咱们能够借由as给外层foreach所绑定的数组定义一个另外的名称,进而在内层foreach中利用此名称来调用外层foreach所绑定的数组。接下来是一个简单的例子:
js部分:
1 var viewModel = { 2 person: ko.observableArray([ 3 { name: "Chiaki", friends: [ "Charlie", "Kazusa" ] }, 4 { name: "Kazusa", friends: [ "Chiaki", "Charlie" ] }, 5 { name: "Charlie", friends: [ "Chiaki", "Kazusa" ] } 6 ]) 7 }; 8 9 ko.applyBindings(viewModel);
html部分:
1 <ul data-bind="foreach: { data: person, as: 'person' }"> 2 <li> 3 <ul data-bind="foreach: { data: friends, as: 'friends' }"> 4 <li> 5 <span data-bind="text: person.name"></span>: 6 <span data-bind="text: friends"></span> 7 </li> 8 </ul> 9 </li> 10 </ul>
页面显示效果以下:
这个例子中的外层foreach绑定的是person数组,person数组中有一个属性name和另一个数组firends,在内层foreach中绑定的是数组firends。当咱们在内层foreach要调用外层的person数组内的属性时,借由as,使用了person.name来调用。而这里也有一个有趣的状况,就是当一个数组里面只有单纯的元素,好比说friends数组,它的元素并非object,也就不存在这identifier的问题,这时咱们要调用它的元素时,只须要调用数组自己便可,这也就是为何在以前的示例中若是咱们调用绑定的数组自己会返回[object, object]。这代表,通常状况下,遍历数组中的元素只须要调用数组名(as指定)或是调用$data便可,而碰到那些内部元素是object的时候,咱们要调用object内的属性则须要调用相应属性的名称。
另外须要注意的一点就是,as后所跟着的必须是一个字符串(as: "person"而不是as: person)。
注意四:有些状况下,咱们使用foreach的场合会比较复杂,好比说以下的例子:
1 <ul> 2 <li>Header item</li> 3 <!-- The following are generated dynamically from an array --> 4 <li>Item A</li> 5 <li>Item B</li> 6 <li>Item C</li> 7 </ul>
这种状况下,咱们并不知道改在哪一个元素内添加foreach。若是是在ul添加的话,咱们事先写好的header item便会被覆盖掉,而ul元素内又只能嵌套li元素,添加另外一个容器来实现foreach也是不可行的。为了解决这一问题,咱们须要使用无容器的控制流语法(containerless control flow syntax),与先前所提到的containerless text syntax相似。一个简单的例子以下:
js部分:
1 var viewModel = { 2 people: ko.observableArray([ 3 "Kazusa", 4 "Chiaki", 5 "Yuki" 6 ]) 7 }; 8 9 ko.applyBindings(viewModel);
html部分:
1 <ul> 2 <li>Header item</li> 3 <!-- ko foreach: people --> 4 <li>name: <span data-bind="text: $data"></span></li> 5 <!-- /ko --> 6 </ul>
页面显示效果以下:
官方文档中的注意五涉及到更改所绑定的数组时的性能问题,注意六主要涉及到destroyed的数组,这部分留做以后研究。
注意七:当咱们须要在生成的DOM元素上运行一些自定义的逻辑时,咱们能够用到afterRender/afterAdd/beforeRemove/beforeMove/afterMove等回调函数。
须要注意的是,这些回调函数仅仅适用于触发与列表的改变有关的动画,若是咱们须要对新添加的DOM元素附加一些其余的行为,好比说事件的处理或是第三方的UI控制,将这些行为做为自定义的绑定(custom binding)会更为合适,由于这样设定的行为是与foreach互相独立的。
接下来是一个调用afterAdd的简单的例子,其中用到了jQuery Color plugin。
js部分:
1 var viewModel = { 2 people: ko.observableArray([ 3 "Kazusa", 4 "Chiaki", 5 "Yuki" 6 ]), 7 8 yellowFadeIn: function(element, index, data) { 9 $(element).filter("li") 10 .animate({ backgroundColor: "yellow" }, 200) 11 .animate({ backgroundColor: "white" }, 800); 12 }, 13 14 addItem: function() { this.people.push("New person"); } 15 }; 16 17 ko.applyBindings(viewModel);
我对这里的this的使用是有疑问的,联想到computed observable内this的使用,留做以后研究。
html部分:
1 <ul data-bind="foreach: { data: people, afterAdd: yellowFadeIn }"> 2 <li data-bind="text: $data"></li> 3 </ul> 4 5 <button data-bind="click: addItem">Add</button>
因为我还没掌握制做和插入动态图片的方式,这里不展现显示的效果,不过在点击Add button以后,New person会被添加,通知其背景颜色又黄色变为白色。
如下是对一些回调函数详尽的介绍:
afterRender是在foreach模块初始化或是添加新的元素时触发的,其接受的参数为(为了可以保持愿意,这里用英文显示,下同):
afterAdd与afterRender相似,不过它只会在新元素添加时触发(foreach初始化的时候并不会触发),它所接受的参数为:
beforeRemove函数会在数组中的某一项被删除时触发。须要注意的是,beforeRemove实际上替代了UI界面对remove所作出的回应,即在beforeRemove函数中若是不对DOM相应的元素进行remove操做,则在页面上的元素是不会被删除的,可是viewModel中的数组相应的元素却已经被删除了。beforeRemove函数接受如下参数:
beforeMove函数会在数组中的元素索引起生变化时触发,beforeMove会应用于全部索引产生变化的元素,即倘若咱们在数组开头插入元素,则其后全部元素都将受到beforeMove回调函数的影响。一个比较经常使用的作法是经过beforeMove来保存原有元素的坐标以便咱们可以在afterMove中控制元素移动的动画。beforeMove函数接受如下参数:
afterMove函数也会在数组中的元素索引起生变化时触发,afterMove会应用于全部索引产生变化的元素,即倘若咱们在数组开头插入元素,则其后全部元素都将受到afterMove回调函数的影响。afterMove函数接收如下参数:
对于回调函数中的before和after,咱们应该有一个比较清醒的认识。before和after针对的都是UI界面中的元素变化,也就是页面产生变化以前和页面产生变化以后,与此同时,viewModel部分的数组已经发生了变化,正是viewModel部分的数组的变化才触发了before和after所对应的回调函数。