迷你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裏是學習成本與維護成本極低的視圖層。這已經不是多了一個模板引擎這麼簡單的事,咱們搶到了 一直以來屬性於後端的禁臠——分層架構

相關文章
相關標籤/搜索