在許多表單應用,我們經常遇到點擊一個復選框(或下拉框)會引發旁邊的復選框(或下拉框)發生改變,這種聯動效果用avalon來做是非常簡單的。因為avalon擁有經典MVVM框架的一大利器,雙向綁定!絕大部分的指令是從vm單向拍到頁面,而雙向綁定,則通過監聽元素的value值變化,反向同步到vm中。如果沒有這種機制,則需要引入額外的機制(flux雲雲)來處理此事。
在avalon中,雙向綁定是由雙工指設,ms-duplex實現的。這個指令在1.0中已經不斷增強,到2.0,它的服務對象已經不局限於表單元素,還擴展到可編輯元素(contenteditable=true)上了。
此外ms-duplex還可以與新加入的ms-validate指令一起使用。因此雙工指令是集成數據轉換,數據格式化,數據驗證,光標處理4大功能。
數據轉換與之前1.5一樣,使用四大轉換器
ms-duplex-string="@aaa"
ms-duplex-number="@aaa"
ms-duplex-boolean="@aaa"
ms-duplex-checked="@aaa"
前三個是將元素的value值轉換成string, number, boolean(只有為'false'時轉換為false),最后是根據當前元素(它只能是radio或checkbox)的checked屬性值轉換為vm對應屬性的值。
它們都是放在屬性名上。當數據從元素節點往vmodel同步時,轉換成預期的數據。
數據格式化是放在屬性值時,以過濾器形式存在,如
ms-duplex='@aaa | uppercase'
ms-duplex='@aaa | date('YYYY:MM:dd')'
此外還存在兩個控制同步時機的過濾器,change與debounce。
change過濾器相當於之前的data-duplex-event="change".
debounce是對頻繁輸入進行節流處理。它既不像那oninput事件那樣密集(由於使用了虛擬DOM,每一個字符,都會重新短成一個全新的虛擬DOM樹),也不像onchange事件那么滯后。這在自動元素的suggest組件中非常有用。debounce可以傳參,為毫秒數
ms-duplex='@aaa | debounce(300)'
然后是數據驗證,這必須在所有表單元素的上方,加上ms-validate才會生效。這時每個表單元素要加上data-duplex-validator.
<form ms-validate="@validation">
<input ms-duplex='@aaa'
data-validators='require,email,maxlength'
data-maxlength='4'
data-maxlength-message='太長了' >
</form>
最后是光標處理,目的是確保光標不會一下子跑到最前還是最后。
除此之后,ms-duplex還有一個回調,data-duplex-changed,用於與事件綁定一樣,默認第一個參數為事件對象。如果傳入多個參數,那么使用$event為事件對象占位。
現在我們來一些實際的例子!
全選與非全選
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<script src="./dist/avalon.js"></script>
<script>
var vm = avalon.define({
$id: "test",
data: [{checked: false}, {checked: false}, {checked: false}],
allchecked: false,
checkAll: function (e) {
var checked = e.target.checked
vm.data.forEach(function (el) {
el.checked = checked
})
},
checkOne: function (e) {
var checked = e.target.checked
if (checked === false) {
vm.allchecked = false
} else {//avalon已經為數組添加了ecma262v5的一些新方法
vm.allchecked = vm.data.every(function (el) {
return el.checked
})
}
}
})
</script>
</head>
<body>
<table ms-controller="test" border="1">
<tr>
<td><input type="checkbox"
ms-duplex-checked="@allchecked"
data-duplex-changed="@checkAll"/>全選</td>
</tr>
<tr ms-for="($index, el) in @data">
<td><input type="checkbox" ms-duplex-checked="el.checked" data-duplex-changed="@checkOne" />{{$index}}::{{el.checked}}</td>
</tr>
</table>
</body>
</html>

我們仔細分析其源碼,allchecked是用來控制最上面的復選框的打勾情況,數組中的checked是用來控制下面每個復選框的下勾情況。由於是使用ms-duplex,因此會監聽用戶行為,當復選框的狀態發生改變時,就會觸發data-duplex-changed回調,將當前值傳給回調。但這里我們不需要用它的value值,只用它的checked值。
最上面的復選框對應的回調是checkAll,它是用來更新數組的每個元素的checked屬性,因此一個forEach循環賦值就是。
下面的復選框對應的checkOne,它們是用來同步最上面的復選框,只要它們有一個為false上面的復選框就不能打勾,當它們被打勾了,它們就得循環整個數組,檢查是否所有元素都為true,是才給上面的checkall屬性置為true。
現在 我們學了循環指令,結合它來做一個表格看看。現在有了強大無比的orderBy, limitBy, filterBy, selectBy。我們做高性能的大表格是得心應手的!
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<script src="./dist/avalon.js" ></script>
<script>
if (!Date.now) {//fix 舊式IE
Date.now = function() {
return new Date - 0;
}
}
var model = avalon.define({
$id: "test",
selected: "name",
options: ["name", "size", "date"],
trend: 1,
data: [
{name: "aaa", size: 213, date: Date.now() + 20},
{name: "bbb", size: 4576, date:Date.now() - 4},
{name: "ccc", size: 563, date: Date.now() - 7},
{name: "eee", size: 3713, date: Date.now() + 9},
{name: "555", size: 389, date: Date.now() - 20}
]
})
</script>
</head>
<body ms-controller="test">
<div style="color:red">
<p>本例子用於顯示如何做一個簡單的表格排序</p>
</div>
<p>
<select ms-duplex="@selected">
<option ms-for="el in @options">{{el}}</option>
</select>
<select ms-duplex-number="@trend">
<option value="1">up</option>
<option value="-1">down</option>
</select>
</p>
<table width="500px" border="1">
<tbody >
<tr ms-for="el in @data | orderBy(@selected, @trend)">
<td>{{el.name}}</td> <td>{{el.size}}</td> <td>{{el.date}}</td>
</tr>
</tbody>
</table>
</body>
</html>

我們再來一個文本域與下拉框的聯動例子,它只用到ms-duplex,不過兩個控件都是綁定同一個屬性。
<!DOCTYPE html>
<html>
<head>
<script src="./dist/avalon.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script>
avalon.define({
$id: "fruit",
options: ["蘋果", "香蕉", "桃子", "雪梨", "葡萄", "哈蜜瓜", "橙子", "火龍果", "荔技", "黃皮"],
selected: "桃子"
})
</script>
</head>
<body ms-controller="fruit">
<h3>文本域與下拉框的聯動</h3>
<input ms-duplex="@selected" />
<select ms-duplex="@selected" >
<option ms-for="el in @options" ms-attr="{value: el}" >{{el}}</option>
</select>
</body>
</html>

<!DOCTYPE html>
<html>
<head>
<script src="./dist/avalon.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script>
var map = {
"中國": ["江南四大才子", "初唐四傑", "戰國四君子"],
"日本": ["日本武將", "日本城堡", "幕府時代"],
"歐美": ["三大騎士團", "三大魔幻小說", "七大奇跡"],
"江南四大才子": ["祝枝山", "文征明", "唐伯虎", "周文賓"],
"初唐四傑": ["王勃", "楊炯", "盧照鄰", "駱賓王"],
"戰國四君子": ["楚國春申君黃歇", "齊國孟嘗君田文", "趙國平原君趙勝", "魏國信陵君魏無忌"],
"日本武將": ["織田信長", "德川家康", "豐臣秀吉"],
"日本城堡": ["安土城", "熊本城", "大坂城", "姬路城"],
"幕府時代": ["鐮倉", "室町", "豐臣", "江戶"],
"三大騎士團": ["聖殿騎士團", "醫院騎士團", "條頓騎士團"],
"三大魔幻小說": ["冰與火之歌", "時光之輪", "荊刺與白骨之王國"],
"七大奇跡": ["埃及胡夫金字塔", "奧林匹亞宙斯巨像", "阿爾忒彌斯月神殿", "摩索拉斯陵墓", "亞歷山大港燈塔", "巴比倫空中花園", "羅德島太陽神巨像"]
}
var vm = avalon.define({
$id: 'linkage',
first: ["中國", "日本", "歐美"],
second: map['日本'].concat(),
third: map['日本武將'].concat(),
firstSelected: "日本",
secondSelected: "日本武將",
thirdSelected: "織田信長"
})
vm.$watch("firstSelected", function (a) {
vm.second = map[a].concat()
vm.secondSelected = vm.second[0]
})
vm.$watch("secondSelected", function (a) {
vm.third = map[a].concat()
vm.thirdSelected = vm.third[0]
})
</script>
</head>
<body >
<div ms-controller="linkage">
<h3>下拉框三級聯動</h3>
<select ms-duplex="@firstSelected" >
<option ms-for="el in @first" ms-attr="{value:el}" >{{el}}</option>
</select>
<select ms-duplex="@secondSelected" >
<option ms-for="el in @second" ms-attr="{value:el}" >{{el}}</option>
</select>
<select ms-duplex="@thirdSelected" >
<option ms-for="el in @third" ms-attr="{value:el}" >{{el}}</option>
</select>
</div>
</body>
</html>

這里的技巧在於使用$watch回調來同步下一級的數組與選中項。注意,使用concat方法來復制數組。
