在許多表單應用,我們經常遇到點擊一個復選框(或下拉框)會引發旁邊的復選框(或下拉框)發生改變,這種聯動效果用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>