迷你MVVM框架 avalonjs 沉思录 第3节 动态模板

模板的发明是编程史上的一大里程碑,让咱们摆脱了烦锁且易出错的字符串拼接,维护性大大提升。javascript

都在JSP,ASP时代,人们已经学会使用include等语句,将多个页面片段拼接成一个页面。 此外,为了将数据库中的数据或业务中用到的变量输出到页面,咱们须要将页面某个地方标记一下,将变量塞到里面去。 最后,出于方便循环输出一组数据,就须要将each语句从HTML里撕开一道口子,加上其余什么if语句,页面上其实变撕裂成两部分 一种是与后端语言相近的逻辑部分,一个是够为纯净的HTML部分,到最后,模板引擎就发展出来。html

在jQuery王朝的后期,业务逻辑不断往前搬,前端模板也发明出来了。这些模板我统称为静态模板或字符串模板,特征是模板是放在 一个script标签或textarea标签里。静态模板的好处是统一管理,咱们从script标签等抽取内容时,它是原汁原味,没有被窜改。 缺点是破坏原有的结构。MVVM时代,knockout, ember等率先发明动态模板,或叫DOM模块,特色是经过在元素节点上标记一些特殊属性,注明此元素里面会输出什么内容 或此元素的子元素是做用循环体要循环多少次,固然if等输出不输出很小儿科。缺点是,须要对文档的总体或某一区域进行扫描,这里耗时比静态模板多上几倍,而且定界符(用于输出变量的标记)可能离奇失踪。但这也没什么大不了,如今流行的两种定界符形式≈lt&;, %>{{ }}在IE10+或W3C浏览器活得好好的,IE6-9,咱们只要避开大于小于号就好了。 此外动态模板与静态模板最大的不一样在于,它是没有编译函数,而是经过扫描文档,根据节点上的定界符与绑定属性实现循环输出,填空等功能。前端

咱们看一下avalon是怎么作的。大体分两块,定义VM,添加绑定。VM是咱们操做的主体,绑定是将页面变成模板的关键。java

VM的定义git

avalon.define("test",function(vm){
   vm.aaa = "司徒正美"
})

avalon.define是用来定义VM,第一个参数为VM的ID名,这是用于在页面圈定做用域的范围,对应的绑定属性是ms-controller。由于一个页面可能有多人负责,就存在多个VM了,而VM至关于一个数据据,它们都用于不一样的区域,这里就须要用ID来区分了。github

添加绑定,咱们随便往body一塞就好了数据库

<body>{{aaa}}</body>

这里的{{ }}是定界符,放在文本节点里。咱们能够用过avalon.config({interpolate: ["{?", "?}"]})来设置定界符。编程

<!DOCTYPE html>
<html>
    <head>
        <title>avalon</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
       <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js">   </script>
        <script>
            avalon.define("test", function(vm){
                vm.aaa = "司徒正美"
            })
        </script>
    </head>
    <body ms-controller="test">
        <h3>{{aaa}}</h3>
        {{aaa}}
    </body>
</html>

后端

但世界上没有这么简单的页面,好比咱们要输出一个列表,是否是要这样干呢?数组

<!DOCTYPE html>
<html>
    <head>
        <title>avalon</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script>
        <script>
            avalon.define("test", function(vm) {
                vm.a = "2014预言:阿里死磕港交所"
                vm.b = "马云进军游戏背后:恐失势电商"
                vm.c = "支付宝信息泄露揭大公司管理困境"
                vm.d = "盘点2013:智能手机开启的新场景"
            })
        </script>
    </head>
    <body ms-controller="test">
        <ol>
            <li>{{a}}</li>
            <li>{{b}}</li>
            <li>{{c}}</li>
            <li>{{d}}</li>
        </ol>
    </body>
</html>

固然不行,这要定义多少个变量啊!这时就需用到循环绑定,ms-repeat!

<!DOCTYPE html>
<html>
    <head>
        <title>avalon</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script>
        <script>
            avalon.define("test", function(vm) {
                vm.array = ["2014预言:阿里死磕港交所", 
                    "马云进军游戏背后:恐失势电商",
                    "支付宝信息泄露揭大公司管理困境", "盘点2013:智能手机开启的新场景"]
            })
        </script>
    </head>
    <body ms-controller="test">
        <ol>
            <li ms-repeat="array">{{el}}</li>
        </ol>
    </body>
</html>

ms-repeat至关于ms-each-el,后面的-el是可配置可省略。好比改为ms-repeat-elem,那么对应的位置要改为{{elem}}。

咱们还能够输出2维数组

<!DOCTYPE html>
<html>
    <head>
        <title>avalon</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script>
        <script>
            avalon.define("test", function(vm) {
                vm.array = [[1, 2], [4, 5], [7, 8]]
            })
        </script>
    </head>
    <body ms-controller="test">
        <table width="80%" border="1">
            <tr ms-repeat-item="array">
                <td ms-repeat="item">{{el}}</td>
            </tr>
        </table>
    </body>
</html>

输出对象数组

<!DOCTYPE html>
<html>
    <head>
        <title>avalon</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script>
        <script>
           var model =  avalon.define("test", function(vm) {
                vm.array = [{name: 111}, {name: 222}, {name: 333}]
            })
        </script>

    </head>
    <body ms-controller="test">
        <ul>
            <li ms-repeat-me="array">{{me.name}}</li>
        </ul>
    </body>
</html>

如今你们算是对ms-repeat算是有一个大致的了解吧。那么咱们学一点高级的。avalon.define会返回一个VM对象,咱们经过操做它就能实现页面的操做数据即操做DOM!!!,好比vmodel.array.push。另,咱们想输出每一个元素对应的索引值,可使用$index这个变量。

<!DOCTYPE html>
<html>
    <head>
        <title>avalon</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script>
        <script>
           var model =  avalon.define("test", function(vm) {
                vm.array = [{name: 111}, {name: 222}, {name: 333}]
            })
            var array = model.array
            setInterval(function(){
                array.push({name: Math.random().toString(32).substr(4,14)})
                if(array.length > 10){
                    array.shift()
                }
            },500)
        </script>

    </head>
    <body ms-controller="test">
        <ul>
            <li ms-repeat="array">{{$index}}--{{el.name}}</li>
        </ul>
    </body>
</html>


之因此 能有这样神奇的效果,是由于avalon会将VM中的数组转换为监控数组,它拥有如下方法:

push, shift, unshift, pop, slice, splice, remove, removeAt, removeAll, clear, ensure, sort, reverse, set

如今咱们跳前一步,学一下ms-on-*绑定,实现一个更复杂的效果。ms-on-*的*对应一个事件名,属性值为VM中一个函数名,与元素onkeypress, onclick同样,它的第一个参数默认是事件对象,this指向元素节点,不一样的是咱们已经对IE6-8下的事件对象作了兼容处理。

<!DOCTYPE HTML>
<html>
    <head>
        <title>ms-repeat</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 
        <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script>
        <script>
            avalon.define("test", function(vm) {
                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, 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-on-keypress="push"></p>
        <p>对数组进行unshift操做,并回车<input ms-on-keypress="unshift"></p>
        <p>对数组进行ensure操做,并回车<input ms-on-keypress="ensure"><br/>
            (只有数组不存在此元素才push进去)</p>
        <p>对数组进行remove操做,并回车<input ms-on-keypress="remove"></p>
        <p>对数组进行removeAt操做,并回车<input ms-on-keypress="removeAt"></p>
        <p><button type="button" ms-on-click="sort">对数组进行sort操做</button></p>
        <p><button type="button" ms-on-click="reverse">对数组进行reverse操做</button></p>
        <p><button type="button" ms-on-click="shift">对数组进行shift操做</button></p>
        <p><button type="button" ms-on-click="pop">对数组进行pop操做</button></p>
        <p>当前数组的长度为<span style="color:red">{{array.size()}}</span>,注意 咱们没法修改数组length的固有行为,所以它没法同步视图,须要用size方法。</p>
    </body>
</html>

有了批量输出的ms-repeat及经过调用监控数组的方法就能实现对应节点的删除添加排序,那么实现grid简直易如反掌。

<!DOCTYPE HTML>
<html>
    <head>
        <title>ms-repeat</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 
        <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script>
        <script>
            if (!Date.now) {
                Date.now = function() {
                    return new Date - 0;
                }
            }
            avalon.define('test', function(scope) {
                scope.selected = "name"
                scope.options = ["name", "size", "date"]
                scope.trend = 1
                scope.data = [
                    {name: "aaa", size: 213, date: Date.now() + 20},
                    {name: "bbb", size: 4576, date: new Date - 4},
                    {name: "ccc", size: 563, date: new Date - 7},
                    {name: "eee", size: 3713, date: 9 + Date.now()},
                    {name: "555", size: 389, date: Date.now() - 20}
                ];
                scope.$watch("selected", function(v) {
                    var t = parseFloat(scope.trend)
                    scope.data.sort(function(a, b) {
                        var ret = a[v] > b[v] ? 1 : -1
                        return t * ret
                    })
                })
                scope.$watch("trend", function(t) {
                    var v = scope.selected, t = parseFloat(t)
                    scope.data.sort(function(a, b) {
                        var ret = a[v] > b[v] ? 1 : -1
                        return t * ret
                    })
                })
            });


        </script>
    </head>
    <body ms-controller="test">
        <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, $watch,你们能够到《入门教程》看看,都是很简单的东西。

接着咱们再看看如何循环输出对象吧,它也是用ms-repeat,不过里面的变量为$key, $val。不用多言,看例子。

<!DOCTYPE html>
<html>
    <head>
        <title>avalon</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script>
        <script>
            var model = avalon.define("test", function(vm) {
                vm.object = {
                    grape: "葡萄",
                    coconut: "椰子",
                    pitaya: "火龙果",
                    orange: "橙子"
                }
                
            })

        </script>

    </head>
    <body ms-controller="test">
        <ul>
            <li ms-repeat="object">{{$key}}--{{$val}}</li>
        </ul>
    </body>
</html>

好了,循环输出就到这里。咱们最后看一下如何实现其余模板引擎的if语句。它的名字为ms-if,若是值为真就输出,不然不输出。

<!DOCTYPE html>
<html>
    <head>
        <title>avalon</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script>
        <script>
            var model = avalon.define("test", function(vm) {
                vm.toggle = true
                vm.click = function(){
                    vm.toggle = !vm.toggle
                }
                vm.text = "捉迷藏"
                
            })

        </script>

    </head>
    <body ms-controller="test">
        <p ms-if="toggle">{{text}} </p>
        <button type="button" ms-on-click="click">点我{{toggle ?  '隐藏' : "显示"}}</button>
    </body>
</html>

{{}}, ms-repeat, ms-if这就是动态模板相对静态模板的全部功能了,但因为动态模板在扫描以后,获得全部要处理的节点的引用,这也意味着,之后咱们要作一小部分的更新,不用像静态模板那样大规模替换,而是细化到每个元素节点,特性节点或文本节点。这就是所谓的“最小化刷新”技术。通常的,只有ms-if等少许绑定才影响到元素节点那一层面,更多的时候, 咱们是在刷新特性节点的value值,文本节点的data值,这也意味着,咱们的刷新不会引发reflow。加之,能获得元素节点本上,咱们就能够轻松实现绑定事件,操做样式,修改属性等功能。这也是为何大多数MVVM框架选择动态模板的缘故,jQuery原来能够作的,咱们所有经过绑定属性或定界符在HTML里搞定。 这也意味着,咱们实现了完美的分层架构,JS里面是纯粹的模型层(包括model与viewmodel),HTML里是学习成本与维护成本极低的视图层。这已经不是多了一个模板引擎这么简单的事,咱们抢到了 一直以来属性于后端的禁脔——分层架构

相关文章
相关标签/搜索