迷你MVVM框架 avalonjs 入門教程

迷你MVVM框架 avalonjs 入門教程


  1. 關於AvalonJs
  2. 開始的例子
  3. 掃描
  4. 視圖模型
  5. 數據模型
  6. 綁定屬性與動態模板
  7. 作用域綁定(ms-controller, ms-important)
  8. 模板綁定(ms-include)
  9. 數據填充(ms-text, ms-html)
  10. 類名切換(ms-class, ms-hover, ms-active)
  11. 事件綁定(ms-on,……)
  12. 顯示綁定(ms-visible)
  13. 插入綁定(ms-if)
  14. 雙工綁定(ms-duplex,原來的ms-model)
  15. 樣式綁定(ms-css)
  16. 數據綁定(ms-data)
  17. 布爾屬性綁定(ms-checked, ms-selected, ms-readonly, ms-disabled, ms-enabled)
  18. 字符串屬性綁定(ms-title, ms-src, ms-href……)
  19. 萬能屬性綁定(ms-attr)
  20. 萬能綁定(ms-bind)
  21. 數組循環綁定(ms-each,ms-repeat)
  22. 對象循環綁定(ms-with)
  23. UI綁定(ms-widget)
  24. $watch
  25. 過濾器
  26. AMD加載器
  27. 路由系統
  28. 通過AJAX加載新數據到已存在的VM中
  29. 功能擴展

關於AvalonJS

avalon是一個迷你的MVVM框架,雖然從發佈到現在,它臌脹了不少,但它現在還是比knockout小許多。avalon開發過程一直遵循三個原則:1,複雜即錯誤,2,數據結構優於算法,3,出奇制勝。這三大原則保證avalon具有良好的維護性,擴展性,與衆不同。

簡單說一下其他三大MVVM的實現思路:

  1. knockout:最早冒出來的JS MVVM庫,通過轉換VM中所有要監聽的東西爲函數,然後執行它們,得到某一時刻中,一共有多少函數被執行,將它們放到棧中,最底的就是最先被執行的,它上面的就是此函數所依賴的函數,從而得到依賴關係。 然後設計一個觀察者模式,從上面的依賴檢測中,將依賴函數作爲被依賴者(最先執行的那個的)的訂閱者,以後我們對被依賴者進行賦值時,就會通先訂閱者更新自身,從而形成一個雙向綁定鏈。 並且,knockout會將視圖中的綁定屬性進行轉換,分解出求值函數與視圖刷新函數,視圖刷新函數依賴於求值函數,而求值函數亦依賴於我們VM中的某些屬性(這時,它們都轉換爲函數),在第一次掃描時,它們會加入對應屬性的訂閱者列隊中, 從而VM中的某個屬性改變,就會自動刷新視圖。
    評價:實現非常巧妙,是avalon0.1-0.3的重要學習對象,但將屬性變成一個函數,讓人用點不習慣,許多用法都有點笨笨的。 雖然是一個輕盈的庫,但擴展性不強,裏面的實現異常複雜,導致能參與源碼的人太少。
  2. emberjs: 一個大而全的框架,包羅萬象。一開始是使用Object.defineProperty+觀察者實現,但IE8的問題,讓它不得不啓用上帝setter, 上帝getter。沒有自動收集依賴的機制,沒有監控數組,計算屬性需要自己指定依賴。VM可繼承。 VM與視圖的雙向綁定依賴於其強大無比上萬行的Handlebars 模板。聽說是外國目前最好用的MV*框架。因爲作者既是jQuery的核心成員,也是Rails的核心成員,雖然由於技術能力沒實現自動收集依賴,但框架的其他方面做得非常易上手,人性化。 
    評價:太大了,優缺點同python的Django框架。
  3. angular: google組織開發的框架,體現其算法至上的時候到了。裏面一共有兩個parser, 一個是ngSanitize/sanitize.js下的HTML parser, 一個是ng/parse.js(它要配合compile.js使用)的JS parser。第一個parser負責綁定抽取,第二個負責從Ctrl函數,工廠函數,服務函數及$watch回調中分解出無數setter, getter, 確認它們的依賴關係,放進觀察者模式中。它的觀察者無比強大,由於它的VM能繼承,於是通過繼承鏈實現四通發達的消息廣播。它還實現了一個基於LRU的緩存系統,因爲google最喜歡以空間換時間了,另一方面說明它要緩存的東西太多了,非常吃內存。 公司內部用angular實現的grid,200行在PC中就拖不動了。它還用到許多時髦的東東,如HTML5 history API, 迷你版Q Promise。內部是極其複雜。 不過最大的問題是,它是基於parser,靜態編譯,這意思着什麼呢?不抗壓縮!爲此,它引進了IOC,官網上給出的簡單例子其實在項目完全不可用,我們需要使用另一種更復雜的寫法,方便編澤器從它們得到不被壓縮的部分, 讓它在壓縮情況也能正常運行。由於基於編譯,許多行爲都不是即時的,可預見的。用戶寫的那些控制器函數,都是爲編譯做準備。由於基於編譯,它不得不要求我們對具有兼容問題的一些全局函數,方法進行屏蔽,用它的給出的服務替代它們,如 window對應$window, document對應$document, location對應$location, setTimout對應$timeout……如果不遵循這規則,它可能運行不了,你需要手動使用$digest手動觸發。 不過對於一些複雜的回調,$digest也奈何不了,但又不報錯,基本無法調試,只能撞大運般地一點點改……
    評價:上手難度非常高,團隊中沒有JS高手慎用

現在的avalon是我在完全消化了knockout發展起來的,準確來說,是0.4版,通過Object.defineProperties與VBScript實現了與普通對象看起來沒什麼兩樣的VM,VM裏面充滿了訪問器屬性,而訪問器屬性肯定對應一個setter,一個getter, 我們就在setter, getter中走knockout的老路,實現自動收集依賴,然後放進一個簡單的觀察者模式中,從而實現雙向綁定。將綁定屬性分解爲求值函數與視圖刷新函數,早前,avalon也與knockout一樣使用一個簡單的parser,然後通過with實現, 0.82一個新的parser 上馬,同樣的迷你,但生成的求值函數,更方便依賴收集,並且沒有with語句,性能更佳。angular也不是一無是處,我也從它那裏抄來了{{}}插值表達式,過濾器機制,控制器綁定什麼的。

avalon在內部使用了許多巧妙的設計,因此能涵蓋angular絕對大多數功能,但體積卻非常少。此外,在性能上,現在除了chrome外,它都比knockout快,angular則是最慢的。 在移動端上,avalon這個優勢會被大大放大化的。

關於avalon的幾點:

  • 兼容IE6
  • 沒有AJAX與動畫模塊,需要配合jQuery等庫使用
  • avalon會自動同步視圖,因此不要在VM中進行DOM操作

迷你MVVM框架在github的倉庫https://github.com/RubyLouvre/avalon, 如果你要兼容IE6,那麼下其中的avalon.js, 如果你只打算兼容IE10與標準瀏覽器,那麼下avalon.mobile.js。

官網地址http://rubylouvre.github.io/mvvm/

開始的例子

我們從一個完整的例子開始認識 avalon :

 <!DOCTYPE html>
 <html>
     <head>
         <title></title>
         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
         <script src="avalon.js"></script>
     </head>
     <body>
         <div ms-controller="box">
             <div style=" background: #a9ea00;" ms-css-width="w" ms-css-height="h"  ms-click="click"></div>
             <p>{{ w }} x {{ h }}</p>
             <p>W: <input type="text" ms-duplex="w" data-duplex-event="change"/></p>
             <p>H: <input type="text" ms-duplex="h" /></p>
         </div>
         <script>
             avalon.define("box", function(vm) {
                 vm.w = 100;
                 vm.h = 100;
                 vm.click = function() {
                     vm.w = parseFloat(vm.w) + 10;
                     vm.h = parseFloat(vm.h) + 10;
                 }
             })
         </script>
     </body>
 </html>
 
        

上面的代碼中,我們可以看到在JS中,沒有任何一行操作DOM的代碼,也沒有選擇器,非常乾淨。在HTML中, 我們發現就是多了一些以ms-開始的綁定屬性與{{}}插值表達式,有的是用於渲染樣式, 有的是用於綁定事件。在ms-model中,我們會發現它會反過來操作VM,VM的改變也會影響視圖的其他部分。

掃描

不過上面的代碼並不完整,它能工作,是因爲框架默認會在DOMReady時掃描DOM樹,將視圖中的綁定屬性與{{}}插值表達式抽取出來,轉換爲求值函數與視圖刷新函數。

上面的JS代碼相當於:

            avalon.ready(function() {
                 avalon.define("box", function(vm) {
                     vm.w = 100;
                     vm.h = 100;
                     vm.click = function() {
                         vm.w = parseInt(vm.w) + 10;
                         vm.h = parseInt(vm.h) + 10;
                     }
                 })
                 avalon.scan()
             })
        

avalon.scan是一個非常重要的方法,它有兩個可選參數,第一個是掃描的起點元素,默認是HTML標籤,第2個是VM對象。

 //源碼
     avalon.scan = function(elem, vmodel) {
         elem = elem || root
         var vmodels = vmodel ? [].concat(vmodel) : []
         scanTag(elem, vmodels)
     }
        

現在掃描的順序是 ms-skip --> ms-important --> ms-controller --> ms-if ... 只要元素存在ms-skip 這個綁定屬性時,就忽略掃描此元素及子孫。然後是ms-important, ms-controller這 兩個與作用域有關的綁定,如果它們指向的VM在avalon.vmodels 不存在時,規則同ms-skip。 最後ms-if,如果ms-if的表達式的結果爲true,那麼走如下步驟:如果沒有插入到DOM樹,插入它,並掃描此元素。 如果值爲假,就移除此元素,並停止掃描此元素的其他綁定屬性及子孫。

視圖模型

我們是通過avalon.define函數返回一個視圖對象VM,並且avalon.define(vmName, function(vm){})中的vm並不等於VM,工廠函數中的vm是用於轉換爲VM的。生成的VM比用戶指定的屬性還多了許多屬性。

默認的,除了函數外,其他東西都轉換爲監控屬性,計算屬性與監控數組。如果不想讓它轉換,可以讓此屬性以 $開頭,框架就不會轉換它們。

    

如果實在不方便改名,又不想被轉換,比如是一個jQuery對象或一個DOM節點,如果轉換,肯定拖死框架,我們可以放到vm.$skipArray = [propName1, propName2]中去,這樣也忽略轉換。視圖裏面,我們可以使用ms-controller, ms-important指定一個VM的作用域。此外,在ms-each, ms-with中,它們會創建一個臨時的VM,用於放置$key, $val, $index, $last, $first, $remove等變量或方法。

另外,avalon不允許在VM定義之後,再追加新屬性與方法,比如下面的方式是錯誤的:

                 var vm = avalon.define("test", function(vm) {
                     vm.test1 = '點擊測試按鈕沒反應 綁定失敗';
                 });
                 vm.one = function() {
                     vm.test1 = '綁定成功';
                 };
    //這裏有兩個錯誤,
    //1在命名上沒有區分avalon.define的返回值與它回調中的參數,
    //2one方法的定義位置不對(這是考慮到兼容IE6-8,要求所有瀏覽器保持行爲一致)
        

此外,不要在avalon.define方法裏面執行函數或方法,因此框架會對define執行了兩次,第1次用於取得用戶對vm對象(factory的傳參)的設置,第2次用於重置裏面的vm對象爲真正的VM。看源碼:

    avalon.define = function(name, factory) {
        var args = avalon.slice(arguments)
        if (typeof name !== "string") {
            name = generateID()
            args.unshift(name)
        }
        factory = args[1]
        var scope = {
            $watch: noop
        }
        factory(scope) //第1次!!!!!!!!!!!!!!
        var model = modelFactory(scope) //偷天換日,將scope換爲model
        stopRepeatAssign = true
        factory(model)  //第2次!!!!!!!!1
        stopRepeatAssign = false
        model.$id = name
        return VMODELS[name] = model
    }
       

因此下面的寫法會執行兩次alert

     

         function check(){ alert("!!!!!!!!!!!")}
         var model = avalon.define("xxx", function(vm){
             vm.bool = true;
             check()//這個方法不應該寫在這裏,請放在avalon.define外面
             vm.array = [1,2,3]
         })
       

如果VM中的某函數是作爲事件回調而存在,如ms-click=aaa, aaa的vm請替換爲avalon.define返回的變量,確保能正確運行。

     
        var model = avalon.define("xxx", function(vm){
              vm.percent = 10
              vm.aaa = function(){
                  model.percent ++;
              }
         })
        

所有定義好的VM都會儲放在avalon.vmodels中!

我們再看看如何更新VM中的屬性(重點):

     
      <script>
            var model = avalon.define("update", function(vm) {
                vm.aaa = "str"
                vm.bbb = false
                vm.ccc = 1223
                vm.time = new Date
                vm.simpleArray = [1, 2, 3, 4]
                vm.objectArray = [{name: "a"}, {name: "b"}, {name: "c"}, {name: "d"}]
                vm.object = {
                    o1: "k1",
                    o2: "k2",
                    o3: "k3"
                }
                vm.simpleArray = [1, 2, 3, 4]
                vm.objectArray = [{name: "a", value: "aa"}, {name: "b", value: "bb"}, {name: "c", value: "cc"}, {name: "d", value: "dd"}]
                vm.object = {
                    o1: "k1",
                    o2: "k2",
                    o3: "k3"
                }
            })
            setTimeout(function() {
                //如果是更新簡單數據類型(string, boolean, number)或Date類型
                model.aaa = "這是字符串"
                model.bbb = true
                model.ccc = 999999999999
                var date = new Date
                model.time = new Date(date.setFullYear(2005))
            }, 2000)

            setTimeout(function() {
                //如果是數組,注意保證它們的元素的類型是一致的
                //只能全是字符串,或是全是布爾,不能有一些是這種類型,另一些是其他類型
                //這時我們可以使用set方法來更新(它有兩個參數,第一個是index,第2個是新值)
                model.simpleArray.set(0, 1000)
                model.simpleArray.set(2, 3000)
                model.objectArray.set(0, {name: "xxxxxxxxxxxxxxxx", value: "xxx"})
            }, 2500)
            setTimeout(function() {
                model.objectArray[1].name = "5555"
            }, 3000)
            setTimeout(function() {
                //如果要更新對象,直接賦給它一個對象,注意不能將一個VM賦給它,可以到VM的$model賦給它(要不會在IE6-8中報錯)
                model.object = {
                    aaaa: "aaaa",
                    bbbb: "bbbb",
                    cccc: "cccc",
                    dddd: "dddd"
                }
            }, 3000)
        </script>
        <div ms-controller="update">
            <div>{{aaa}}</div>
            <div>{{bbb}}</div>
            <div>{{ccc}}</div>
            <div>{{time | date("yyyy - MM - dd mm:ss")}}</div>
            <ul ms-each="simpleArray">
                <li>{{el}}</li>
            </ul>
            <div>  <select ms-each="objectArray">
                    <option ms-value="el.value">{{el.name}}</option>
                </select>
            </div>
            <ol ms-with="object">
                <li>{{$key}}                {{$val}}</li>
            </ol>
        </div>
        

這裏還有個例子,大家認真看看。

數據模型

當我們要用AJAX與後端交互時,如果直接把VM傳上去太大了,這時我們需要把它對應的純數組的JS對象。在VM中有個叫$model的屬性,這是一個對象,就是數據模型M了。當我們更改VM時,框架就會自動同步M

綁定屬性與動態模板

在開始之前,我們看一下靜態模板是怎麼工作的:

我之前寫了一個叫ejs的靜態模板引擎:

 <script type="tmpl" id="table_tmpl">
         <&= title() &>
         <table border=1>
         <&- for(var i=0,tl = @trs.length,tr;i<tl;i++){  -&>
             <&- tr = @trs[i]; -&>
             <tr>
             <td><&= tr.name;; &></td> <td><&= tr.age; &></td> <td><&= tr.sex || "男" &></td>
             </tr>
             <& } &>
         </table>
         < 怎麼可能不支持圖片 &>
         <img src="<&= @href &>">
 </script>
        

它是以一個script標籤做容器,裏面的整個叫模板。模板裏面有許多以 <& 與 &>劃分出來的區塊,用於插入JS代碼,以@開頭的變量是對應於數據包中的某個屬性。

幾乎所有靜態模板的實現原理都是一樣的,將這個模板變成一個函數,然後裏面分成靜態部分與動態部分,靜態部分就是上面的HTNMl部分,轉換爲一個個字符串,動態部分就是插入的JS代碼, 它們基本上原封不動地成爲函數體的邏輯。然後我們傳入一個對象給這個函數,最後得到一個符合HTML格式的字符串,最後用它貼到頁面上某個位置就行了。

靜態模板有幾個缺點,首先它容易混入大量的JS邏輯,對於菜鳥來說,他們特別喜歡在裏面放入越來越多JS代碼。這個在JSP年代,已經證明是bad practice。爲此出現了logic-less的 mustache。 其次,它更新視圖總是一大片一大片地處理,改動太大。最後,是由於第2點引發的問題,它對事件綁定等不友好,因爲一更新,原來的節點都被消滅了,需要重新綁定。幸好,jQuery普及了事件代理,這問題纔沒有 暴露出來。

再次,字符串模塊沒有對樣式的操作,流程的操作進行封裝,沒有計算屬性,監控數組的東西,很容易誘導用戶在頁面上寫大量業務邏輯,導致代碼無法維護。

下面就是一個PHP+原生JS+JQ的例子:

再看動態模板,幾乎所有MVVM框架都用動態模板(當然也有例外,如emberjs)。動態模板以整個DOM樹爲容器,它通過掃描方式進行第一次更新視圖。 在靜態模板,通過<& 與 &>劃分的部分,轉換爲綁定屬性與{{}}插值表達式(這是一種文本綁定,在avalon中,我們可以通過|html過濾器,轉換html綁定) 這樣就有效阻止用戶在頁面上寫邏輯。雖然動態模板也支持ms-if, ms-each等表示邏輯關係的綁定,但它的值最複雜也只能是一個表達式。 在綁定屬性中,屬性名用於指定操作行爲,如切換類名,控制顯示,循環渲染,綁定事件,數據填充什麼的,而屬性值是決定這些操作是否執行,與渲染結果。 由於雙向綁定的關係,它不像靜態模板那樣,每次都要自己將數據包放進函數,得到結果,然後innerHTML刷新某個區域。它是在用戶爲VM的某個屬性進行重新賦值,將視圖中對應的某個文本節點, 特性節點或元素節點的值進行重刷。因此不會影響事件綁定。

在avalon中,這些視圖刷新函數都有個element屬性,保持對應的元素節點,每次同步時,都會檢測此元素節點是否在DOM樹,不在DOM樹就取消訂閱此刷新函數,節約內存,防止無效操作。

因此,你們可以看區別了吧。綁定屬性與插值表達式就是對應靜態模板中的JS邏輯部分,由於只允許爲表達式或單個屬性值,複雜度被控制了,強制用戶將它們轉移到VM中。 VM作爲一個數據源,對應靜態模板的數據包,並且多了一個自動觸發功能,進化成一個消息中心。

         <p ms-controller="test" ms-click="click">{{ a }}</p>
 
         <script>
             avalon.define("test", function(vm) {
                 vm.a = '123';
                 vm.click = function() {
                     vm.a = new Date - 0
                 }
             })
         </script>
        

作用域綁定(ms-controller, ms-important)

avalon提供ms-controller, ms-important來指定VM在視圖的作用範圍。比如有兩個VM,它們都有一個firstName屬性,在DIV中,如果我們用 ms-controller="VM1", 那麼對於DIV裏面的{{firstName}}就會解析成VM1的firstName中的值。

有關它們的詳細用法,可見這裏

模板綁定(ms-include)

如果單是把DOM樹作爲一個模板遠遠不夠的,比如有幾個地方,需要重複利用一套HTML結構,這就要用到內部模板或外部模板了。

內部模板是,這個模板與目標節點是位於同一個DOM樹中。我們用一個MIME不明的script標籤或者noscript標籤(0.94後支持,建議使用它)保存它,然後通過ms-include="id"引用它。

         <script type="text/avalon" id="tpl">
             here, {{ 1 + 1 }}
         </script>
 
         <div  ms-include="'tpl'"></div>
        

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

如果大家想在模板加載後,加工一下模板,可以使用data-include-loaded來指定回調的名字。

如果大家想在模板掃描後,隱藏loading什麼的,可以使用data-include-rendered來指定回調的名字。

下面是它們的實現

                var vmodels = data.vmodels
                var rendered = getBindingCallback(elem.getAttribute("data-include-rendered"), vmodels)
                var loaded = getBindingCallback(elem.getAttribute("data-include-loaded"), vmodels)

                function scanTemplate(text) {
                    if (loaded) {
                        text = loaded.apply(elem, [text].concat(vmodels))
                    }
                    avalon.innerHTML(elem, text)
                    scanNodes(elem, vmodels)
                    rendered && checkScan(elem, function() {
                        rendered.call(elem)
                    })
                }
        

外部模板,通常用於多個頁面的複用,因此需要整成一個獨立的文件。這時我們就需要通過ms-include-src="src"進行加載。

比如有一個HTML文件tmpl.html,它的內容爲:

      <div>這是一個獨立的頁面</div>
      <div>它是通過AJAX的GET請求加載下來的</div>
        

然後我們這樣引入它

         <div  ms-include-src="'tmpl.html'"></div>
        

有關它的高級應用的例子可見這裏利用ms-include與監控數組實現一個樹

數據填充(ms-text, ms-html)

這分兩種:文本綁定與HTML綁定,每種都有兩個實現方式

         <script>
            
          avalon.define("test", function(vm) {
               vm.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>
        

默認情況下,我們是使用{{ }} 進行插值,如果有特殊需求,我們還可以配置它們

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

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

avalon提供了多種方式來綁定類名,有ms-class, ms-hover, ms-active, 具體可看這裏

事件綁定(ms-on)

avalon通過ms-on-click或ms-click進行事件綁定,並在IE對事件對象進行修復,並統一了所有瀏覽器對return false的處理。具體可看這裏

avalon並沒有像jQuery設計一個近九百行的事件系統,連事件回調的執行順序都進行修復(IE6-8,attachEvent添加的回調在執行時並沒有按先入先出的順序執行),只是很薄的一層封裝,因此性能很強。

  • ms-click
  • ms-dblclick
  • ms-mouseout
  • ms-mouseover
  • ms-mousemove
  • ms-mouseenter
  • ms-mouseleave
  • ms-mouseup
  • ms-mousedown
  • ms-keypress
  • ms-keyup
  • ms-keydown
  • ms-focus
  • ms-blur
  • ms-change
  • ms-on-*
<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="UTF-8">
        <title>有關事件回調傳參</title>
        <script src="avalon.js" type="text/javascript"></script>
        <script>

            avalon.ready(function() {
                var a = avalon.define("simple", function(vm) {
                    vm.firstName = "司徒"
                    vm.lastName = "正美"
                    vm.array = ["aaa", "bbb", "ccc"]
                    vm.argsClick = function(e, a, b) {
                        alert(a+ "  "+b)
                    }
                    vm.loopClick = function(a) {
                        alert(a)
                    }
                });
                avalon.scan();
            })

        </script>
    </head>
    <body>
        <fieldset ms-controller="simple">
            <legend>例子</legend>
            <div ms-click="argsClick($event, 100, firstName)">點我</div>
            <div ms-each-el="array" >
                <p ms-click="loopClick(el)">{{el}}</p>
            </div>
        </fieldset>
    </body>
</html>
        

另外,這裏有一些結合ms-data實現事件代理的技巧,建議事件綁定接口支持事件代理,最簡單就是table上可以綁定td的點擊事件

顯示綁定(ms-visible)

avalon通過ms-visible="bool"實現對某個元素顯示隱藏控制,它用是style.display="none"進行隱藏。


插入綁定(ms-if)

這個功能是抄自knockout的,ms-if="bool",同樣隱藏,但它是將元素移出DOM。這個功能直接影響到CSS :empty僞類的渲染結果,因此比較有用。


雙工綁定(ms-duplex)

這功能抄自angular,原名ms-model起不得太好,姑且認爲利用VM中的某些屬性對錶單元素進行雙向綁定。打算啓用一個新名字叫ms-duplex

這個綁定,它除了負責將VM中對應的值放到表單元素的value中,還對元素偷偷綁定一些事件,用於監聽用戶的輸入從而自動刷新VM。具體如下:

text, password, textarea
默認是通過input事件進行監聽,舊式IE是通過propertychange實現,換言之,每改一個字符串都觸發。如果想在失去焦點時才觸發,可以在元素上使用data-duplex-event="change"進行調整。 它要求VM對應的屬性爲一個字符串或數字,不過觸發一次之後,屬性就會變成字符串。
radio
默認是通過change事件進行監聽,舊式IE是通過chick實現, 它要求VM對應的屬性爲一個布爾。在存在複數個name值相同,value值一樣的radio的情況下,我們要實現radio之間切換,則需要用到ms-duplex-text,這時VM對應的屬性爲一個字符串,爲其他一個radio的value值。
checkbox
默認是通過change事件進行監聽, 它要求VM對應的屬性爲一個字符串數組。有時,我們需要將checkbox 當成radio用,就需要用ms-duplex-radio,點我看此例子
select
默認是通過change事件進行監聽, 它要求VM對應的屬性爲一個字符串或字符串數組(視multiple的值)。

注意:ms-duplex與ms-checked不能在同時使用於一個元素節點上。

大家可以通過這個頁面進行學習。

樣式綁定(ms-css)

用法爲ms-css-name="value"

              

數據綁定(ms-data)

用法爲ms-data-name="value", 用於爲元素節點綁定HTML5 data-*屬性。


布爾屬性綁定

這主要涉及到表單元素幾個非常重要的布爾屬性,即disabed, readyOnly, selected , checked, 分別使用ms-disabled, ms-enabled, ms-readonly, ms-checked, ms-selected。ms-disabled與ms-enabled是對立的,一個true爲添加屬性,另一個true爲移除屬性。


字符串屬性綁定

這主要涉及到幾個非常常用的字符串屬性,即href, src, alt, title, value, 分別使用ms-href, ms-src, ms-alt, ms-title, ms-value。它們的值的解析情況與其他綁定不一樣,如果值沒有{{}}插值表達式,那麼就當成VM中的一個屬性,並且可以與加號,減號混用, 組成表達式,如果裏面有表達式,整個當成一個字符串。

              xxxx
              xxxx
        

萬能屬性綁定(ms-attr)

ms-attr-name="value",這個允許我們在元素上綁定更多種類的屬性,如className, tabIndex, name, colSpan什麼的。

萬能綁定(ms-bind)

已廢棄。替換方案見這裏

數組循環綁定(ms-each, ms-repeat)

用法爲ms-each-xxx="array", 其中xxx可以隨意改,如item, el, 它是用於在子元素中進行引用。array對應VM中的一個普通數組或一個監控數組。詳見這裏

ms-each與ms-repeat的不同之處在於,前者循環它的孩子,後者循環它自身。

注意,ms-each, ms-repeat會生成一個或兩個新VM插入當前的vmodels中。如果是數組的元素是簡單類型,那麼會生成一個,如果是對象,那麼就生成兩個。

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

 
         <script>
             avalon.define("test", function(vm) {
                 vm.array = [{value: "aaa", text: "111"}, {value: "bbb", text: "222"}, {value: "bbb", text: "333"}]
             })
         </script>
         <div ms-controller="test">
             <select ms-each-el="array">
                 <option ms-value="el.value">{{$index}}、{{el}}</option>
             </select>
         </div>
 
        
     <style>
            .id2013716 {
                width: 200px;
                float:left;
            }
        </style>
        <script>
            var a = avalon.define("array", function(vm) {
                vm.array = ["1", "2", "3", "4"]
            })
            setTimeout(function() {
                a.array.set(0, 7)
            }, 1000);
            var b = avalon.define("complex", function(vm) {
                vm.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 = "zzz"
            }, 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()
            }, 3000)
            setTimeout(function() {
                a.array.pop()
            }, 4000)
            setTimeout(function() {
                a.array.splice(1, 3, "x", "y", "z")
            }, 5000)
        </script>
        <fieldset class="id2013716" ms-controller="array">
            <legend>例子</legend>
            <ul ms-each-el="array">
                <li>數組的第{{$index+1}}個元素爲{{el}}</li>
            </ul>
        </fieldset>

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

對象循環綁定(ms-with)

語法爲 ms-with="obj" 子元素裏面用$key, $val分別引用鍵名,鍵值

<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script type='text/javascript' src="avalon.js"></script>
        <script>
            var a = avalon.define("xxx", function(vm) {
                vm.obj = {
                    aaa: "xxx",
                    bbb: "yyy",
                    ccc: "zzz"
                }
                vm.first = "司徒正美"
            })
            setTimeout(function() {
                a.obj.aaa = "7777777777"
                a.first = "清風火忌"
            }, 1000)
            setTimeout(function() {
                a.obj.bbb = "8888888"
            }, 3000)
        </script>
    </head>
    <body ms-controller="xxx">
        <div ms-with="obj">
            <div>{{$key}} {{$val}}</div>
        </div>
        <hr/>
        <div ms-with="obj">
            <div>{{$key}} {{$val}}</div>
        </div>
        <hr/>
        <div ms-with="obj">
            <div>{{$key}} {{$val}}</div>
        </div>
    </body>
</html>
        

UI綁定(ms-widget)

它的格式爲ms-widget="uiName, id?, optsName?"

  • uiName,必選,一定要全部字母小寫,表示組件的類型
  • id 可選 這表示新生成的VM的$id,方便我們從avalon.vmodels[id]中獲取它操作它,如果它等於$,那麼表示它是隨機生成,與不寫這個效果一樣,框架會在uiName加上時間截,生成隨機ID
  • optName 可選, 配置對象的名字。指在已有的VM中定義一個對象(最好指定它爲不可監控的外),作爲配置的一部分(因爲每個UI都有它的默認配置對象,並且我們也可以用data- uiName? -xxx來做更個性化的處理 )。如果不指optName默認與uiName同名。框架總是找離它(定義ms-widget的那個元素節點)最近的那個VM來取這個配置項。如果這個配置項裏面有widget+"Id"這個屬性,那麼新生成的VM就是用它作爲它的$id

下面是一個完整的實例用於教導你如何定義使用一個UI。

例子

首先,以AMD規範定義一個模塊,文件名爲avalon.testui.js,把它放到與avalon.js同一目錄下。內容爲:

define(["avalon"], function(avalon) {
    //    必須 在avalon.ui上註冊一個函數,它有三個參數,分別爲容器元素,data, vmodels
    avalon.ui["testui"] = function(element, data, vmodels) {
      //將它內部作爲模板,或者使用文檔碎片進行處理,那麼你就需要用appendChild方法添加回去
        var innerHTML = element.innerHTML
        //由於innerHTML要依賴許多widget後來添加的新屬性,這時如果被掃描肯定報“不存在”錯誤
        //因此先將它清空
        avalon.clearHTML(element)
        var model = avalon.define(data.testuiId, function(vm) {
            avalon.mix(vm, data.testuiOptions)//優先添加用戶的配置,防止它覆蓋掉widget的一些方法與屬性
            vm.value = 0; // 給input一個個默認的數值
            vm.plus = function(e) { // 只添加了這個plus
                model.value++;
            }
        })
        avalon.nextTick(function() {
            //widget的VM已經生成,可以添加回去讓它被掃描
            element.innerHTML = innerHTML
            avalon.scan(element, [model].concat(vmodels))
        })
        return model//必須返回新VM
    }
    avalon.ui["testui"].defaults = {
        aaa: "aaa",
        bbb: "bbb",
        ccc: "ccc"
    }
    return avalon//必須返回avalon
})
     
            

然後頁面這樣使用它

        
     <!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <script src="avalon.js"></script>
    </head>
    <body>
        <script>
            require("avalon.testui", function() {
                avalon.define("test", function(vm) {
                    vm.$opts = {
                        name: "這是控件的內容"
                    }
                })
                avalon.scan()
            })
        </script>
        <div ms-controller="test" ms-widget="testui,ddd,$opts" >
            <input ms-duplex="value" />
            <button type="button" ms-click="plus">ClickMe</button>
        </div>
    </body>
</html>
 
            

$watch

這是一個位於VM的方法,用於監聽VM的某人屬性的變化,回調中有兩個傳參,新屬性值與舊屬性值,裏面的this指向VM,詳見這裏


過濾器

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

avalon自帶以下幾個過濾器

html
沒有傳參,用於將文本綁定轉換爲HTML綁定
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;
               }
        

AMD 加載器

avalon裝備了AMD模範的加載咕咕,這涉及到兩個全局方法 require與define

require(deps, callback)

deps 必需。String|Array。依賴列表,可以是具體路徑或模塊標識,如果想用字符串表示多個模塊,則請用“,”隔開它們。

callback 必需。Function。回調,當用戶指定的依賴以及這些依賴的依賴樹都加載執行完畢後,纔會安全執行它。

模塊標識

一個模塊標識就是一個字符串,通過它們來轉換成到對應JS文件或CSS文件的路徑。

有關模塊標識的CommonJS規範,可以見 這裏

具體約定如下:

  1. 每個模塊標識的字符串組成只能是合法URL路徑,因此只能是英文字母,數字,點號,斜扛,#號。
  2. 如果模塊標識是 以"./"開頭,則表示相對於它的父模塊的目錄中找。
  3. 如果模塊標識是 以"../"開頭,則表示相對於它的父模塊的父目錄中找。
  4. 如果模塊標識不以點號或斜扛開始,則有以下三種情況 
    1. 如果此模塊標識在 $.config.alias存在對應值,換言之某一模塊定義了一個別名,則用此模塊的具體路徑加載文件。
    2. 如果此模塊標識 以http://、https://、file:/// 等協議開頭的絕對路徑,直接用它加載文件。
    3. 否則我們將在引入框架種子模塊(avalon.js)的目錄下尋找是否有同名JS文件,然後指向它。
  5. 對於JS模塊,它可以省略後綴名,即“.js”可有可無;但對於CSS需要使用css!插件機制。
  6. 框架種子模塊的目錄保存於 $.config.base屬性中。
  7. ready!是系統佔位符,用於表示DOM樹是否加載完畢,不會進行路徑轉換。

如果想禁止使用avalon自帶的加載器,可以在第一次調用require方法之前,執行如下代碼:

             
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="require.js"></script>
        <script src="avalon.mobile.js"></script>
        <script>
            avalon.config({loader: false})
            alert(require)
            avalon.define("xxx", function(vm){
                vm.aaa = "司徒正美"
            })
        </script>
    </head>
    <body ms-controller="xxx" >
        <div>{{aaa}}</div>
    </body>
</html>
        

與jquery更好的集成,比如一些舊系統,直接在頁面引入jquery庫與其大量jquery插件,改成動態加載方式成本非常大。怎麼樣才能與jquery和平共存,亦能讓AMD加載發揮作呢?先引入jquery庫, 然後將avalon.modules.jquery 加個預設值(exports: jquery用於shim機制, state: 2 表明它已經加載完畢)就行了。

例子
<!DOCTYPE html>
<html>

    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="jquery.js" type="text/javascript"></script>
        <script src="avalon.js" type="text/javascript"></script>

    </head>
    <body>
        <div ms-controller="main" ms-click="click">
            <p>
                <a href="#" >點擊我</a>
            </p>
        </div>
        <script type="text/javascript">

            avalon.modules.jquery = {
                exports: jQuery,
                state: 2
            }
            require('jquery,ready!', function($) {
                avalon.log('加載jq了啊……')
                $.ajaxSetup({
                    headers: {ajaxRequest: true},
                    beforeSend: function(o) {
                        avalon.log(typeof o)
                        avalon.log(typeof o.id)
                    },
                    complete: function(data) {
                        avalon.log('ajax 成功執行啦,阿門!')
                    }
                })
                $('body').bind("click", function(e) {
                    alert("document");
                    avalon.log(typeof e.target.$vmodel)
                    $.post('./h.js', {}, function(res) {
                        avalon.log(typeof res)
                    })
                });
            })

        </script>
    </body>
</html>
            
<!DOCTYPE html>
<html>

    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <!-->這裏沒有東西</-->
        <script src="avalon.js" type="text/javascript"></script>

    </head>
    <body>
        <div ms-controller="main" ms-click="click">
            <p>
                <a href="#" >點擊我</a>
            </p>
        </div>
        <script type="text/javascript">
/* 0.982之前可以
            avalon.config({
                alias: {
                    jquery: {
                        exports: "jQuery",//這是原來jQuery庫的命名空間,必須寫上
                        src: "jquery.js"
                    }
                }
            })
*/
//下面是兼容requirejs的方法,推薦使用這個
           avalon.config({
                paths: {
                    jquery: "http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"
                },
                shim: {
                    jquery: {
                        exports: "jQuery"//這是原來jQuery庫的命名空間,必須寫上
                    }
                }
            })
            require('jquery,ready!', function($) {
                avalon.log('加載jq了啊……')
                $.ajaxSetup({
                    headers: {ajaxRequest: true},
                    beforeSend: function(o) {
                        avalon.log(typeof o)
                        avalon.log(typeof o.id)
                    },
                    complete: function(data) {
                        avalon.log('ajax 成功執行啦,阿門!')
                    }
                })
                $('body').bind("click", function(e) {
                    alert("document");
                    avalon.log(typeof e.target.$vmodel)
                    $.post('./h.js', {}, function(res) {
                        avalon.log(typeof res)
                    })
                });
            })

        </script>
    </body>
</html>
            
例子

加載單個模塊。

             // 由於lang.js與mass.js是位於同一目錄下,可以省略./
             require("lang", function(lang) {
                 alert(lang.String.toUpperCase("aa"))
             });
            
例子

加載多個模塊。需要注意的是,涉及DOM操作時必須要待到DOM樹建完才能進入,因此我們在這裏指定了一個標識,叫"ready!", 它並不一個模塊,用戶自定義模塊,也不要起名叫"ready!"。

             require("jquery,node,attr,ready!", function($) {
                 alert($.fn.attr + ""); 
                 alert($.fn.prop + "");
             });
            
例子

加載多個模塊,使用字符串數組形式的依賴列表。

             require(["jquery", "css", "ready!"], function($, css) {
                 $("#js_require_ex3").toggle();
             });
            
例子

加載CSS文件。

             require(["jquery", "ready!", "css!http//sdfds.xdfs.css"], function($) {
                 $("#js_require_ex3").toggle();
             });
 
            
例子

使用別名機制管理模塊的鏈接。

             var path = location.protocol + "//" + location.host + "/doc/scripts/loadtest/"
/* 0.982之前可以
             require.config({
                 alias: {
                     "aaa": path + "aaa.js",
                     "bbb": path + "bbb.js",
                     "ccc": path + "ccc.js",
                     "ddd": path + "ddd.js"
                 }
             })
*/
//下面是兼容requirejs的方法,推薦使用這個
             require.config({
                paths: {
                     "aaa": path + "aaa.js",
                     "bbb": path + "bbb.js",
                     "ccc": path + "ccc.js",
                     "ddd": path + "ddd.js"
                 }
             })
             require("aaa,bbb,ready", function(a, b, $) {
                 var parent = $("#loadasync2")
                 parent.append(a);
                 parent.append(b);
                 $("#asynctest2").click(function() {
                     require("ccc,ddd", function(c, d) {
                         parent.append(c);
                         parent.append(d);
                     })
                 })
             });
            
例子

加載不按規範編寫的JS文件,可以讓你不用改jQuery的源碼就加載它。相當於其他加載器的shim插件。 與別名機制不同的是,現在它對應一個對象,src爲完整路徑,deps爲依賴列表,exports爲其他模塊引用它時,傳送給它們的參數

  !function() {
                 var path = "http://files.cnblogs.com/shuicaituya/"
                 require.config({
                     pashs: {
                         "jquery":  path + "jquery.js"
                     },
                     shim:{
                         jquery:   {
                             deps: [], //沒有依賴可以不寫
                             exports: "jQuery"
                         }
                    }
                 });
                 require("jquery", function($) {
                     alert($)
                     alert("回調調起成功");
                 })
             }()
            

define方法用於定義一個模塊,格式爲:

define( id?, deps?, factory )

 id
 可選。String。模塊ID。它最終會轉換一個URL,放於 $.modules中。
 deps
 可選。String|Array。依賴列表。
 factory
 必需。Function|Object。模塊工廠。它的參數列參爲其依賴模塊所有返回的值,如果某個模塊沒有返回值,則對應位置爲undefined
        

注意, define方法不能寫在script標籤的innerHTML中,只能寫在JS文件裏。

例子

加載不按規範編寫的JS文件,可以讓你不用改jQuery的源碼就加載它。相當於其他加載器的shim插件。 與別名機制不同的是,現在它對應一個對象,src爲完整路徑,deps爲依賴列表,exports爲其他模塊引用它時,傳送給它們的參數

 
             //aaa.js 沒有依賴不用改
             define("aaa", function() {
                 return 1
             })
 
             //bbb.js  沒有依賴不用改
             define("bbb", function() {
                 return 2
             });
             //ccc.js
             define("ccc", ["$aaa"], function(a) {
                 return 10 + a
             })
 
             //ddd/ddd.js
             define("ddd", ["$ddd"], function(c) {
                 return c + 100
             });
            

路由系統

它需要依賴於另一個獨立的組件mmRouter,用法請見這裏


通過AJAX加載新數據到已存在的VM中

AJAX可以使用jQuery或mmRequest, mmRequest體積更少,覆蓋jQuery ajax模塊的90%功能,並且在現代瀏覽器中使用了XMLHttpRequest2實現,性能更佳。

var model = avalon.define("test", function(vm){
   vm.ajaxData = {} //這是一個佔位符
   vm.arrayData = [1,2,3,4]

})

$.ajax({
  type: "GET",
  url: "xxx",
  success: function(data){
    var newData = fn(data) //fn是你自己定義一個方法,對data進行扁平化,最好變成一重的對象
    //如果你的vm.ajaxData如果是個空對象,可以直接賦值
    model.ajaxData = newData
    //如果它不是一個空對象,那麼需要使用avalon.mix,先已有的數據,新的數據,全部拷貝到一個全新的空對象中,再賦值
    //  newData = avalon.mix({}, model.ajaxData.$model, newData )//這是關鍵,防止影響原來的$model
    // model.ajaxData = newData

     
  //  model.arrayData.push.apply(model.arrayData, data.newData)//添加更多元素
  //  model.arrayData =  data.newData//直接替換

  }

})
        

擴展功能

avalon現在有三個擴展點,一是在avalon.fn上添加新的原型方法,這是用於處理DOM的,二是在avalon.bindingHandlers上添加新的綁定(ms-xxx),三是在avalon.filters添加新的過濾器。

添加原型方法就不用多說,建議儘可能返回this,實現鏈式操作,this[0]爲它包含的元素節點。

添加過濾器也很簡,翻看源碼看看lowercase如何實現就行了。

添加新綁定難一點,框架要求對應的處理函數有兩個參數,data與vmodels, data擁有如下幾個屬性:

  • element: 綁定了ms-xxx的元素,如<div ms-xxx-yyy='zzz'>innerHTML</div>,ms-xxx綁定所在的DIV元素。
  • value:是指mx-xxx綁定的這個特性節點的值,即上面的zzz。
  • param:是指mx-xxx綁定名以“-”分開幾截,除了最前面的兩部分外的東西,如這裏的“yyy”。

vmodels是指,從DOM樹最頂點到添加此綁定的元素所路過的ms-controller的值(它們都對應一個VM)。注意,ms-each, ms-with也產生VM。

現在avalon擁有如此多綁定:

            active
            alt
            animationend
            attr
            bind
            blur
            change
            checked
            class
            click
            css
            data
            dblclick
            disabled
            duplex
            each
            enabled
            focus
            hover
            href
            html
            if
            include
            keydown
            keypress
            keyup
            model
            mousedown
            mouseenter
            mouseleave
            mousemove
            mouseout
            mouseover
            mouseup
            on
            readonly
            selected
            src
            text
            title
            ui
            value
            visible
            with
        

更多學習資料

利用avalon 實現一個簡單的成績單, 教你如何使用ms-each數組循環綁定與$watch回調

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章