在許多表單應用,我們經常遇到點擊一個復選框(或下拉框)會引發旁邊的復選框(或下拉框)發生改變,這種聯動效果用avalon來做是非常簡單的。在avalon里,存在各種綁定回調與$watch回調,完全滿足你的需求。
下面是avalon所有綁定回調。
- data-duplex-changed,用於ms-duplex綁定,值改變后觸發; 參數:當前元素的值
- data-include-loaded,用於ms-include-src綁定,模板加載后觸發,可以在這里修改模板 參數: tmpl,vmodel1, vmodel2 ...
- data-include-rendered,用於ms-include, ms-include-src綁定,模板渲染好后觸發; 沒參數
- data-repeat-rendered,用ms-repeat綁定,當監控數組發生添加,刪除,重排等操作時觸發; 參數:當前操作名("add","del","index","clear","move")
- data-with-sorted,用ms-repeat, ms-with綁定,趕對象渲染之前觸發,要求輸出一個字符串數組,對象的鍵值對會根據它依次輸出; 參數:原對象的所有鍵名構成的數組
- data-with-rendered,用ms-with綁定,當目標對象輸出頁面后觸發; 參數:當前操作名("add","del","index","clear","move")
- data-each-rendered,用ms-each綁定,當監控數組發生添加,刪除,重排等操作時觸發; 參數:當前操作名("add","del","index","clear","move")
在表單聯動效果中,我們最常用的是data-duplex-change。事不宜遲,我們先來一個全選非全選例子吧。
<!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 = 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>
我們仔細分析其源碼,allchecked是用來控制最上面的復選框的打勾情況,數組中的checked是用來控制下面每個復選框的下勾情況。由於是使用ms-duplex,因此會監聽用戶行為,當復選框的狀態發生改變時,就會觸發data-duplex-change回調,將當前值傳給回調。但這里我們不需要用它的value值,只用它的checked值。最上面的復選框對應的回調是checkAll,它是用來更新數組的每個元素的checked屬性,因此一個forEach循環賦值就是。下面的復選框對應的checkOne,它們是用來同步最上面的復選框,只要它們有一個為false上面的復選框就不能打勾,當它們被打勾了,它們就得循環整個數組,檢查是否所有元素都為true,是才給上面的checkall屬性置為true。
再看用ms-duplex與$watch實現表格排序效果。
<!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="../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}
]
})
model.$watch("selected", function(v) {
var t = parseFloat(model.trend)
model.data.sort(function(a, b) {
if (v === "name") {
return t * a[v].localeCompare(b[v])
} else {
var ret = a[v] > b[v] ? 1 : -1
return t * ret
}
})
})
model.$watch("trend", function(t) {
var v = model.selected, t = parseFloat(t)
model.data.sort(function(a, b) {
var ret = a[v] > b[v] ? 1 : -1
return t * ret
})
})
</script>
</head>
<body ms-controller="test">
<div style="color:red">
<p>本例子用於顯示如何做一個簡單的表格排序</p>
<p>ms-repeat="array"相當於ms-repeat-el="array" </p>
</div>
<p>
<select ms-duplex="selected">
<option ms-repeat="options">{{el}}</option>
</select>
<select ms-duplex="trend">
<option value="1">up</option>
<option value="-1">down</option>
</select>
</p>
<table width="500px" border="1">
<tbody >
<tr ms-repeat="data">
<td>{{el.name}}</td> <td>{{el.size}}</td> <td>{{el.date}}</td>
</tr>
</tbody>
</table>
</body>
</html>
當我們改動下拉框時,會通過ms-duplex同步selected, trend屬性,而selected, trend的值改變時,就會觸發對應的$watch回調,然后通過監控數組的sort方法實現表格排序。從而也可以看出監控數組與ms-repeat的強大。
我們再來一個文本域與下拉框的聯動例子,它只用到ms-duplex,不過兩個控件都是綁定同一個屬性。
<!DOCTYPE html>
<html>
<head>
<script src="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-repeat-option="options" ms-value="option" >{{option}}</option>
</select>
</body>
</html>

我們再看一個超級復雜的三級聯動下拉框。
<!DOCTYPE html>
<html>
<head>
<script src="avalon.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script>
var map = {
"中國": ["江南四大才子", "初唐四傑", "戰國四君子"],
"日本": ["日本武將", "日本城堡", "幕府時代"],
"歐美": ["三大騎士團", "三大魔幻小說", "七大奇跡"],
"江南四大才子": ["祝枝山", "文征明", "唐伯虎", "周文賓"],
"初唐四傑": ["王勃", "楊炯", "盧照鄰", "駱賓王"],
"戰國四君子": ["楚國春申君黃歇", "齊國孟嘗君田文", "趙國平原君趙勝", "魏國信陵君魏無忌"],
"日本武將": ["織田信長", "德川家康", "豐臣秀吉"],
"日本城堡": ["安土城", "熊本城", "大坂城", "姬路城"],
"幕府時代": ["鐮倉", "室町", "豐臣", "江戶"],
"三大騎士團": ["聖殿騎士團", "醫院騎士團", "條頓騎士團"],
"三大魔幻小說": ["冰與火之歌", "時光之輪", "荊刺與白骨之王國"],
"七大奇跡": ["埃及胡夫金字塔", "奧林匹亞宙斯巨像", "阿爾忒彌斯月神殿", "摩索拉斯陵墓", "亞歷山大港燈塔", "巴比倫空中花園", "羅德島太陽神巨像"]
}
avalon.define("linkage", function(vm) {
vm.first = ["中國", "日本", "歐美"]
vm.second = map[vm.first[1]].concat()
vm.third = map[vm.second[0]].concat()
vm.firstSelected = "日本"
vm.secondSelected = "日本武將"
vm.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-repeat="first" ms-value="el" >{{el}}</option>
</select>
<select ms-duplex="secondSelected" >
<option ms-repeat="second" ms-value="el" >{{el}}</option>
</select>
<select ms-duplex="thirdSelected" >
<option ms-repeat="third" ms-value="el" >{{el}}</option>
</select>
</div>
</body>
</html>
由於存在三個下拉框,需要的數據比較多,因此我們搞了一個map來存放它們。。然后我們先初始化第一個下拉框,vm.first = [”中國”, ”日本”, ”歐美”],默認是選中第二個(firstSelected)。然后初始化第二個下拉框,需要從map拷貝一份賦給second,然后再默認其選中項,然后是第三個下拉框……
接着是通過$watch回調實現聯動,不用管第三個下拉框(因為它總是被動的),那么只有監聽firstSelected, secondSelected。每次變化都需要從map找到正確的數組,復制一次賦給second與third,並且默認選中第一項。
最后奉上一用戶做的多級聯動效果,自己細細品味吧。
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="avalon.js"></script>
<script>
var data = [
{"id": 0, "name": "1公司", "grade": -1, "parentid": "-1"},
{"id": 1, "name": "廣東公司", "grade": 0, "parentid": "0"},
{"id": 2012300001, "name": "廣州1分公司", "grade": 1, "parentid": 1},
{"id": 200000000, "name": "廣州分公司", "grade": 1, "parentid": 1},
{"id": 4050, "name": "天河分公司", "grade": 2, "parentid": 200000000},
{"id": 999182, "name": "工業園營銷服務中心", "grade": 3, "parentid": 4050},
{"id": 4174, "name": "南沙分公司", "grade": 2, "parentid": 200000000},
{"id": 10121, "name": "南沙營銷服務中心", "grade": 3, "parentid": 4174},
]
var a = avalon.define("test", function(vm) {
vm.searchForm = [];
vm.setSearchFormInit = function(currNode) {
var childNodes = vm.getChildOrg(currNode);
if (childNodes.length)
vm.searchForm = [childNodes];
};
vm.setSearchForm = function(index) {
//vm.searchForm._del(index,10)//在選中某個select后,取得index,刪除它后邊的所有成員
avalon.log("刪除第" + index + "個元素后的所有成員")
a.searchForm.removeAll(function(arr, i) {
if (i > index)
return true;
return false;
});
var currNode = this.value;
var childNodes = vm.getChildOrg(currNode);
if (childNodes.length)
vm.searchForm.push(childNodes);
};
vm.getChildOrg = function(id) {
var retList = [];
for (var i = 0; i < data.length; i++) {
var elem = data[i]
if (elem.parentid != id)
continue;
retList.push({
id: elem.id,
name: elem.name,
parentid: elem.parentid
});
}
return retList;
}
})
a.setSearchFormInit(0)
</script>
</head>
<body ms-controller="test">
<div ms-repeat-form="searchForm">
<select ms-change="setSearchForm($index)">
<option></option>
<option ms-repeat-el="form" ms-value="el.id">{{el.name}}</option>
</select>
</div>
</body>
</html>

