avalon是通過ms-repeat實現對一組數據的批量輸出。這一組數據可以是一個數組,也可以是一個哈希(或叫對象)。我們先從數組說起吧。
第二節就說,凡是定義在VM中的數組,如果沒有以$開頭或者沒放在$skipArray數組里,都會轉會監控數組。監控數組其實就是一個被重寫了push、unshift、shift、pop、 splice、sort、reverse方法的普通數組。當然它也添加了其他一些方法,如set、 pushArray、remove、removeAt、removeAll、clear、ensure、 contains、size。我們只要操作這些方法就能同步視圖。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="avalon.js" ></script>
<script>
var model = avalon.define({
$id: "test",
array: ["aaa","bbb","ccc"]
})
</script>
</head>
<body ms-controller="test">
<ul>
<li ms-repeat="array">{{el}} --- {{$index}}</li>
</ul>
</body>
</html>
上面就是array被改造成監控數組后的樣式,添加了大量屬性與方法。 ms-repeat是配合與監控數組使用的。我們注意到在ms-repeat的作用范圍下,多出了el、$index兩個變量,而它們在VM(ViewModel)中是尋不到它們的蹤影。這是循環綁定特有的功能,其中el稱之為代理VM,$index是與這個el相對應的索引值。並且這個el是可以配置的,如
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="avalon.js" ></script>
<script>
var model = avalon.define({
$id: "test",
array: ["aaa","bbb","ccc"]
})
</script>
</head>
<body ms-controller="test">
<ul>
<li ms-repeat-item="array">{{item}} --- {{$index}}</li>
</ul>
</body>
</html>
說起作用域,我們可以看到ms-repeat是將當前元素根據當前數組的個數,以原元素為模板,在原地重復復制N遍實現的。
有了循環綁定,我們想做一個切換卡是非常簡單的。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="avalon.js" ></script>
<script>
var model = avalon.define({
$id: "test",
array: ["aaa", "bbb", "ccc"],
currentIndex: 0,
changeIndex: function(i) {
model.currentIndex = i
}
})
</script>
<style>
.ms-tabs{
border: 1px solid black;
padding: 1em;
width:300px;
height:100px;
}
.ms-tigger{
background:#DDD;
margin-right:1em;
}
.ms-active{
background:#CD235C;
}
</style>
</head>
<body ms-controller="test">
<button type="button"
class="ms-tigger"
ms-repeat="array"
ms-class="ms-active: currentIndex === $index"
ms-click="changeIndex($index)">切換鍵{{$index+1}}</button>
<div class="ms-tabs"
ms-repeat="array"
ms-if-loop="currentIndex == $index">{{el}}</div>
</body>
</html>
這里有一個ms-if-loop,第三節就介紹過綁定屬性的執行順序,ms-if是先於ms-repeat執行的,當我想在循環時,要根據元素的情況做一些分支判定時就實現不了。因此需要一個晚於ms-repeat的ms-if,於是ms-if-loop就應運而生了。
在循環過程中,ms-repeat除了會產生el、$index等臨時變量,還有其他變量供我們調遣。
- $index,這個一個數字,為元素對應的索引值
- $first,這是一個布爾,判定它是否第一個
- $last,這是一個布爾,判定它是否最后一個
- $remove,這是一個方法,移除此數組元素
- $outer,這是一個對象,用於獲取外圍循環中的VM對象,它里面包含$index, $first, $last, $remove等屬性。
它們的關系就如下面的javascript循環代碼:
for(var i = 0, n = array.length; i < n; i++){ //----> ms-each-el=array
var el = array[i] // $index --> i
for(var j = 0, k = el.length; j < k; j++){ //---> ms-each-elem=el
var elem = el[j] // elem.$outer ---> el
}
}
下面我們看一下$first、 $last、$remove的使用方法:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="avalon.js" ></script>
<script>
var model = avalon.define({
$id: "test",
array: ["aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg"]
})
</script>
<style>
.last{
background: purple;
}
.first{
background:violet;
}
</style>
</head>
<body ms-controller="test">
<ul>
<li ms-repeat-xx="array"
ms-class="last: $last"
ms-class-1="first: $first"
ms-click="$remove">{{xx}}:{{$index}}</li>
</ul>
</body>
</html>
當我們點擊LI元素時,它就會自動從監控數組移除對應的元素,並立即同步視圖,刪除我們剛才點擊的元素節點,同時會調整其他元素的$index、$first、$last,從而確保first、 last類名顯示正確。
$outer主要是用在二維數組或多維數組里。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="avalon.js" ></script>
<script>
var model = avalon.define({
$id: "test",
array: [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
})
</script>
</head>
<body ms-controller="test">
<table border="1">
<tr ms-repeat-el="array">
<td ms-repeat-elem="el">{{elem}} 它位於第<b style="color:orchid">{{$outer.$index}}</b>行</td>
</tr>
</table>
</body>
</html>

<!DOCTYPE html>
<html>
<head>
<title>avalon入門</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="avalon.js" type="text/javascript"></script>
<script>
var model = avalon.define({
$id: "test",
num: [1,2,3],
data: ["a", "b", "c"]
});
</script>
</head>
<body>
<div ms-controller="test">
<div ms-repeat="num">
<strong ms-repeat="data">
{{el}}: {{$outer.el}}
</strong>
</div>
</div>
</body>
</html>
如果我們想在綁定屬性得到當前數組的長度,請記得使用size方法,不要直接用length屬性啊。我們來一個復制,演示怎么調用它的方法來同步視圖的。
<!DOCTYPE HTML>
<html>
<head>
<title>ms-repeat</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="avalon.js" ></script>
<script>
avalon.define("test", function(vm) {//這里是使用avalon.define的舊風格
vm.array = ["1", "2", "3", "4"]
"push,unshift,remove,ensure".replace(/\w+/g, function(method) {
vm[method] = function(e) {
if (this.value && e.which == 13) {//this為input元素
vm.array[method](this.value);
this.value = "";
}
}
})
vm.removeAt = function(e) {
if (isFinite(this.value) && e.which == 13) {//this為input元素
var a = ~~this.value
vm.array.removeAt(a)
this.value = "";
}
}
"pop,shift,sort,reverse".replace(/\w+/g, function(method) {
vm[method] = function(e) {
vm.array[method]();
}
})
});
</script>
</head>
<body ms-controller="test">
<p>監控數組擁有以下方法,我們可以操作它們就能同步對應的區域</p>
<blockquote>
push, pushAll, shift, unshift, pop, slice, splice, remove, removeAt, removeAll, clear, ensure, sort, reverse, set
</blockquote>
<ul>
<li ms-repeat="array">數組的第{{$index+1}}個元素為{{el}}</li>
</ul>
<p>對數組進行push操作,並回車<input ms-keypress="push"></p>
<p>對數組進行unshift操作,並回車<input ms-keypress="unshift"></p>
<p>對數組進行ensure操作,並回車<input ms-keypress="ensure"><br/>
(只有數組不存在此元素才push進去)</p>
<p>對數組進行remove操作,並回車<input ms-keypress="remove"></p>
<p>對數組進行removeAt操作,並回車<input ms-keypress="removeAt"></p>
<p><button type="button" ms-click="sort">對數組進行sort操作</button></p>
<p><button type="button" ms-click="reverse">對數組進行reverse操作</button></p>
<p><button type="button" ms-click="shift">對數組進行shift操作</button></p>
<p><button type="button" ms-click="pop">對數組進行pop操作</button></p>
<p>當前數組的長度為<span style="color:red">{{array.size()}}</span>,注意 我們無法修改數組length的固有行為,因此它無法同步視圖,需要用size方法。</p>
</body>
</html>
通過操作屬性就能操作視圖是不是很爽呢!要知道上面的代碼如果換成jQuery來不寫不知要寫多少行!
我們再來一點實用的例子。
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<script src="avalon.js"></script>
<script>
var model = avalon.define({
$id: "test",
data: [{checked: false}, {checked: false}, {checked: false}],
allchecked: false,
checkAll: function() {
var bool = model.allchecked = this.checked
model.data.forEach(function(el) {
el.checked = bool
})
},
checkOne: function() {
if (!this.checked) {
model.allchecked = false
} else {//avalon已經為數組添加了ecma262v5的一些新方法
model.allchecked = model.data.every(function(el) {
return el.checked
})
}
}
})
</script>
</head>
<body>
<table ms-controller="test" border="1">
<tr>
<td><input type="checkbox" ms-duplex-radio="allchecked" data-duplex-changed="checkAll"/>全選</td>
</tr>
<tr ms-repeat="data">
<td><input type="checkbox" ms-duplex-radio="el.checked" ms-data-index=$index data-duplex-changed="checkOne"/>xxxxxxxxxxxx</td>
</tr>
</table>
</body>
</html>

此外,我們還可以通過data-each-rendered來指定這些元素都插入DOM被渲染了后執行的回調,this指向元素節點,有一個參數表示為當前的操作,是add、del、 move、 index還是clear。
上面我們說了這么有關數組的東西,我們再來看它是如何操作哈希的。對於哈希,ms-repeat內部只會產生$key、 $val、 $outer三個變量,不存在$index什么的。$key就是屬性名,$val就是屬性值,$outer與之前的講解相同。如果你想在對象循環時使用$index,可以這樣做:
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<script src="avalon.js"></script>
<script>
var index = 0
var model = avalon.define({
$id: "test",
data:{
aaa: 1111,
bbb: 2222,
ccc: 3333,
ddd: 4444
},
getIndex: function(){
return index++
}
})
</script>
</head>
<body ms-controller="test">
<ul>
<li ms-repeat="data" >{{getIndex()}}、{{$key}}--{{$val}}</li>
</ul>
</body>
</html>

如果我們想控制對象屬性的輸出順序,或讓某些元素不輸出來,那么我們可以使用data-with-sorted回調。它用ms-repeat、ms-with綁定,趕對象渲染之前觸發,要求輸出一個字符串數組,對象的鍵值對會根據它依次輸出;框架默認會輸入原對象的所有鍵名構成的數組作為參數。
我們改一下上面的例子:
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<script src="avalon.js"></script>
<script>
var index = 0
var model = avalon.define({
$id: "test",
data:{
aaa: 1111,
bbb: 2222,
ccc: 3333,
ddd: 4444
},
keys: function(a){
console.log(a)
console.log(this)
return ["ccc","ddd","aaa"]
},
getIndex: function(){
return index++
}
})
</script>
</head>
<body ms-controller="test">
<ul>
<li ms-repeat="data" data-with-sorted="keys" >{{getIndex()}}、{{$key}}--{{$val}}</li>
</ul>
</body>
</html>

不過ms-repeat只能循環自身,如果有時我們碰到一些復雜的結構,如定義列表,那么我們可以使用ms-each、 ms-with。ms-each是用於循環數組,ms-with是循環對象。除了循環范圍不一樣外,其他與ms-repeat沒什么不同。
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<script src="avalon.js"></script>
<script>
var model = avalon.define({
$id: "test",
data: [
{text: "title1", value: 111111},
{text: "title2", value: 222222},
{text: "title3", value: 333333}
]
})
</script>
</head>
<body ms-controller="test">
<dl ms-each="data">
<dt>{{el.text}}</dt>
<dd>{{el.value}}</dd>
</dl>
</body>
</html>

循環綁定是一個非常重要的綁定,是屬於流程綁定之一,用法與注意點非常多,我們可以在這里繼續學習。
