双工绑定

双工绑定是MVVM框架中最强大的指令.react推崇单向数据流,没有双工绑定,那么需要rudex等额外的库来实现相同的功能.

双工绑定只要用于表单元素上.或当一个div设置了contenteditable为true,也可以用ms-duplex指令.

各个表单元素的用法

  1. <body ms-controller="test">
  2. <script>
  3. avalon.define({
  4. $id: 'test',
  5. aaa: 'aaa',
  6. bbb: 'bbb',
  7. ccc: 'ccc'
  8. })
  9. </script>
  10. <input ms-duplex="@aaa"/>{{@aaa}}
  11. <input ms-duplex="@bbb" type="password"/>{{@bbb}}
  12. <textarea ms-duplex="@ccc" /></textarea>{{@ccc}}
  13. </body>

上面有三个控件,text, password, textarea它们都是属于输入型控件, 只要每为控件敲入一个字符,后面的文本都会立即变化.那是因为它们默认是绑定oninput事件,如果想控件全部输入好,失去焦点时才同步,那么可以使用change过滤器

  1. <input ms-duplex="@aaa | change"/>{{@aaa}}

如果你是做智能提示, 控件是绑定了一个AJAX请求与后端不断交互, 使用oninput事件会太频繁,使用onchange事件会太迟钝,那么我们可以使用debounce过滤器

  1. <input ms-duplex="@aaa | debounce(300)"/>{{@aaa}}

300ms同步一次.

另外,可编辑元素的用法与过滤器与上面三种控件一样.

  1. <div contenteditable="true" ms-duplex="@aaa | debounce(300)"/></div>
  2. <p>{{@aaa}}</p>

这两个过滤器只能适用于上面的情况.

此外, 控件还有许多种, 像checkbox, radio,它们的同步机制也不一样.

  1. <body ms-controller="test">
  2. <script>
  3. avalon.define({
  4. $id: 'test',
  5. aaa: '33',
  6. bbb: ['22']
  7. })
  8. </script>
  9. <input type="radio" value="11" ms-duplex="@aaa"/>
  10. <input type="radio" value="22" ms-duplex="@aaa"/>
  11. <input type="radio" value="33" ms-duplex="@aaa"/>
  12. <input type="checkbox" value="11" ms-duplex="@bbb"/>
  13. <input type="checkbox" value="22" ms-duplex="@bbb"/>
  14. <input type="checkbox" value="33" ms-duplex="@bbb"/>
  15. <p>radio: {{@aaa}}; checkbox:{{@bbb}}</p>
  16. </body>

checkbox与radio是一点击就会更新.radio要求在vm中为一个简单数据类型数据,字符串,数字或布尔.而checkbox则要求是一个数组.并且在最开始时,ms-duplex会令radio钩上其value值等vm属性的控件,checkbox则可以勾选多个.如此一来,vm中的属性些总是等于radio与checkbox的属性值.但我们也可以让vm的属性值等于此控件的勾选状态,这时需要用上ms-duplex-checked转换器.

  1. <body ms-controller="test">
  2. <script>
  3. avalon.define({
  4. $id: 'test',
  5. aaa: false,
  6. bbb: false
  7. })
  8. </script>
  9. <input type="radio" ms-duplex-checked="@aaa"/>
  10. <input type="checkbox" ms-duplex-checked="@bbb"/>
  11. <p>radio: {{@aaa}}; checkbox:{{@bbb}}</p>
  12. </body>

最后表单元素还有select控件,它根据其multiple属性分为单选下拉框与复选下拉框,其在vm中的值与radio,checkbox一样.即单选时,必须是一个简单数据类型, 复选时为一个数组.在最开始时, 当option元素的value值或innerText(不在value值)与数据相同,它们就会被选上.

  1. <body ms-controller="test">
  2. <script>
  3. avalon.define({
  4. $id: 'test',
  5. aaa: 'bbb',
  6. bbb: ['bbb','ccc'],
  7. })
  8. </script>
  9. <select :duplex="@aaa"><option>aaa</option><option>bbb</option><option>ccc</option></select>
  10. <select multiple="true" :duplex="@bbb"><option>aaa</option><option>bbb</option><option>ccc</option></select>
  11. </body>
控件触发时机数据
text,password,textarea及可编辑元表oninput,onchange, debounce简单数据
radio,checkboxonclick简单数据或数组
selectonchange简单数据或数组

数据转换

上面我们已经提到一个数据转换器ms-duplex-checked了.那只能用于checkbox与radio.

为什么会有这种东西呢?因为无论我们原来的数据类型是什么,跑到表单中都会变成字符串,然后我们通过事件取出来它们也是字符串,不会主动变回原来的类型.我们需要一种机制保持数据原来的类型,这就是数据转换器.

avalon内置了4种过滤器

  • 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同步时,转换成预期的数据。

  1. <input value="11" ms-duplex-number="@aaa"/>

数据格式化

一般来说,数据格式化是由过滤器实现的,如

  1. <input value="11" ms-duplex="@aaa | uppercase"/>

但这里有一个隐患,可能导致死循环, 因此建议放在事件回调中实现.

  1. <body ms-controller="test">
  2. <script>
  3. var vm = avalon.define({
  4. $id: 'test',
  5. aaa: '111',
  6. bbb: '222',
  7. format1: function(e){//只能输入数字
  8. vm.aaa = e.target.value.replace(/\D/g,'')
  9. },
  10. format1: function(e){//只能输入数字
  11. vm.bbb = avalon.filter.date(e.target.value, 'yyyy-MM-dd')
  12. }
  13. })
  14. </script>
  15. <input :duplex="@aaa" :on-input="@format1"/>{{@aaa}}
  16. <input :duplex="@bbb" :on-change="@format2"/>{{@bbb}}
  17. </body>

数据格式化是放在属性值时,以过滤器形式存在,如

  1. ms-duplex='@aaa | uppercase'
  2. ms-duplex='@aaa | date('yyyy:MM:dd')'

数据验证

这必须在所有表单元素的上方form元素加上ms-validate指令,当前元素加上ms-rules才会生效

  1. <form ms-validate="@validation">
  2. <input ms-duplex='@aaa'
  3. ms-rules='require,email,maxlength'
  4. data-maxlength='4'
  5. data-maxlength-message='太长了' >
  6. </form>

详见ms-rules指令

同步后的回调

ms-duplex还有一个回调,data-duplex-changed,用于与事件绑定一样, 默认第一个参数为事件对象。如果传入多个参数,那么使用$event为事件对象占位。

  1. <input value="11" ms-duplex-number="@aaa" data-duplex-changed="@fn"/>

示例

现在我们来一些实际的例子!

全选与非全选

  1. var vm = avalon.define({
  2. $id: "duplex1",
  3. data: [{checked: false}, {checked: false}, {checked: false}],
  4. allchecked: false,
  5. checkAll: function (e) {
  6. var checked = e.target.checked
  7. vm.data.forEach(function (el) {
  8. el.checked = checked
  9. })
  10. },
  11. checkOne: function (e) {
  12. var checked = e.target.checked
  13. if (checked === false) {
  14. vm.allchecked = false
  15. } else {//avalon已经为数组添加了ecma262v5的一些新方法
  16. vm.allchecked = vm.data.every(function (el) {
  17. return el.checked
  18. })
  19. }
  20. }
  21. })
  22. <table ms-controller=" duplex1" border="1">
  23. <tr>
  24. <td><input type="checkbox"
  25. ms-duplex-checked="@allchecked"
  26. data-duplex-changed="@checkAll"/>全选</td>
  27. </tr>
  28. <tr ms-for="($index, el) in @data">
  29. <td><input type="checkbox" ms-duplex-checked="el.checked" data-duplex-changed="@checkOne" />{{$index}}::{{el.checked}}</td>
  30. </tr>
  31. </table>

我们仔细分析其源码,allchecked是用来控制最上面的复选框的打勾情况, 数组中的checked是用来控制下面每个复选框的下勾情况。由于是使用ms-duplex,因此会监听用户行为, 当复选框的状态发生改变时,就会触发data-duplex-changed回调,将当前值传给回调。 但这里我们不需要用它的value值,只用它的checked值。

最上面的复选框对应的回调是checkAll,它是用来更新数组的每个元素的checked属性,因此一个forEach循环赋值就是。

下面的复选框对应的checkOne,它们是用来同步最上面的复选框,只要它们有一个为false上面的复选框就不能打勾, 当它们被打勾了,它们就得循环整个数组,检查是否所有元素都为true,是才给上面的checkall属性置为true。

现在我们学了循环指令,结合它来做一个表格看看。现在有了强大无比的orderBy, limitBy, filterBy, selectBy。 我们做高性能的大表格是得心应手的!

  1. if (!Date.now) {//fix 旧式IE
  2. Date.now = function() {
  3. return new Date - 0;
  4. }
  5. }
  6. avalon.define({
  7. $id: "duplex2",
  8. selected: "name",
  9. options: ["name", "size", "date"],
  10. trend: 1,
  11. data: [
  12. {name: "aaa", size: 213, date: Date.now() + 20},
  13. {name: "bbb", size: 4576, date:Date.now() - 4},
  14. {name: "ccc", size: 563, date: Date.now() - 7},
  15. {name: "eee", size: 3713, date: Date.now() + 9},
  16. {name: "555", size: 389, date: Date.now() - 20}
  17. ]
  18. })
  1. <div ms-controller=" duplex2">
  2. <div style="color:red">
  3. <p>本例子用于显示如何做一个简单的表格排序</p>
  4. </div>
  5. <p>
  6. <select ms-duplex="@selected">
  7. <option ms-for="el in @options">{{el}}</option>
  8. </select>
  9. <select ms-duplex-number="@trend">
  10. <option value="1">up</option>
  11. <option value="-1">down</option>
  12. </select>
  13. </p>
  14. <table width="500px" border="1">
  15. <tbody >
  16. <tr ms-for="el in @data | orderBy(@selected, @trend)">
  17. <td>{{el.name}}</td> <td>{{el.size}}</td> <td>{{el.date}}</td>
  18. </tr>
  19. </tbody>
  20. </table>
  21. </div>

我们再来一个文本域与下拉框的联动例子,它只用到ms-duplex,不过两个控件都是绑定同一个属性。

  1. avalon.define({
  2. $id: "fruit",
  3. options: ["苹果", "香蕉", "桃子", "雪梨", "葡萄",
  4. "哈蜜瓜", "橙子", "火龙果", "荔技", "黄皮"],
  5. selected: "桃子"
  6. })
  1. <div ms-controller=" fruit">
  2. <h3>文本域与下拉框的联动</h3>
  3. <input ms-duplex="@selected" />
  4. <select ms-duplex="@selected" >
  5. <option ms-for="el in @options" ms-attr="{value: el}" >
  6. {{el}}
  7. </option>
  8. </select>
  9. </div>

下拉框三级联动

  1. var map = {
  2. "中国": ["江南四大才子", "初唐四杰", "战国四君子"],
  3. "日本": ["日本武将", "日本城堡", "幕府时代"],
  4. "欧美": ["三大骑士团", "三大魔幻小说", "七大奇迹"],
  5. "江南四大才子": ["祝枝山", "文征明", "唐伯虎", "周文宾"],
  6. "初唐四杰": ["王勃", "杨炯", "卢照邻", "骆宾王"],
  7. "战国四君子": ["楚国春申君黄歇", "齐国孟尝君田文", "赵国平原君赵胜", "魏国信陵君魏无忌"],
  8. "日本武将": ["织田信长", "德川家康", "丰臣秀吉"],
  9. "日本城堡": ["安土城", "熊本城", "大坂城", "姬路城"],
  10. "幕府时代": ["镰仓", "室町", "丰臣", "江户"],
  11. "三大骑士团": ["圣殿骑士团", "医院骑士团", "条顿骑士团"],
  12. "三大魔幻小说": ["冰与火之歌", "时光之轮", "荆刺与白骨之王国"],
  13. "七大奇迹": ["埃及胡夫金字塔", "奥林匹亚宙斯巨像", "阿尔忒弥斯月神殿", "摩索拉斯陵墓", "亚历山大港灯塔", "巴比伦空中花园", "罗德岛太阳神巨像"]
  14. }
  15. var vm = avalon.define({
  16. $id: 'linkage',
  17. first: ["中国", "日本", "欧美"],
  18. second: map['日本'].concat(),
  19. third: map['日本武将'].concat(),
  20. firstSelected: "日本",
  21. secondSelected: "日本武将",
  22. thirdSelected: "织田信长"
  23. })
  24. vm.$watch("firstSelected", function (a) {
  25. vm.second = map[a].concat()
  26. vm.secondSelected = vm.second[0]
  27. })
  28. vm.$watch("secondSelected", function (a) {
  29. vm.third = map[a].concat()
  30. vm.thirdSelected = vm.third[0]
  31. })
  1. <div ms-controller=" linkage">
  2. <h3>下拉框三级联动</h3>
  3. <select ms-duplex="@firstSelected" >
  4. <option ms-for="el in @first" ms-attr="{value:el}" >{{el}}</option>
  5. </select>
  6. <select ms-duplex="@secondSelected" >
  7. <option ms-for="el in @second" ms-attr="{value:el}" >{{el}}</option>
  8. </select>
  9. <select ms-duplex="@thirdSelected" >
  10. <option ms-for="el in @third" ms-attr="{value:el}" >{{el}}</option>
  11. </select>
  12. </div>

这里的技巧在于使用$watch回调来同步下一级的数组与选中项。注意,使用concat方法来复制数组。