迷你MVVM框架 avalonjs1.5 入門教程

avalon通過幾年之後,已成爲國內一個舉足輕重的框架。它提供了多種不一樣的版本,知足不一樣人羣的須要。好比avalon.js支持IE6等老舊瀏覽器,讓許多靠政府項目或對兼容性要求夠高的公司也能享受MVVM的樂趣。avalon.modern.js支持IE10以上版本,優先使用新API,性能更優,體積更少。avalon.mobile.js在avalon.modern的基礎提供了觸屏事件的支持,知足你們在移動開發的需求。此外,它們分別存在avalon.xxx.shim版本,指無自帶加載器版,avalon.xxx.min版本,指上線壓縮版本。javascript

avalon早期嚴重受到angular與knockout的影響,API與它們很相近,通過多年的發展,漸漸摸索出本身一套模式。騰訊百度阿里等大公司都有部門在使用avalonjs,這有力促進avalon的發展。avalon1.5是一個里程碑的版本,它帶來許多全新的特性,讓咱們編寫代碼更加爽快。css

avalon1.5的下載地址: https://github.com/RubyLouvre/avalon/tree/1.5html

  1. 視圖模型
  2. 非監控屬性與監控屬性
  3. 視圖模型的做用域
  4. 掃描機制
  5. 指令(綁定)
  6. 數據填充(ms-text, ms-html)
  7. 模板綁定(ms-include)
  8. 類名切換(ms-class, ms-hover, ms-active)
  9. 事件綁定(ms-on,……)
  10. 顯示綁定(ms-visible)
  11. 插入綁定(ms-if)
  12. 雙工綁定(ms-duplex)
  13. 樣式綁定(ms-css)
  14. 數據綁定(ms-data)
  15. 屬性綁定(ms-attr)
  16. 循環綁定(ms-repeat,ms-each,ms-with)
  17. 動畫綁定(ms-effect)
  18. 自定義標籤組件
  19. 模塊間通訊及屬性監控 $watch,$fire
  20. 過濾器
  21. 自定義指令(綁定)
  22. 加載器
  23. AJAX
  24. 路由系統
  25. 在IE6下調試avalon
  26. avalon1.4.6到1.5的升級指南
  27. 其餘要注意的地方(更新VM等)

視圖模型

avalon與jQuery最大的一個區別是,思惟的不一樣。jQuery要操做一個元素,老是設法找到此元素,想象這個元素是否有ID,有某個類名,存在某個特定的標籤下,是父節點的第幾個孩子,諸如此類,最後拼湊出一個CSS表達式,而後$(expr)找到元素,而後再進行操做,因而JS代碼裏滿屏$。維護代碼的人,老是要對着頁面來看看,這表達式是對應某某元素,若是隻有ID,類名還好,新手非常寫出很長的CSS表達式,致使你最後崩潰掉。前端

avalon要操做某個元素,就直接在HTML爲它添加一些指令,這些指令或者以ms-開頭的元素屬性,或是標籤之間的4個花括號。指令裏面存在某些變量,這些變量最後在JS彙集成一個對象,這就叫作VM(View Model, 視圖模型)。咱們只要操做這個VM的數據變更就好了,頁面上就會自動變化。有了這一層的分離,咱們在代碼量就少能許多操做DOM的代碼,專致於業務自己。好比說:java

<p>{{aaa}}</p>

至關於jQuery的如下代碼:node

$(function(){
   $("p").text(aaa)
})

那咱們看看怎麼定義一個VM吧。avalon在1.5以前存在兩種定義方式,如今1.5只支持新風格,即git

var vm = avalon.define({
    $id: "test",
    a: 1,
    b: 2,
    c: {
      d: 1
    },
    onClick: function(e){
       e.preventDefault()
    },
    arr: [1,2,3]
})

avalon.define是一個很是重要的方法,要求傳入一個對象,對象裏面必須有$id屬性,它是用於指定其在頁面的做用範圍。github

avalon.define會返回一個新對象,它除了以前咱們定的屬性與方法,還添加了$watch, $events, $fire, $model等屬性與方法。web

當咱們以vm.a = 4來從新賦值時,頁面上用到a的地方會天然做出反應,這個行爲稱之爲綁定,有的屬性會使用ms-duplex指令綁定到表單元素上,這時反應是雙向的,input,select, textarea的值被用戶改動時,會天然反應到VM上,而咱們對VM上的操做也會反應到表單元素上,這叫作雙工綁定ajax

有的東西,你壓底只有它只做用一次,如大表格的數據展現,之後沒有任何互動交互,那咱們有幾種方式:

  • 將該屬性的名字以$開頭,以$aaa,這樣標識它爲非監控屬性。
  • 將該屬性的名字放到$skipArray數組,也能標識它非監控屬性,由於有的名字須要先後一致,後端不肯意加$,這種方式更靈活。
  • 有的屬性我想它在這裏能夠屢次改動,有的則顯示就不改了,可使用單向綁定,須要在ms-*屬性的值前面加::,或花括號內部的前面加::
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script src="avalon.js"></script>
        <script>
            var vm = avalon.define({
                $id: "test",
                a: 1,
                $b: 2,
                $skipArray: ["a"],
                c: 3
            })
            setTimeout(function () {
                vm.a = 100
                vm.$b = 100
                vm.c = 100
            }, 3000)
        </script>
    </head>
    <body>
        <div ms-controller="test">
            <p>{{a}}不會變</p>
            <p>{{$b}}不會變</p>
            <p>{{::c}}不會變</p>
            <p>{{c}}會變</p>
        </div>
    </body>
</html>

非監控屬性與監控屬性

VM是一個對象,它除了包含一些必須的方法與屬性外,其餘的東西就分爲兩大類,非監控屬性與監控屬性。

非監控屬性,就是咱們上面指的以$開頭,或是名字定義在$skipArray數組的東西,此外,當某屬性的值的類型爲函數或元素節點,文本節點,註釋節點,文檔對象,文檔碎片與window時,它們也沒法監控。

監控屬性則分爲4類:

  • 當其值爲字符串,數值,布爾,undefined, null等簡單數據類型時,爲監控屬性
  • 當它定義在$computed對象中時,其值爲一個對象,擁有get,set方法,爲計算屬性。計算屬性老是有一個或一個以上的監控屬性構成。
  • 當其值的類型爲數組時,稱之爲監控數組,咱們能夠改動它的方法,來同步視圖。在1.5中,咱們不斷能夠監聽其長度變化,還能夠監聽其元素或元素的屬性變化。
  • 當其值爲一個對象時,它裏面的對象也繼續轉換爲計算屬性,監控屬性,這個對象咱們稱之爲子VM

在1.5以前的版本,還有一個叫監控函數的東西,即裏面包含了某些監控方法。但如今咱們不建議這樣用,由於在將來的版本,咱們打算像angular那樣經過純靜態詞法分析,就能獲得此指令所依賴的全部監控屬性。而監控方法則須要使用動態的依賴檢測實現。動態依賴檢測雖然很是強大,但也很是耗性能。在1.5以前,avalon是徹底經過動態依賴檢測實現綁定的,1.5是結合靜態詞法分析與動態依賴檢測,將來會一點點改成純靜態詞法分析。

            var vm = avalon.define({
                $id: "test",
                a: 1,
                $b: 2,
                $skipArray: ["a"],
                c: 3, //監控屬性
                d: {  //這是子VM
                    dd: {
                        ddd: 3
                    },
                    dd2: 4
                },
                arr: [1, 2, 3, 4], //監控數組
                $computed: {
                    c: {//計算屬性c
                        get: function () {
                            return this.a + " " + this.c
                        },
                        set: function (val) {
                            var arr = val.split(" ")
                            this.a = arr[0]
                            this.b = arr[1]
                        }
                    },
                    e: {//計算屬性e
                        get: function () {
                            return this.a + 100
                        }
                    }
                }
            })

視圖模型的做用域

爲了方便協做開發的需求,咱們引入了做用域的概念。由於一個頁面可能很大,分爲N個模塊,每一個模塊交同不一樣的人來編寫。這個在移動端的SPA應用中尤其明顯。 對於JS,咱們能夠拆分爲N個JS文件,每一個JS文件都有本身的VM。頁面也是拆成一塊塊,這能夠經過PHP或nodejs的模板貼合起來。而在這以前,咱們先爲它們加上ms-controller!

ms-controller爲一個指令,其值爲一個VM的$id,如ms-controller="test",它就會在avalon.vmodels中找到該VM,而後這個元素下方用到的全部指令中的變量,都應該位於此VM。

但若是一個功能模塊特別複雜,它用到的字段特別多,意味着這個VM也要定義許多許多屬性,而這些屬性的某一部分也在其餘頁面或模塊用到,這時咱們就須要對它進行拆分,方便重用。拆分後的兩個對象或N個對象,avalon容許咱們以ms-controller套ms-controller的形式,實現做用域間的數據共享。換言之,若是某變量在當前的VM換不到,它就會往上找,在上面的VM中查找此屬性,一直找到爲止。這有點像JS的對象屬性查找,其實,它像CSS的做用域查找,由於咱們還引入了ms-important。ms-important的寓意就是CSS中的important!符號,就在此做用域查找,不往外找!

此外,還有些地方,你不想avalon來處理它們,如script標籤的內容,style標籤的內容,文章的語法高亮部分,引用別人文章的部分,這個可使用ms-skip指令來繞開這些無用的區域。

至少,咱們學習了ms-controller, ms-important, ms-skip, 更詳細能夠到新官網上學習

掃描機制

avalon能實現VM與視圖之間的互動,最關鍵的東西就是這個。在有的MVVM框架,這也叫作編譯(compile),意即,將視圖的某一部分的全部指令所有抽取出來,轉換爲一個個視圖刷新函數,而後放到一個個數組中,當VM的屬性變更時,就會執行這些數組的函數。固然數組裏面的東西不定是函數,也多是對象,但裏面確定有個視圖刷新函數。這是MVVM框架的核心機制,但怎麼抽取出來,每一個框架的方式都不同。avalon將這個過程稱之爲掃描。掃描老是從某個節點開始。在avalon內部,已經默認進行了一次掃描,從body元素開始描。若是咱們爲頁面插入了什麼新內容,而這個區域裏面又包括了avalon指令,那麼咱們就須要手動掃描了。

avalon.scan是avalon第二重要的API,它有兩個參數,第一個是元素節點,第二個是數組,裏面爲一個個VM。固然這兩個參數是可選的。但當你手動掃描時,最好都會進去,這樣會加快掃描速度,並減小意外。由於全部指令,都掃描後就變移除掉,這包括指定VM用的ms-controller,ms-important!

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script src="avalon.js"></script>
        <script>
            avalon.ready(function () {
                var div = document.createElement("div")
                div.innerHTML = "{{aaa}}"
                div.setAttribute("ms-controller", "eee")
                document.body.appendChild(div)
                var vm = avalon.define({
                    $id: "eee",
                    aaa: 111
                })
                avalon.scan(div, vm)
            })
        </script>
    </head>
    <body>

    </body>
</html>

指令(綁定)

指令是指寫在HTML中的特殊符號,包括如下幾種,ms-開頭的綁定屬性,寫在innerText裏面的{{}}的插值表達式,相似data-duplex-xxx的輔助指令(data-後面跟着的綁定屬性的名字,它們必須與綁定元素定義在同一元素),還有新添加的自定義標籤(它們必須帶有:號)

新手們或從angular過來的人很容易犯一個錯誤,就是直接在屬性值裏面加一個{{}},覺得就能綁定了,卻不知avalon爲了性能優化,會跳過全部非ms-*屬性。

這裏擁有全部指令的一覽圖

數據填充

這裏提供ms-text, ms-html兩種指令,其餘ms-text擁有{{expr}}這個變體,ms-html擁有{{expr|html}}這個變體。當大家頁面也使用後端模板拼湊而成時,可能 後端會佔用了{{}}界定符,咱們能夠經過如下配置方式從新指定界定符

avalon.config({
   interpolate:["{%","%}"]
})

而且咱們能夠經過avalon.config.openTag, avalon.config.closeTag獲得「{%」,"%}"。注意,界定符裏面千萬別出現<, >,由於這存在兼容性問題。這兩個界定符也不能同樣,最好它們的長度都大於1。

         <script>
            
          avalon.define({
              $id: "test",
               text: "<b> 1111  </b>"
          })
 
         </script>
         <div ms-controller="test">
             <div><em>用於測試是否被測除</em>xxxx{{text}}yyyy</div>
             <div><em>用於測試是否被測除</em>xxxx{{text|html}}yyyy</div>
             <div ms-text="text"><em>用於測試是否被測除</em>xxxx yyyy</div>
             <div ms-html="text"><em>用於測試是否被測除</em>xxxx yyyy</div>
         </div>

插值表達式{{}}在綁定屬性的使用只限那些能返回字符串的綁定屬性,如ms-attr、ms-css、ms-include、ms-class、 ms-href、 ms-title、ms-src等。一旦出現插值表達式,說明這個整個東西分紅可變的部分與不可變的部分,{{}}內爲可變的,反之亦然。 若是沒有{{}}說明整個東西都要求值,又如ms-include="'id'",要用兩種引號強制讓它的內部不是一個變量。

模板綁定

ms-include指令是ms-html的有效補充。咱們知道ms-html是將VM中某個符合HTML結構的字符串,放到某元素底下解析爲節點。但若是這個字符串很大,放在VM上就不合算,這時咱們就想到將它到頁面的某個位置上(如script, noscript, textarea等能放大片內容的特殊標籤)或乾脆獨立成一個HTML文件。因而前者叫作內部模板,由於是放在頁面的內部,後者叫作外部模板。對於前者,咱們使用ms-include=「expr」來引用,後者,咱們是使用ms-include-src="expr"來引用。src表示一個路徑,所以其值每每是一個URL地址,爲了你們方便拼接URL,咱們容許ms-include-src的值可使用插值表達式。如ms-include-src="aaa/{{bbb}}.html"。因爲咱們加載外部模板時是用AJAX實現的,所以你們在調試代碼時,必須打開WEB服務器。

<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
        <script src="avalon.js"></script>
        <script>
            avalon.define({
                 $id: "test",
                 xxx: "引入內部模板"
              })
        </script>
    </head>
    <body >

        <script type="avalon" id="tpl">
            here, {{ 3 + 6 * 5  }}
        </script>
        <div ms-controller="test">
            <p>{{xxx}}</p>
            <div ms-include="'tpl'"></div>
        </div>

    </body>
</html>
        

注意,ms-include的值要用引號括起,表示這只是一個字符串,這時它就會搜索頁面的具備此ID的節點,取其innerHTML,放進ms-include所在的元素內部。不然這個tpl會被當成一個變量, 框架就會在VM中檢測有沒有此屬性,有就取其值,重複上面的步驟。若是成功,頁面會出現here, 2的字樣。

若是你們想在模板加載後,加工一下模板,可使用data-include-loaded來指定回調的名字。

若是你們想在模板掃描後,隱藏loading什麼的,可使用data-include-rendered來指定回調的名字。

因爲ms-include綁定須要定義在一個元素節點上,它的做用僅僅是一個佔位符,提供一個插入位置的容器。 若是用戶想在插入內容後,去掉這容器,可使用data-include-replace="true"。

avalon在使用ms-include-src 加載外部模板時,會將它們存放到avalon.templateCache對象中,所以咱們能夠搞出一種架構出來,在上線前,將全部要遠程加載的模板所有打包到avalon.templateCache對象中,這樣它在發出請求前,先查找此對象,發現存在就不會發出請求了。

注意,不管是ms-include仍是ms-include-src都會在其值變化時,請空原元素的全部子孫節點,致使原有數據丟失,裏面用到的全部組件從新生成,若是保持原來的節點,可使用data-include-cache="true"輔助指令。

下面是一個經典的後臺系統框架!

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script src="avalon.js"></script>
        <script>
            avalon.templateCache = {
                aaa: "<div>這裏是很是複雜的HTML結構1</div>",
                bbb: "<div>這裏是很是複雜的HTML結構2</div>"
                ccc: "<div>這裏是很是複雜的HTML結構3</div>"
                ddd: "<div>這裏是很是複雜的HTML結構4</div>"
            }
            var vm =avalon.define({
                $id:"root",
                tabs:["aaa","bbb", "ccc","ddd"],//全部標籤頁的名字
                curTab: "aaa",
                switchTab: function(el){
                    vm.curTab = el
                },
                showLoading: function(){},
                hideLoading:function(){}
            })
        </script>
    </head>
    <body ms-controller="root">
        <table>
            <tr>
                <td>
                    <ul>
                        <li ms-repeat="tabs" ms-click="switchTab(el)">{{el}}</li>
                    </ul>
                </td>
                <td>
                    <!--主內容顯示區-->
                    <div ms-include-src="curTab" 
                         data-include-loaded="showLoading"
                         data-include-rendered="hideLoading"
                         >
                    </div>
                </td>
            </tr>
            
        </table>
    </body>
</html>

更詳細的內容可見新官網

類名切換(ms-class, ms-hover, ms-active)

avalon1.5如今只支持新風格,即ms-class="aaa: true"這種形式,此綁定屬性的值以冒號分爲兩部分,前面爲類名,後面表示添加或移除。 ms-class="aaa bbb ccc: toggle",當toggle在VM中爲true時,它會爲元素同時添加aaa, bbb, ccc三個類名。冒號及其後面的東西也不是必須的, 如ms-class="aaa1 bbb2",表示老是爲元素添加aaa1,bbb2這兩個類名。前面的部分也可使用插值表達式動態生成,如ms-class="{{className}}:true", className在VM是什麼,就會爲元素添加什麼類名。若是你想爲元素添加多個類名綁定,可使用ms-class-1="aaa: true" ms-class-2="bbb:toggle"來添加。

ms-hover, ms-active與ms-class的用法相同,但它們一個只在鼠標掠過元素上方時添加類名,移走時移除;另外一個則在元素得到焦點時(好比點擊)添加類名,失去焦點時移除。

更詳細的內容可見新官網

事件綁定

咱們能夠經過ms-on-*爲元素綁定各類事件,好比ms-on-click=fn,表示爲當前元素綁定點擊事件,fn爲VM的一個函數。默認地,咱們會爲fn傳入一個參數event,咱們已經爲它作了兼容處理,所以你在IE下也能使用preventDefault, stopPropagation, pageX, pageY, target, timeStamp, which等標準屬性與方法。若是你還想傳其餘參數,還想用事件對象,能夠用$event佔位。ms-on-click=fn(aaa, bbb, $events)。此外,咱們爲全部經常使用事件作了快捷處理,所以大家還能夠這樣用,ms-click=fn2, ms-mouseover=fn3, ms-mouseleave=fn4。 注意,事件綁定的值的第一個單詞必須是VM中的函數名字,你不能在其值裏面使用加減乘除,如 ms-click="aaa+bbb",這樣是不對的。若是 你想同時綁定多個點擊事件,用法與ms-class,ms-hover同樣,在後面加數字就好了。ms-click-1=fn1 ms-click-2=fn2 ms-click-3=fn3。

<!DOCTYPE HTML>
<html>
    <head>
        <title>ms-on</title>
        <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>
            var model = avalon.define({
                $id: "test",
                firstName: "司徒",
                array: ["aaa", "bbb", "ccc"],
                argsClick: function(e, a, b) {
                    alert([].slice.call(arguments).join(" "))
                },
                loopClick: function(a, e) {
                    alert(a + "  " + e.type)
                },
                status: "",
                callback: function(e) {
                    model.status = e.type
                },
                field: "",
                check: function(e) {
                    model.field = this.value + "  " + e.type
                },
                submit: function() {
                    var data = model.$model
                    if (window.JSON) {
                        setTimeout(function() {
                            alert(JSON.stringify(data))
                        })
                    }
                }
           })

        </script>
    </head>
    <body>
        <h3 style="text-align: center">ms-on-*</h3>
        <fieldset ms-controller="test">
            <legend>有關事件回調傳參</legend>
            <div ms-mouseenter="callback" ms-mouseleave="callback">{{status}}<br/>
              <input ms-on-input="check"/>{{field}}
            </div>
        <div ms-click="argsClick($event, 100, firstName)">點我</div>
            <div ms-each-el="array" >
                <p ms-click="loopClick(el, $event)">{{el}}</p>
            </div>
            <button ms-click="submit">點我</button>
        </fieldset>
    </body>
</html>

<!DOCTYPE HTML>
<html>
    <head>
        <title>ms-on</title>
        <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>
            var count = 0
            var vm = avalon.define({
                $id: "multi-click",
                str1: "1",
                str2: "2",
                str3: "3",
                click0: function () {
                    vm.str1 = "xxxxxxxxx" + (count++)
                },
                click1: function () {
                    vm.str2 = "xxxxxxxxx" + (count++)
                },
                click2: function () {
                    vm.str3 = "xxxxxxxxx" + (count++)
                }
            })
        </script>
    </head>
    <body>
        <fieldset>
            <legend>一個元素綁定多個同種事件的回調</legend>
            <div ms-controller="multi-click">
                <div ms-click="click0" ms-click-1="click1" ms-click-2="click2" >請點我</div>
                <div>{{str1}}</div>
                <div>{{str2}}</div>
                <div>{{str3}}</div>
            </div>
        </fieldset>
    </body>
</html>

一個綜合了ms-click, ms-class, 監控數組的例子:

<!doctype hmtl>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
        <title>attribute</title>
        <script type="text/javascript" src="avalon.modern.js"></script>
        <script>
            var vm = avalon.define({
                $id: "test",
                selected: [],
                array: avalon.range(0, 12),
                onClick: function (i) {
                    if (vm.selected.indexOf(i) === -1) {
                        vm.selected.push(i)
                    } else {
                        avalon.Array.remove(vm.selected, i)
                    }
                }
            })
        </script>
        <style>
            b{
                background: gray;
                -moz-border-radius: 15px;      /* Gecko browsers */
                -webkit-border-radius: 15px;   /* Webkit browsers */
                border-radius:15px;            /* W3C syntax */
                width:30px;
                height:30px;
                display: inline-block;
                line-height: 30px;
                text-align: center;
            }
            b.toggle{
                background: red;
            }
        </style>
    </head>

    <body ms-controller="test">
        <p ms-each="array">
            <b ms-click="onClick($index)" ms-class="toggle: selected.indexOf($index)!==-1">{{$index}}</b> 
        </p>
    </body>
</html>

更詳細的內容可見新官網

顯示綁定(ms-visible)

ms-visible="expr"是經過改變元素的style.display值來控制元素的顯示隱藏。

在1.5中結合動畫指令還可使用動畫效果。

<!DOCTYPE HTML>
<html>
    <head>
        <title>ms-visible</title>
        <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>
            avalon.define({
                $id: "test",
                a: true
            })
        </script>
        <style>
            .rect{
                width:100px;
                height:100px;
                background: red;
                display: none;
                border:1px solid red; 
                text-align: center
            }
        </style>
    </head>
    <body>
        <h3>ms-visible</h3>
        <div ms-controller="test">
            <p>點我隱藏或顯示下面的方塊<input ms-duplex-checked="a"  type="radio"></p>
            <div class="rect" ms-visible="a" >visible</div>
        </div>
    </body>
</html>

更詳細的內容可見新官網

插入綁定(ms-if)

ms-if 與 ms-visible的效果很像,都是不顯示元素,但ms-if更進一步,將元素移出原來的位置藏到別處。而且它還會延遲其內容元素的掃描與綁定,所以相對於ms-visible性能更好。若是你想ms-if 與ms-repeat用在同一元素,讓某個不合格的數組元素表明的那個地方不顯示出來,那你請務必使用ms-if-loop!

在1.5中結合動畫指令還可使用動畫效果。

<!DOCTYPE HTML>
<html>
    <head>
        <title>ms-if</title>
        <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>
        <style>
            .active{
                background: blueviolet;
            }
        </style>
        <script>
            var vm = avalon.define({
                $id: "test",
                data: {
                    toggle: true
                },
                toggle: function () {
                    vm.data.toggle = !vm.data.toggle
                }
            });

        </script>
    </head>
    <body>
        <div class="avalonHide">測試avalonHide</div>
        <div ms-controller="test">
            <div ms-if="data.toggle" class="active">能動態添加移除</div>{{data.toggle}}
            <p><button type='button' ms-click='toggle'>點我</button></p>
        </div>
    </body>
</html>

更詳細的內容可見新官網

雙工綁定(ms-duplex)

MVVM框架中最重要的功能之一,惟一一個能讓視圖同步VM的指令。值得注意的是,它綁定的元素只是 表單元素,input, textarea, select。

在avalon1.5中,ms-duplex只支持duplex2.0。所謂的duplex2.0是指, 咱們能夠從表單元素的checked屬性,value屬性抽取數據,轉變成所須要的字符串類型,數值類型,布爾類型或數組類型,或添加更多攔截器進行更多操做。

ms-duplex實際上是默認使用了string 攔截器,即至關於ms-duplex-string。

其餘內置攔截器分別是ms-duplex-number, ms-duplex-boolean, ms-duplex-checked

名字 應用元素 效果
ms-duplex-checked radio, checkbox 將其checked值同步到VM
ms-duplex-string 全部表單元素 將其value值同步到VM,
對於checkbox, select[multiple=true]會進一步轉換爲數組
ms-duplex-number 全部表單元素 將其value值同步到VM,
對於checkbox, select[multiple=true]會進一步轉換爲數組;
咱們還能夠經過data-duplex-number='strong|medium|weak'輔助指令來轉換非數值
爲strong時,不能轉的都轉換爲0;
爲medium時,空字符串會忽略轉換,不然轉換0或其餘數字;
爲weak時,不是數值形式的值不作轉換
ms-duplex-boolean 全部表單元素 將其value值同步到VM,
對於checkbox, select[multiple=true]會進一步轉換爲數組;
轉換方式爲,當其值爲「true」轉換爲true,其餘都轉換爲false

默認地,對於文本域,文本區,密碼域,框架是使用oninput事件進笨監聽,即用戶每改動一個字符都會同步到VM。若是想在失去焦點時同步VM,可使用 data-duplex-event="change"輔助指令來調整。

另,咱們還能夠爲元素添加一個輔助指令, data-duplex-changed="fn", fn爲VM中的一個函數。當元素的值 改動時,它會爲回調傳入當前的值及回調內部的this指向當前表單元素。

咱們還能夠在avalon.duplexHooks上添加本身的攔截器。

<!DOCTYPE html>
<html>
    <head>
        <title>ms-duplex</title>
        <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>
            var model = avalon.define({
                $id: "test",
                text: "xxx",
                textcb: "",
                textchange: function(a) {
                    model.textcb = a
                },
                radio: "radio",
                radiocb: "",
                radiochange: function(a) {
                    model.radiocb = a
                },
                select: "2222",
                selectcb: "",
                selectchange: function(a) {
                    model.selectcb = a
                }
            })
        </script>
    </head>
    <body ms-controller="test">
        <h3>data-duplex-changed回調</h3>
        <div><input ms-duplex="text" data-duplex-changed="textchange">{{textcb}}</div>
        <div><input ms-duplex-checked="radio" type="radio" data-duplex-changed="radiochange">{{radiocb}}</div>
        <div>
            <select ms-duplex="select" data-duplex-changed="selectchange">
                <option>1111</option>
                <option>2222</option>
                <option>3333</option>
                <option>4444</option>
            </select>
            {{selectcb}}</div>
    </body>
</html>

咱們能夠經過如下方式定義一個攔截器,在avalon.duplexHooks上添加一個對象,此對象擁有init, get, set這三個方法,此三個方法不必定要寫全,只要某一個就好了。init在初始化時調用,get方法是同步到VM時調用,set方法是同步視圖時調用,這些方法都有兩個參數,第一個參數是當前值,第二個參數是綁定對象。

有了攔截器,咱們就能夠實現各類表單驗證,而且一個ms-duplex是能夠帶N多攔截器的,如ms-duplex-number-notempty-gt10

<!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",
                a: 111
            });
            avalon.duplexHooks.add100 = {
                get: function (val) {
                    return val
                },
                set: function (val) {
                    return val + 100
                }
            }

        </script>
    </head>
    <body>
        <div ms-controller="test">
            <input ms-duplex-number-add100="a" >{{a}}
        </div>
    </body>
</html>

avalon的oniui的驗證組件也是基於攔截器機制構建的,你們能夠參考一下,方便本身設計本身的表單驗證。

更詳細的內容可見新官網

樣式綁定(ms-css)

用法爲ms-css-name="value"。其值可使用插值表達式, 如ms-css-width=」prop」(會自動補px),ms-css-height=」{{prop}}%」, ms-css-color=」prop」, ms-css-background-color=」prop」, ms-css-background-image=」url({{imageUrl}})」, ms-css-font-size=」{{prop}}px」

注意:屬性值不能加入CSS hack與important!

<!DOCTYPE html>
<html>
    <head>
        <title>by 司徒正美</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="../avalon.js"></script>
        <script>
            avalon.define({
               $id: "test", 
               o: 0.5,  
               bg: "#F3F"// 不能使用CSS hack,如 bg : "#F3F\9\0"
            })
        </script>
        <style>
            .outer{
                width:200px;
                height: 200px;
                position: absolute;
                top:1px;
                left:1px;
                background: red;
                z-index:1;
            }
            .inner{
                width:100px;
                height: 100px;
                position: relative;
                top:20px;
                left:20px;
                background: green;
            }
        </style>
    </head>
    <body ms-controller="test" >
        <h3>在舊式IE下,若是父元素是定位元素,但沒有設置它的top, left, z-index,那麼爲它設置透明時,
            它的全部被定位的後代都沒有透明</h3>
 
        <div class="outer" ms-css-opacity="o" ms-css-background-color="bg" >
            <div class="inner"></div>
        </div>
 
    </body>
</html>

更詳細的內容可見新官網

數據綁定(ms-data)

爲當前元素添加data-*屬性。能夠參見ms-attr的用法。

更詳細的內容可見新官網

屬性綁定(ms-attr)

其用法爲ms-attr-name="value", value可使用插值表達式。當value爲undefined, null, false,會移除此屬性,不然會添加此屬性。

<!DOCTYPE html>
<html>
    <head>
        <title>ms-attr-*</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <script src="../avalon.js"></script>

        <script>
            var vm = avalon.define({
                $id: "test",
                aaa: true,
                bbb: "@@@",
                ccc: "&&&",
                active: "active",
                click: function(){
                    vm.aaa = !vm.aaa
                }
            })
           
  
        </script>
        <style>
            .active {
                background: goldenrod;
            }
            .readonly{
                border:1px solid blueviolet;
            }
        </style>

    </head>
    <body>
        <form method="get" action="aaa.html" ms-controller="test">
            <input ms-enabled="aaa" name="a1" value="12345"/>
            <input ms-disabled="aaa" name="a2" value="67890"/>
            <input ms-readonly="aaa" name="a3" ms-class="readonly: aaa" value="readonly" />

            <input ms-duplex-radio="aaa" type="checkbox" value="checkbox" name="a4"/>
            <select name="a5">
                <option>222</option>
                <option ms-selected="aaa">555</option>
            </select>
            <p>
                <input ms-attr-value="其餘內容  {{ccc}}" name="a6" value="改"/>
                <input ms-attr-value="'其餘內容 '+ccc" name="a7" value="改" />
                <input ms-value="其餘內容  {{ccc}}" name="a8" value="改"/>
            </p>
            <button type="button" ms-click="click" ms-attr-class="active">
                點我
            </button>
            <input type="submit" value="提交" />
            <svg width="100" height="100">
            <circle ms-attr-cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
            </svg>
        </form>
    </body>
</html>

更詳細的內容可見新官網

循環綁定(ms-repeat, ms-each, ms-with)

這三個東西都很相像,見下表

名字 循環的類型 循環的範圍
ms-repeat 用於循環數組與對象 循環當前元素
ms-each 用於循環數組 循環當前元素的內部
ms-with 用於循環對象 循環當前元素的內部

當咱們用ms-each, ms-repeat循環數組時,能夠爲元素指定其別名,如ms-each-item="array1", ms-repeat-elem="array2", 若是你不指定別名,則默認爲el。(注意,別名不能出現大寫,由於屬性名在HTML規範中,會所有轉換爲小寫,詳見這裏)。監控數組擁有原生數組的全部方法,而且比它還多了set, remove, removeAt, removeAll, ensure, pushArray與 clear方法 。詳見這裏

注意,ms-each, ms-repeat會生成一個新的代理VM對象放進當前的vmodels的前面,這個代理對象擁有el, $index, $first, $last, $remove, $outer等屬性。另外一個會產生VM對象的綁定是ms-widget。

  1. el: 不必定叫這個名字,好比說ms-each-item,它就變成item了。默認爲el。指向當前元素。
  2. $first: 斷定是否爲監控數組的第一個元素
  3. $last: 斷定是否爲監控數組的最後一個元素
  4. $index: 獲得當前元素的索引值
  5. $outer: 獲得外圍循環的那個元素。
  6. $remove:這是一個方法,用於移除此元素

咱們還能夠經過data-repeat-rendered, data-each-rendered來指定這些元素都插入DOM被渲染了後執行的回調,this指向元素節點, 有一個參數表示爲當前的操做,是add, del, move, index仍是clear

若是循環的是對象,那麼代理VM就有$val, $key, $index, $outer等屬性。$key, $val分別引用鍵名,鍵值。另咱們能夠經過指定data-with-sorted回調,規定只輸出某一部分建值及它們的順序。 注意,此綁定已經不建議使用,它將被ms-repeat代替,ms-repeat裏面也可使用data-with-sorted回調。


<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

        <script src="avalon.js"></script>
        <style>
            .id2013716 {
                width: 200px;
                float:left;
            }
        </style>
        <script>
            var a = avalon.define({
               $id: "array",
                array: ["1", "2", "3", "4"]
            })
            setTimeout(function() {
                a.array.set(0, 7)
            }, 1000);
            var b = avalon.define({
                $id: "complex",
                array: [{name: "xxx", sex: "aaa", c: {number: 2}}, {name: "yyy", sex: "bbb", c: {number: 4}}]//
            });
            setTimeout(function() {
                b.array[0].c.number = 9
                b.array[0].name = "1000"
            }, 1000)

            setTimeout(function() {
                a.array.push(5, 6, 7, 8, 9)
            }, 1000)
            setTimeout(function() {
                a.array.unshift("a", "b", "c", "d")
            }, 2000)
            setTimeout(function() {
                a.array.shift()
                b.array[1].name = 7
            }, 3000)
            setTimeout(function() {
                a.array.pop()
            }, 4000)
            setTimeout(function() {
                a.array.splice(1, 3, "x", "y", "z")
                b.array[1].name = "5000"
            }, 5000)
        </script>
    </head>
    <body>
        <fieldset class="id2013716" ms-controller="array">
            <legend>例子</legend>
            <ul ms-each="array">
                <li >數組的第{{$index+1}}個元素爲{{el}}</li>
            </ul>
            <p>size: <b style="color:red">{{array.size()}}</b></p>
        </fieldset>

        <fieldset  class="id2013716" ms-controller="complex">
            <legend>例子</legend>
            <ul >
                <li ms-repeat-el="array">{{el.name+" "+el.sex}}它的內容爲 number:{{el.c.number}}</li>
            </ul>
        </fieldset>
    </body>
</html>
    

更詳細的內容可見新官網

動畫綁定(ms-effect)

avalon1.5新增的功能,容許咱們用avalon.effect方法定義一個動畫,而後在頁面上存在ms-if,ms-repeat,ms-include,ms-visible的元素添加ms-effect="effectName"實現動畫效果。此動畫可使用CSS3與JS實現。

更詳細的內容可見新官網

自定義標籤組件

經過自定義標籤聲明使用某個組件,這是avalon1.5引入的最重要特性。

早在20年前,JAVA也想經過標籤庫實現堆積木般的開發。但鑑於當時JAVAer的技術力量,這東西沒有作成;如今谷歌有了本身的瀏覽器,實現了一套WEB Components規範,開源polymer,也是往這條路子走下去。由於以標籤的形式聲明組件比起在JS裏以類的形式建立組件,來得更簡單明瞭。

<oni:buttonset>
    <oni:button color="danger"  type="icon" icon-position="left" icon="\&\#xf04c;"></oni:button>
    <oni:button color="danger"  type="icon" icon-position="left" icon="\&\#xf04b;"></oni:button>
    <oni:button color="danger"  type="icon" icon-position="left" icon="\&\#xf074;"></oni:button>
</oni:buttonset>

作這套東西有幾個難點:

  • 生命週期管理,從構建配置對象,到模板微調,到初始化,到子組件就緒,到自身組件就緒,到組件銷微,都有本身相應的回調。
  • 繼承機制
  • 當前組件必須等到子組件就緒才閉合自身,好比父組件的高度是由子組件決定
  • 組件的某些很大塊的區塊如何定義(這個引入插入點的概念處理),如彈出層有title, content, footer

爲了讓一個頁面使用多個UI庫,avalon引入了命名空間的概念,這是來自早期XML的設計。一個標籤名由冒號隔開,前面是命名空間,後面是標籤名。

avalon.library("oni", {})//這是聲明一個命名空間,也是算聲明一個UI庫

在定義一個UI組件,最少有3個文件,JS文件(引用其餘兩個文件或更多子組件的JS),HTML文件(組件的模塊),CSS文件(它能夠由SASS更高級的語言生成)。


define(["avalon",
    "text!./avalon.pager.html",
    "css!../chameleon/oniui-common.css",
    "css!./avalon.pager.css"
], function (avalon, template) {

    var _interface = function () {
    }
    avalon.component("oni:pager", {
        regional: {}, //@config {Object} 默認語言包
        perPages: 10, //@config {Number} 每頁包含多少條目
        showPages: 10, //@config {Number} 中間部分一共要顯示多少頁(若是兩邊出現省略號,即它們之間的頁數) 
        currentPage: 1, //@config {Number} 當前選中的頁面 (按照人們平常習慣,是從1開始),它會被高亮 
        _currentPage: 1, //@config {Number}  跳轉檯中的輸入框顯示的數字,它默認與currentPage一致
        totalItems: 200, //@config {Number} 總條目數
        totalPages: 0, //@config {Number} 總頁數,經過Math.ceil(vm.totalItems / vm.perPages)求得
        pages: [], //@config {Array} 要顯示的頁面組成的數字數組,如[1,2,3,4,5,6,7]
        ellipseText: "…", //@config {String} 省略的頁數用什麼文字表示 
        prevText: "<", //@config {String} 「下一頁」分頁按鈕上顯示的文字 
        nextText: ">", //@config {String} 「上一頁」分頁按鈕上顯示的文字 
        firstPage: 0, //@config {Number} 當前可顯示的最小頁碼,不能小於1
        $ready: function(){
              //....
       }
       //....
   })
})

avalon.component是用來定義一個組件,第一個參數爲自定義標籤的名字(包括命名空間),第二個是配置對象,裏面定義這個組件用到的屬性,狀態與方法。

如何作一個avalon組件

更詳細的內容可見新官網

咱們也能夠到這裏直接看pager, button, checkboxlist是怎麼定義。

將來與avalon配套使用的OniUI一點點所有改爲自定義標籤的形式,更但願你們一塊兒爲avalon設計更多的UI組件。

模塊間通訊及屬性監控 $watch,$fire

avalon1.5是無比強大,並如今只有VM第一層有$watch, $fire方法,並去掉$unwatch方法。$watch方法會返回一個方法,調用它就會去掉此回調。

早期$watch只能監聽當前層的屬性,不能監聽其子對象的屬性,尤爲在數組中,沒法監聽元素的屬性變更。所以在avalon1.5中,引入通配符及多級監聽功能。

var vm = avalon.define({
     $id: "test",
     array: [1, 2, 3],
     arr: [{a: 1}, {a: 2}, {a: 3}],
     obj: {
         a: 1,
         b: 2
     },
     a: {
         b: {
             c: {
                 d: 33
             }
         }
     }
 })
 vm.$watch("array.*", function (a, b) {
     expect(a).to.be(6)
     expect(b).to.be(2)
 })
 vm.$watch("arr.*.a", function (a, b) {
     expect(a).to.be(99)
     expect(b).to.be(1)
 })
 vm.$watch("obj.*", function (a, b, c) {
     expect(a).to.be(111)
     expect(b).to.be(1)
 })
 vm.$watch("a.*.c.d", function (a, b, c) {
     expect(a).to.be(88)
     expect(b).to.be(33)
 })
 setTimeout(function () {
     vm.array.set(1, 6)
     vm.arr[0].a = 99
     vm.obj.a = 111
     vm.a.b.c.d = 88
 }, 100)

注意,*通配任只能出現一次

var unwatch = vm.$watch("array.*", function (a, b) {
    expect(a).to.be(6)
    expect(b).to.be(2)
 })
unwatch() //移除當前$watch回調

若是在跨模塊通訊,因爲全部VM都儲存在avalon.vmodels中,咱們能夠遍歷它們,找到目標VM,而後$fire。

avalon多級聯動例子:

<!DOCTYPE html>
<html>
    <head>
        <title>三級聯動</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script src="avalon.js"></script>

        <script>
            var map = {
                "中國": ["江南四大才子", "初唐四傑", "戰國四君子"],
                "日本": ["日本武將", "日本城堡", "幕府時代"],
                "歐美": ["三大騎士團", "三大魔幻小說", "七大奇蹟"],
                "江南四大才子": ["祝枝山", "文徵明", "唐伯虎", "周文賓"],
                "初唐四傑": ["王勃", "楊炯", "盧照鄰", "駱賓王"],
                "戰國四君子": ["楚國春申君黃歇", "齊國孟嘗君田文", "趙國平原君趙勝", "魏國信陵君魏無忌"],
                "日本武將": ["織田信長", "德川家康", "豐臣秀吉"],
                "日本城堡": ["安土城", "熊本城", "大阪城", "姬路城"],
                "幕府時代": ["鎌倉", "室町", "豐臣", "江戶"],
                "三大騎士團": ["聖殿騎士團", "醫院騎士團", "條頓騎士團"],
                "三大魔幻小說": ["冰與火之歌", "時光之輪", "荊刺與白骨之王國"],
                "七大奇蹟": ["埃及胡夫金字塔", "奧林匹亞宙斯巨像", "阿爾忒彌斯月神殿", "摩索拉斯陵墓", "亞歷山大港燈塔", "巴比倫空中花園", "羅德島太陽神巨像"]
            }
            var vm = avalon.define({
                $id: "test",
                first: ["中國", "日本", "歐美"],
                second: [],
                third: [],
                firstSelected: "日本",
                secondSelected: "日本武將",
                thirdSelected: "織田信長"
            })

            vm.second = map[vm.first[1]].concat()
            vm.third = map[vm.second[0]].concat()

            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="test">
            <h3>下拉框三級聯動</h3>
            <select ms-duplex="firstSelected" >
                <option  ms-repeat="first" ms-attr-value="el" >{{el}}</option>
            </select>
            <select ms-duplex="secondSelected" >
                <option ms-repeat="second" ms-attr-value="el" >{{el}}</option>
            </select>
            <select ms-duplex="thirdSelected" >
                <option ms-repeat="third" ms-attr-value="el" >{{el}}</option>
            </select>
        </div>
    </body>
</html>

過濾器

avalon從angular中抄來管道符風格的過濾器,但有點不同。 它只能用於{{}}插值表達式。若是不存在參數,要求直接跟|filter,若是存在參傳,則要用小括號括起,參數要有逗號,這與通常的函數調用差很少,如|truncate(20,"……")

avalon自帶如下幾個過濾器

html
沒有傳參,用於將文本綁定轉換爲HTML綁定
sanitize
去掉onclick, javascript:alert等可能引發注入攻擊的代碼。
uppercase
大寫化
lowercase
小寫化
truncate
對長字符串進行截短,truncate(number, truncation), number默認爲30,truncation爲「...」
camelize
駝峯化處理
escape
對相似於HTML格式的字符串進行轉義,把尖括號轉換爲&gt; &lt;
currency
對數字添加貨幣符號,以及千位符, currency(symbol)
number
對數字進行各類格式化,這與與PHP的number_format徹底兼容, number(decimals, dec_point, thousands_sep),
              decimals	可選,規定多少個小數位。
              dec_point	可選,規定用做小數點的字符串(默認爲 . )。
             thousands_sep	可選,規定用做千位分隔符的字符串(默認爲 , ),若是設置了該參數,那麼全部其餘參數都是必需的。
            
date
對日期進行格式化,date(formats)
      'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
      'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
      'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
      'MMMM': Month in year (January-December)
      'MMM': Month in year (Jan-Dec)
      'MM': Month in year, padded (01-12)
      'M': Month in year (1-12)
      'dd': Day in month, padded (01-31)
      'd': Day in month (1-31)
      'EEEE': Day in Week,(Sunday-Saturday)
      'EEE': Day in Week, (Sun-Sat)
      'HH': Hour in day, padded (00-23)
      'H': Hour in day (0-23)
      'hh': Hour in am/pm, padded (01-12)
      'h': Hour in am/pm, (1-12)
      'mm': Minute in hour, padded (00-59)
      'm': Minute in hour (0-59)
      'ss': Second in minute, padded (00-59)
      's': Second in minute (0-59)
      'a': am/pm marker
      'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200)
      format string can also be one of the following predefined localizable formats:
      
      'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm)
      'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm)
      'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010)
      'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010
      'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010)
      'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10)
      'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm)
      'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm)
            

例子:

生成於{{ new Date | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ "2011/07/08" | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ "2011-07-08" | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ "01-01-2000" | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ "03 04,2000" | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ "3 4,2000" | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ 1373021259229 | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ "1373021259229" | date("yyyy MM dd:HH:mm:ss")}}

值得注意的是,new Date可傳的格式類型很是多,但不是全部瀏覽器都支持這麼多,詳看這裏

多個過濾器一塊兒工做

        
         <div>{{ prop | filter1 | filter2 | filter3(args, args2) | filter4(args)}}</div>
    

若是想自定義過濾器,能夠這樣作

               avalon.filters.myfilter = function(str, args, args2){//str爲管道符以前計算獲得的結果,默認框架會幫你傳入,此方法必須返回一個值
                  /* 具體邏輯 */
                  return ret;
               }
          //默認過濾器
             avalon.filters.default = function(str, defaultStr){
                return str || defaultStr
             }
           //性別過濾器
             avalon.filters.sex = function(str){
                 return str == '0' ? '男': '女'
              }

    

自定義指令(綁定)

avalon1.5新添加的API,avalon.directive, 用於快速建立一種全新的指令!

過去avalon自定義綁定比較麻煩,很是影響avalon推廣,也不利於業務上各類功能的開發,新的定義方式將變得很是簡單

avalon.directive(name, {
  init: function(binding){
     //在這裏處理binding.expr屬性
  },
  update: function(value, oldValue){
  //value是binding.expr通過解析獲得的當前VM屬性的值
  //oldValue是它以前的值
  },
})

拿最簡單的data綁定來講吧:

avalon.directive("data", {
    priority: 100,
    update: function (val) {
        var elem = this.element
        var key = "data-" + this.param
        if (val && typeof val === "object") {
            elem[key] = val
        } else {
            elem.setAttribute(key, String(val))
        }
    }
})

咱們發現它沒有init回調,init主要是對binding.expr進行加工,或綁定事件的,它這些都不須要, 就會直接返回原始值.好比說,<div ms-data-xxx="yyy"< 這裏的屬性會抽取成下面一個對象:

var binding = {
   name: "ms-data-xxx",
   expr: "yyy",
   type: name,
   element: DIVElement,
   param: "xxx",
   oneTime: false,
   uuid: //框架會在這裏生成一個UUID給它
   priority://框架會根據上面的定義,計算出來,大概是1000+ ;
}

假如此時vm.yyy = 999; 那麼第一次update時,value與oldValue分別爲 999, undefined

對於用戶來講,priority通常不須要設置, 其計算公式爲

//詳見 scanAttr
priority: (directives[type].priority || type.charCodeAt(0) * 10) + (Number(param.replace(/\D/g, "")) || 0)

此外,若是你的綁定須要綁定一些事件來監聽用戶行爲,那麼你得在init或update添加一個roolback回調,在裏面解綁定事件.

update方法中的this就是init的傳參binding

那麼咱們作一下簡單的例子吧:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script src="avalon.js">

        </script>
        <script>
            avalon.directive("foo", {
                init: function (binding) {
                    var elem = binding.element
                    var vmodels = binding.vmodels
                    var remove = avalon(elem).bind("click", function () {
                        elem.innerHTML = new Date - 0
                        for (var i = 0, v; v = vmodels[i++]; ) {
                            if (v.hasOwnProperty(binding.expr)) {
                                v[binding.expr] = elem.innerHTML
                                break
                            }
                        }

                    })
                    binding.roolback = function () {
                        avalon(elem).unbind("click", remove)
                    }
                },
                update: function (value, oldValue) {
                    this.element.innerHTML = value
                }
            })
            var vm = avalon.define({
                $id: "test",
                aaa: 111
            })
            vm.$watch("aaa", function (a, b) {
                console.log(a, b)
            })
        </script>
    </head>
    <body>
        <div ms-controller="test">
            <div ms-foo="aaa">點我</div>
        </div>
    </body>
</html>

別看例子簡單,其實它是除duplex外又一個新的雙工綁定!

加載器

avalon自帶加載器,它是使用業界最通用的AMD規範。

若是你想禁用自帶加載器,有兩種辦法:

  • 先入avalon再引入其餘加載器,而後當即禁用avalon自帶加載器
    avalon.config({
           loader:false
    })
               
  • 這裏下載沒有加載器的版本(以shim結尾)

至於avalon是怎麼與jQuery, requirejs怎麼搭配,怎麼打包,能夠詳看這裏

AJAX

avalon自己沒提供AJAX模塊,你們可使用jQuery或 mmRequest

            require(["mmRequest/mmRequest"], function() {
                var vm = avalon.define("test", function(vm) {
                    vm.loadScript = function() {
                        avalon.getScript("test.js", function() {
                            console.log("success")
                            vm.time = + new Date()
                        })
                    }
                    vm.loadAJAX = function() {
                        avalon.ajax({
                            url: "test.js",
                            dataType: "script",
                            data: {
                                page: 1,
                                name: 1
                            },
                            success: function() {
                                console.log("success")
                                vm.time = + new Date()
                            }
                        })
                    }
                    vm.jsonData = []
                    vm.time = ""
                })
                avalon.scan()
            })

mmRequest覆蓋原jQuery全部的功能,其文檔地址在這裏

若是咱們想將VM提交到後臺,因爲VM存在許多方法與其餘東西,直接提交數據量很大,所以咱們能夠經過下面方法提交純數據

$("button").keyup(function(){
  $.post("demo_ajax_gethint.asp", JSON.parse(JSON.stringify(vm.$model)),function(result){
      alert("提交成功")
  });
});

路由系統

avalon提供了mmRouer與mmState兩個路由器,但mmState也是基於mmRouter開發的,引入mmState就會自動引入mmRouter。

這裏兩個加載器均可以在這裏下載到,包括(mmPromise, mmHIstory, mmRouter, mmState)

其文檔放在這裏,有不懂的地方直接在此倉庫提ISSUE

在IE6下調試avalon

因爲IE6下沒有console.log,若是又不想用VS等巨無霸IDE,能夠本身定義如下方法

if(!window.console){
       window.console = {}
       console.log = function(str){
            avalon.ready(function() {
                var div = document.createElement("pre");
                div.className = "mass_sys_log";
                div.innerHTML = str + ""; //確保爲字符串
                document.body.appendChild(div);
            });
       }
      
}

上線後,將.mass_sys_log{ display: none; }

若是是高級瀏覽器,avalon會在控制檯上打印許多調試消息,若是不想看到它們,能夠這樣屏蔽它們:avalon.config({debug: false})

avalon1.4.6到1.5的升級指南

  • avalon1.5再也不支持舊風格,avalon.define(id, callback)所有改爲avalon.define(object)這種形式
  • avalon1.5定義計算屬性時,須要在$computed對象上統必定義
  • avalon1.5生成的VM 只有第一層對象上擁有 $fire, $watch方法,子對象沒有$fire, $watch方法。以前監聽數組長度變化時,是用vm.array.$watch("length",callback),如今改爲vm.$watch("array.length",callback),以前監聽子對象某個屬性的變化,是使用vm.aaa.$watch("bbb",callback),如今改爲vm.$watch("aaa.bbb",callback),以前監聽某一層的全部屬性變化是vm.$watch("all!", callback),如今改爲vm.$watch("*",callback);以前不能監聽數組元素變化,如今能夠vm.$watch("array2.*", callback)
  • avalon1.5再也不支持$unwatch,詳見上方。
  • avalon1.5再也不支持data-duplex-focus, data-duplex-observe輔助指令
  • avalon1.5再也不支持ms-widget,請使用avalon.component建立組件,而後以自定義標籤的形式聲明使用。
  • avalon1.5再也不使用avalon.bindingHandlers,avalon.bindingExecutors來建立新指令,再改用更方便的avalon.directive方法
  • avalon在循環對象時,之前須要本身計算索引值,如今它與循環數組同樣,存在$index變量了!

其餘要注意的地方(更新VM等)

avalon是經過Object.defineProperty來實現屬性監聽,所以不像angular,每次都VM的全部屬性都比較一遍,而是用戶一改動屬性就當即觸發視圖變更。

這是avalon的優點,但隱藏一個很差的地方。用戶必須在avalon.define的配置對象將全部監聽的對象預先寫好,不然沒法監聽到。

           var vm =  avalon.define({
                $id: "test",
                a: "aaa", //這個能夠監聽,改變它會同步視圖
                $b:"ccc"  //這個是非監控屬性,改變它不會同步視圖
            })
            vm.c = '111' //這是avalon.define後才添加的,改變它不會同步視圖
                         //在IE6-8還會拋錯

avalon在IE6-8是使用VBScript實現,不支持隨便添加新屬性,好比上例就會拋錯

VBScript還帶一個問題,它是不區分大小寫,aaa與AAA是認爲同樣的,這時它會報重複定義的問題。

某些屬性因爲是VBscript的關鍵字,也不能做爲屬性或方法,不然在IE6-8也拋錯,如type, err, erm, me

因爲avalon是經過Object.defineProperty來實現屬性監聽,它會轉換其底下的每個屬性,每個數組,每個子對象,所以對象的層次不太深,不然會帶來性能問題。最好不超過3層,數組的層次也宜過長。而且儘可能使用$開頭或放在$skipArray減小無謂的屬性監聽。

avalon能夠經過如下手段撕開不能監聽新屬性的限制,就是重寫子對象!

           var vm =  avalon.define({
                $id: "test",
                a: {}
            })
            vm.a = {
               b: 2,//如今a.b, a.c, a.d能夠與視圖互相同步了
               c: 3
               d: 4
            }

若是你想重寫數組的某一個元素,能夠其set方法,若是交互兩個數組元素,請使用splice方法或深拷貝原數組再賦值。

<!DOCTYPE html>
<html lang="zh-cn">
    <head>
        <meta charset="utf-8">
        <script src="avalon.js"></script>
        <script>
            var vm = avalon.define({
                $id: "test",
                items: [
                    {
                        name: 'First lady'
                    },
                    {
                        name: 'Second boy'
                    },
                    {
                        name: 'Third guy'
                    }
                ];
            });

            var temp = vm.items.splice(2, 1);
            vm.items.splice(1, 0, vm[0].$model);//若是是簡單數據類型,就不須要.$model
        </script>
    </head>
    <body>
        <ul ms-controller="test">
            <li ms-repeat="items">{{ el.name }}</li>
        </ul>
    </body>
</html>

<!DOCTYPE html>
<html>

    <head>
        <title>avalon入門</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="avalon.js" type="text/javascript"></script>
        <script>
            var model = avalon.define({
                $id: 'test',
                arr: [{
                        'value': '0',
                        'text': 'zero'
                    }, {
                        'value': '1',
                        'text': 'one'
                    }, {
                        'value': '2',
                        'text': 'two'
                    }, {
                        'value': '3',
                        'text': 'three'
                    }],
                click: function () {


                    var aa = avalon.mix(true, [], model.$model.arr)
                    //交換one和three

                    aa[1] = aa[3]
                    aa[3] = aa[1]

                    //控制檯顯示交換成功
                    console.log(aa[1])
                    console.log(aa[3])
                    model.arr = aa
                }
            })
        </script>
    </head>

    <body>
        <div ms-controller="test">
            <button type="button" ms-click="click">xxx</button>
            <ul>
                <li ms-repeat="arr" >
                    {{el.text}}
                </li>
            </ul>
        </div>
    </body>

</html>

與mmState搭配使用時,頁面上的組件重複生成的解決方法:在ms-view所在元素上添加一屬性data-view-cache="true", 。但若是兩個視圖有相同的dialog(就是ms-include-src='路徑.html')相互以前切換的話dialog vmodels會被刪除。就把調用公用dialog的html移除到ms-view以外解決了。

咱們能夠在官網這裏看到更多有用的信息

有問題請到GITHUB提ISUUE,或到avalon專門論壇提問。

avalon在慕課網也有相關視頻教程前端亂燉上有系列長文剖析avalon的原理

本人也承受北京地區的avalon上門培訓!!!

相關文章
相關標籤/搜索