這篇文章主要涉及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所對應的回調函數。