迷你MVVM框架 avalonjs 入門教程

新官網

請不要無視這裏,這裏都是連接,能夠點的

視頻教程: 地址1 地址2
  1. 關於AvalonJs
  2. 開始的例子
  3. 掃描
  4. 視圖模型
  5. 數據模型
  6. 綁定
  7. 做用域綁定(ms-controller, ms-important)
  8. 忽略掃描綁定(ms-skip)
  9. 模板綁定(ms-include)
  10. 數據填充(ms-text, ms-html)
  11. 類名切換(ms-class, ms-hover, ms-active)
  12. 事件綁定(ms-on,……)
  13. 顯示綁定(ms-visible)
  14. 插入綁定(ms-if)
  15. 雙工綁定(ms-duplex)
  16. 樣式綁定(ms-css)
  17. 數據綁定(ms-data)
  18. 屬性綁定(ms-attr)
  19. 循環綁定(ms-repeat)
  20. 數組循環綁定(ms-each廢棄)
  21. 對象循環綁定(ms-with廢棄)
  22. UI綁定(ms-widget)
  23. 模塊間通訊及屬性監控 $watch,$fire, $unwatch
  24. 過濾器
  25. AMD加載器
  26. 路由系統
  27. AJAX
  28. 功能擴展
  29. 在IE6下調試avalon
  30. 權限控制
錨點路由 8.1. 路由定義 8.2. 參數定義 8.3. 業務處理 定義模板變量標識標籤 AJAX 10.1. HTTP請求 10.2. 廣義回調管理 工具函數 11.1. 上下文綁定 11.2. 對象處理 11.3. 類型斷定 其它服務 12.1. 日誌 12.2. 緩存 12.3. 計時器 12.4. 表達式函數化 12.5. 模板單獨使用 自定義模塊和服務 13.1. 模塊和服務的概念與關係 13.2. 定義模塊 13.3. 定義服務 13.4. 引入模塊並使用服務 附加模塊 ngResource 14.1. 使用引入與總體概念 14.2. 基本定義 14.3. 基本使用 14.4. 定義和使用時的佔位量 http://zouyesheng.com/angular.html

關於AvalonJS

avalon是一個簡單易用迷你的MVVM框架,它最先發佈於2012.09.15,爲解決同一業務邏輯存在各類視圖呈現而開發出來的。 事實上,這問題其實也能夠簡單地利用通常的前端模板加jQuery 事件委託 搞定,但隨着業務的膨脹, 代碼就充滿了各類選擇器與事件回調,難以維護。所以完全的將業務與邏輯分離,就只能求助於架構。 最初想到的是MVC,嘗試過backbone,但代碼不降反升,很偶爾的機會,碰上微軟的WPF, 優雅的MVVM架構當即吸引住我,我以爲這就是我一直追求的解決之道。javascript

MVVM將全部前端代碼完全分紅兩部分,視圖的處理經過綁定實現(angular有個更炫酷的名詞叫指令), 業務邏輯則集中在一個個叫VM的對象中處理。咱們只要操做VM的數據,它就天然而然地神奇地同步到視圖。 顯然全部神祕都有其內幕,C#是經過一種叫訪問器屬性的語句實現,那麼JS也有沒有對應的東西。 感謝上帝,IE8最先引入這東西(Object.defineProperty),惋惜有BUG,但帶動了其餘瀏覽器實現它,IE9+便能安全使用它。 對於老式IE,我找了很久,實在沒有辦法,使用VBScript實現了。css

Object.defineProperty或VBS的做用是將對象的某一個屬性,轉換一個setter與getter, 咱們只要劫持這兩個方法,經過Pub/Sub模式就能偷偷操做視圖。爲了記念WPF的指引,我將此項目以WPF最初的開發代號avalon來命名。 它真的能讓前端人員脫離DOM的苦海,來到數據的樂園中!html

優點

絕對的優點就是下降了耦合, 讓開發者從複雜的各類事件中掙脫出來。 舉一個簡單地例子, 同一個狀態可能跟若干個事件的發生順序與發生時的附加參數都有關係, 不用 MVC (包括 MVVM) 的狀況下, 邏輯可能很是複雜並且脆弱。 而且一般須要在不一樣的地方維護相關度很是高的一些邏輯, 稍有疏忽就會釀成 bug 不能自拔。使用這類框架能從根本上下降應用開發的邏輯難度, 而且讓應用更穩健。前端

除此以外, 也免去了一些重複的體力勞動, 一個 {value} 就代替了一行 $(selector).text(value)。 一些個經常使用的 directive 也能快速實現一些本來可能須要較多代碼才能實現的功能java

  • 使用簡單,做者是吃透了knockout, angular,rivets API設計出來,沒有太多複雜的概念, 指令數量控制得當,基本能覆蓋全部jQuery操做, 確保中小型公司的菜鳥前端與剛轉行過來的後端也能迅速上手。
  • 兼容性很是好, 支持IE6+,firefox3.5+, opera11+, safari5+, chrome4, 最近也將國產的山寨瀏覽器(360, QQ, 搜狗,獵豹, 邀遊等)加入兼容列隊 (相比其餘MVVM框架,KnockoutJS(IE6), AngularJS1.3(IE9), EmberJS(IE8), WinJS(IE9))
  • 向前兼容很是好,不會出現angular那種跳崖式升級
  • 注重性能,因爲avalon一直在那些上千的大表格里打滾,經歷長期的優化, 它能支撐14000以上綁定(相對而言,angular一個頁面只能放2000個綁定)。另,在IE10等能良好支持HTML5的瀏覽器, 還提供了avalon.modern.js這個高性能的版本。
  • 沒有任何依賴,不到5000行,壓縮後不到50KB
  • 完善的單元測試,因爲測試代碼很是龐大,放在獨立的倉庫中—— avalon.test
  • 擁有一個包含2個Grid,1個樹,1 個驗證插件等總數近50個UI組件庫 OniUI, 由去哪兒網前端架構組在全力開發與維護
  • 存在一個活躍的小社區,因爲國內已有很多公司在用,咱們都集中一個QQ羣裏互相交流幫助 QQ:79641290、228372837(註明來學avalon)
  • 支持管道符風格的過濾函數,方便格式化輸出
  • 讓DOM操做的代碼近乎絕跡,所以實現一個功能,大體把比jQuery所寫的還要少50%
  • 使用相似CSS的重疊覆蓋機制,讓各個ViewModel分區交替地渲染頁面
  • 節點移除時,智能卸載對應的視圖刷新函數,節約內存
  • 操做數據即操做DOM,對ViewModel的操做都會同步到View與Model去
  • 自帶AMD模塊加載器,免得與其餘加載器進行整合

avalon如今有三個分支:avalon.js 兼容IE6,標準瀏覽器, 及主流山寨瀏覽器(QQ, 獵豹, 搜狗, 360, 傲遊); avalon.modern.js 則只支持IE10等支持HTML5現代瀏覽器 ; avalon.mobile.js,添加了觸屏事件與fastclick支持,用於移動端node

開始的例子

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

 <!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>
               var vm = avalon.define({
                  $id: "box",
                   w: 100,
                   h: 100,
                   click: function() {
                     vm.w = parseFloat(vm.w) + 10;
                     vm.h = parseFloat(vm.h) + 10;
                   }
               })
         </script>
     </body>
 </html>
 
        

上面的代碼中,咱們能夠看到在JS中,沒有任何一行操做DOM的代碼,也沒有選擇器,很是乾淨。在HTML中, 咱們發現就是多了一些以ms-開始的屬性與{{}}標記,有的是用於渲染樣式, 有的是用於綁定事件。這些屬性或標記,實質就是avalon的綁定系統的一部分。綁定(有的框架也將之稱爲指令), 負責幫咱們完成視圖的各類操做,至關於一個隱形的jQuery。正由於有了綁定,咱們就能夠在JS代碼專一業務邏輯自己, 寫得更易維護的代碼! git

掃描

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

上面的JS代碼至關於:ajax

            avalon.ready(function() {
                var vm = avalon.define({
                      $id: "box",
                      w: 100,
                      h: 100,
                      click: function() {
                         vm.w = parseFloat(vm.w) + 10;
                         vm.h = parseFloat(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)
     }
        

視圖模型

視圖模型,ViewModel,也常常被略寫成VM,是經過avalon.define方法進行定義。生成的對象會默認放到avalon.vmodels對象上。 每一個VM在定義時必須指定$id。若是你有某些屬性不想監聽,能夠直接將此屬性名放到$skipArray數組中。

        var vm = avalon.define({
                 $id: "test",
                 a: 111,
                 b: 222,
                 $skipAarray: ["b"],
                 $c: 333,
                 firstName: "司徒",
                 lastName: "正美",
                 fullName: {//一個包含set或get的對象會被當成PropertyDescriptor,
                    set: function(val) {//裏面必須用this指向scope,不能使用scope
                        var array = (val || "").split(" ");
                        this.firstName = array[0] || "";
                        this.lastName = array[1] || "";
                    },
                    get: function() {
                        return this.firstName + " " + this.lastName;
                    }
                 },
                 array: [1,2,3],
                 array2:[{e: 1}, {e: 2}]
                 d: {
                    k: 111,
                    $skipArray: ["f"],
                    f: 2222
                 }
            })
        

接着咱們說一些重要的概念:

  • $id, 每一個VM都有$id,若是VM的某一個屬性是對象(而且它是可監控的),也會轉換爲一個VM,這個子VM也會默認加上一個$id。 但只有用戶添加的那個最外面的$id會註冊到avalon.vmodels對象上。
  • 監控屬性,通常地,VM中的屬性都會轉換爲此種屬性,當咱們以vm.aaa = yyy這種形式更改其值時,就會同步到視圖上的對應位置上。
  • 計算屬性,定義時爲一個對象,而且只存在set,get兩個函數或只有一個get一個函數。它是監控屬性的高級形式,表示它的值是經過函數計算出來的,是依賴於其餘屬性合成出來的。
  • 監控數組,定義時爲一個數組,它會添加了許多新方法,但通常狀況下與普通數組無異,但調用它的push, unshift, remove, pop等方法會同步視圖。
  • 非監控屬性,這包括框架添加的$id屬性,以$開頭的屬性,放在$skipArray數組中的屬性,值爲函數、元素節點、文本節點的屬性,總之,改變它們的值不會產生同步視圖的效果。

$skipArray 是一個字符串數組,只能放當前對象的直接屬性名,想禁止子對象的某個屬性的監聽,在那個子對象上再添加一個$skipAray數組就好了。

視圖裏面,咱們可使用ms-controller, ms-important指定一個VM的做用域。

此外,在ms-each, ms-with,ms-repeat綁定屬性中,它們會建立一個臨時的VM,咱們稱之爲代理VM, 用於放置$key, $val, $index, $last, $first, $remove等變量或方法。

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

                 var vm = avalon.define({
                     $id:   "test", 
                     test1: "點擊測試按鈕沒反應 綁定失敗";
                 });
                 vm.one = function() {
                     vm.test1 = "綁定成功";
                 };
        

也不容許在define裏面直接調用方法或ajax

avalon.define("test", function(vm){
   alert(111) //這裏會執行兩次
   $.ajax({  //這裏會發出兩次請來
      async:false,
      type: "post",
      url: "sdfdsf/fdsfds/dsdd",
      success: function(data){
          console.log(data)
          avalon.mix(vm, data)
      }
   })
})
        

應該改爲:

var vm = avalon.define({
   $id: "test",
   aaa: "", //這裏應該把全部AJAX都返回的數據都定義好
   bbb: "",

})

$.ajax({  //這裏會發出兩次請來
      async:false,
      type: "post",
      url: "sdfdsf/fdsfds/dsdd",
      success: function(data){
           for(var i in data){
               if(vm.hasOwnProperty(i)){
                  vm[i] = data[i]
               }
           }
      }
   })
 
        

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

     
      <script>
     var model : avalon.define({
          $id:  "update", 
          aaa : "str",
          bbb : false,
          ccc : 1223,
          time : new Date,
          simpleArray : [1, 2, 3, 4],
          objectArray : [{name: "a"}, {name: "b"}, {name: "c"}, {name: "d"}],
          object : {
              o1: "k1",
              o2: "k2",
              o3: "k3"
          },
          simpleArray : [1, 2, 3, 4],
          objectArray : [{name: "a", value: "aa"}, {name: "b", value: "bb"}, {name: "c", value: "cc"}, {name: "d", value: "dd"}],
          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>
        

這裏還有個例子,你們認真看看。

綁定

avalon的綁定(或指令),擁有如下三種類型:

  • {{}}插值表達式, 這是開標籤與閉標籤間,換言之,也是位於文本節點中,innerText裏。{{}}裏面能夠添加各類過濾器(以|進行標識)。值得注意的是{{}}實際是文本綁定(ms-text)的一種形式。
  • ms-*綁定屬性, 這是位於開標籤的內部, 95%的綁定都以這種形式存在。 它們的格式大概是這樣劃分的"ms" + type + "-" + param1 + "-" + param1 + "-" + param2 + ... + number = value
                        ms-skip                //這個綁定屬性沒有值
                        ms-controller="expr"   //這個綁定屬性沒有參數
                        ms-if="expr"           //這個綁定屬性沒有參數
                        ms-if-loop="expr"       //這個綁定屬性有一個參數
                        ms-repeat-el="array"    //這個綁定屬性有一個參數
                        ms-attr-href="xxxx"    //這個綁定屬性有一個參數
                        ms-attr-src="xxx/{{a}}/yyy/{{b}}"   //這個綁定屬性的值包含插值表達式,注意只有少部分表示字符串類型的屬性可使用插值表達式
                        ms-click-1="fn"       //這個綁定屬性的名字最後有數字,這是方便咱們綁定更多點擊事件 ms-click-2="fn"  ms-click-3="fn"  
                        ms-on-click="fn"     //只有表示事件與類名的綁定屬性的能夠加數字,如這個也能夠寫成  ms-on-click-0="fn"    
                        ms-class-1="xxx" ms-class-2="yyy" ms-class-3="xxx" //數字還表示綁定的次序
                        ms-css-background-color="xxx" //這個綁定屬性有兩個參數,但在css綁定裏,至關於一個,會內部轉換爲backgroundColor 
                        ms-duplex-aaa-bbb-string="xxx"//這個綁定屬性有三個參數,表示三種不一樣的攔截操做 
                    
  • data-xxx-yyy="xxx",輔助指令,好比ms-duplex的某一個輔助指令爲data-duplex-event="change",ms-repeat的某一個輔助指令爲data-repeat-rendered="yyy"

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

若是一個頁面很是複雜,就須要劃分模塊,每一個模塊交由不一樣的ViewModel去處理。咱們就要用到ms-controller與ms-important來指定ViewModel了。

咱們看下面的例子:

HTML結構

<div ms-controller="AAA">
    <div>{{name}} :  {{color}}</div>
    <div ms-controller="BBB">
        <div>{{name}} :  {{color}}</div>
        <div ms-controller="CCC">
            <div>{{name}} :  {{color}}</div>
        </div>
        <div ms-important="DDD">
            <div>{{name}} :  {{color}}</div>
        </div>
    </div>
</div>
            

ViewModel

   avalon.ready(function() {
      avalon.define({
            $id: "AAA",
            name: "liger",
          color: "green"
      });
        avalon.define({
            $id: "BBB",
            name: "sphinx",
          color: "red"
      });
      avalon.define({
            $id: "CCC",
            name: "dragon" //不存在color
      });
         avalon.define({
            $id: "DDD",
            name: "sirenia" //不存在color
      });
      avalon.scan()
  })
            
{{name}} : {{color}}
{{name}} : {{color}}
{{name}} : {{color}}
{{name}} : {{color}}

能夠看出ViewModel在DOM樹的做用範圍其實與CSS很類似,採起就近原則,若是當前ViewModel沒有此字段 就找上一級ViewModel的同名字段,這個機制很是有利於團隊協做。

若是從另外一個角度來看,因爲這種隨機組成的方式就能實現相似繼承的方式,所以咱們就沒必要在JS代碼時構建複雜的繼承體系

類的繼承體系是源自後端複雜業務的膨脹而誕生的。早在20世界80年代初期,也就是面向對象發展的初期,人們就很是看重繼承這個概念。 繼承關係蘊涵的意義是很是深遠的。使用繼承咱們能夠基於差別編程,也就是說,對於一個知足咱們大部分需求的類,能夠建立一個它的子類,重載它個別方法來實現咱們所要的功能。只子繼承一個類, 就能夠重類該類的代碼!經過繼承,咱們能夠創建完整的軟件結構分類,其中每個層均可以重用該層次以上的代碼。這是一個美麗新世界。

但類繼承的缺點也是很明顯的,在下摘錄一些:

面嚮對象語言與生俱來的問題就是它們與生俱來的這一整個隱性環境。你想要一根香蕉,但你獲得的是一頭手裏握着香蕉的大猩猩,以及整個叢林。 -- Joe Armstrong
在適合使用複合模式的共有類中使用繼承,會把這個類與它的超類永遠地束縛在一塊兒,從而人爲地限制了子類的性能

類繼承的缺點

  1. 超類改變,子類要跟着改變,違反了「開——閉」原則
  2. 不能動態改變方法實現,不能在運行時改變由父類繼承來的實現
  3. 破壞原有封裝,由於基類向子類暴露了實現細節
  4. 繼承會致使類的爆炸

所以在選擇是繼承仍是組合的問題上,avalon傾向組合。組合的使用範例就是CSS,所以也有了ms-important的誕生。

而ms-important就至關於CSS的important語句,強制這個區域使用此ViewModel,再也不往上查找同名屬性或方法!

另,爲了不未經處理的原始模板內容在頁面載入時在頁面中一閃而過,咱們可使用如下樣式(詳見這裏):

   .ms-controller,.ms-important,[ms-controller],[ms-important]{
        visibility: hidden;
    }
        

忽略掃描綁定(ms-skip)

這是ms-skip負責。只要元素定義了這個屬性,不管它的值是什麼,它都不會掃描其餘屬性及它的子孫節點了。

        <div ms-controller="test" ms-skip>
            <p 
                ms-repeat-num="cd" 
                ms-attr-name="num"
                ms-data-xxx="$index">
                {{$index}} - {{num}}
            </p>
            A:<div ms-each="arr">{{yy}}</div>
        </div>
        

模板綁定(ms-include)

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

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

<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"。

下面是它們的實現

                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-include-src須要後端服務器支持,由於用到同域的AJAX請求。

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

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

         <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>
        

默認狀況下,咱們是使用{{ }} 進行插值,若是有特殊需求,咱們還能夠配置它們

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

注意,你們不要用<, > 做爲插值表達式的界定符,由於在IE6-9裏可能轉換爲註釋節點,詳見這裏

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

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

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

事件綁定(ms-on)

avalon經過ms-on-click或ms-click進行事件綁定,並在IE對事件對象進行修復,具體可看這裏

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-scroll
  • ms-animation
  • ms-on-*
<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="UTF-8">
        <title>有關事件回調傳參</title>
        <script src="avalon.js" type="text/javascript"></script>
        <script>

          avalon.ready(function() {
              avalon.define({
                    $id: "simple", 
                    firstName: "司徒",
                    lastName: "正美",
                    array: ["aaa", "bbb", "ccc"],
                    argsClick: function(e, a, b) {
                        alert(a+ "  "+b)
                    },
                    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僞類的渲染結果,所以比較有用。

<!DOCTYPE html>
   <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>ms-if</title>
        <script t src="avalon.js"></script>
    </head>
    <body ms-controller="test">

        <ul ms-each-item="array">
            <li ms-click="$remove" ms-if="$index % 2 == 0">{{ item }} --- {{$index}}</li>
        </ul>

       
        <script type="text/javascript">

         avalon.define({
           $id: "test",
           array: "a,b,c,d,e,f,g".split(",")
        });

        </script>
    </body>
    </html>
        

這裏得介紹一下avalon的掃描順序,由於一個元素可能會存在多個屬性。總的流程是這樣的:

ms-skip --> ms-important --> ms-controller --> ms-if --> ms-repeat --> ms-if-loop --> ...-->ms-each --> ms-with --> ms-duplex

首先跑在最前面的是 ms-skip,只要元素定義了這個屬性,不管它的值是什麼,它都不會掃描其餘屬性及它的子孫節點了。而後是 ms-important, ms-controller這兩個用於圈定VM的做用域的綁定屬性,它們的值爲VM的$id,它們不會影響avalon繼續掃描。接着是ms-if,因爲一個頁面可能被當成子模塊,被不一樣的VM所做用,那麼就會出現有的VM沒有某個屬性的狀況。好比下面的狀況:


<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>ms-if</title>
        <script  src="avalon.js"></script>
    </head>
    <body ms-controller="Test">
        <h1>{{aaa}}</h1>
        <ul ms-if="array" ms-each-item="array">
            <li ms-click="$remove" ms-if="$index % 2 == 0">{{ item }} --- {{$index}}</li>
        </ul>
        <script type="text/javascript">

            avalon.define('Test', function(vm) {
                vm.aaa = "array不存在啊"
            });

        </script>
    </body>
</html>
        

若是沒有ms-if作代碼防護,確定報一大堆錯。

接着是 ms-repeat綁定。出於某些緣由,咱們不想顯示數組中的某些元素,就須要讓ms-if拖延到它們以後才起做用,這時就要用到ms-if-loop。

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>ms-if</title>
        <script  src="avalon.js"></script>
    </head>
    <body ms-controller="test">
        <h1>{{aaa}}</h1>
        <ul>
            <li ms-repeat="array" ms-if-loop="el">{{ el }}</li>
            <li>它總在最後</li>
        </ul>
        <script type="text/javascript">

            avalon.define({
                $id: "test",
                array: ["aaa", "bbb", null, "ccc"]
            });

        </script>
    </body>
</html>
        

以後就是其餘綁定,但殿後的老是ms-duplex。從ms-if-loop到ms-duplex之間的執行順序是按這些綁定屬性的首字母的小寫的ASCII碼進行排序,好比同時存在ms-attr與ms-visible綁定,那麼先執行ms-attr綁定。若是咱們想綁定多個類名,用到ms-class, ms-class-2, ms-class-3, ms-class-1,那麼執行順序爲ms-class, ms-class-1, ms-class-2, ms-class-3。若是咱們要用到綁定多個點擊事件,須要這樣綁定:ms-click, ms-click-1, ms-click-2……更具體能夠查看源碼中的scanTag, scanAttr方法。


雙工綁定(ms-duplex)

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

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

對於select type=multiple與checkbox等表示一組的元素, 須要對應一個數組;其餘表單元素則須要對應一個簡單的數據類型;若是你就是想表示一個開關,那大家能夠在radio, checkbox上使用ms-duplex-checked,須要對應一個布爾(在1.3.6以前的版本,radio則須要使用ms-duplex, checkbox使用ms-duplex-radio來對應一個布爾)。

舊(1.3.6以前) 功能
ms-duplex-checked
只能應用於radio、 checkbox
ms-duplex
只能應用於radio
ms-duplex-radio
checkbox
多用於實現GRID中的全選/全不選功能
經過checked屬性同步VM
ms-duplex-string
應用於全部表單元素
ms-duplex-text
只能應用於radio
經過value屬性同步VM
ms-duplex-boolean
應用於全部表單元素
ms-duplex-bool
只能應用於radio
value爲」true」時轉爲true,其餘值轉爲false同步VM
ms-duplex-number
應用於表單元素
沒有對應項 若是value是數字格式就轉換爲數值,不然不作轉換,而後再同步VM
ms-duplex
至關於ms-duplex-string
ms-duplex
在radio至關於ms-duplex-checked
在其餘上至關於ms-duplex-string
見上

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

注意:若是表單元素同時綁定了ms-duplex=xxx與ms-click或ms-change,而事件回調要當即獲得這個vm.xxx的值,input[type=radio]是存在問題,它不能當即獲得當前值,而是以前的值,須要在回調裏面加個setTimeout。

有關ms-duplex的詳細用法,你們能夠經過這個頁面進行學習。

<!DOCTYPE html>
<html>
    <head>
        <title>ms-duplex</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <div ms-controller="box">
            <ul>
                <li><input type="checkbox" ms-click="checkAll" ms-checked="checkAllbool"/>全選</li>
                <li ms-repeat="arr" ><input type="checkbox" ms-value="el" ms-duplex="selected"/>{{el}}</li>
            </ul>
        </div>
        <script src="avalon.js" ></script>
        <script>
            var vm = avalon.define({
                $id: "box",
                arr : ["1", '2', "3", "4"],
                selected : ["2", "3"],
                checkAllbool : false,
                checkAll : function() {
                    if (this.checked) {
                        vm.selected = vm.arr
                    } else {
                        vm.selected.clear()
                    }
                }
            })
            vm.checkAllbool = vm.arr.length === vm.selected.length
            vm.selected.$watch("length", function(n) {
                vm.checkAllbool = n === vm.arr.size()
            })
        </script> 
    </body>
</html>
        

對於非radio, checkbox, select的控件,咱們能夠經過data-duplex-changed來指定一個回調,傳參爲元素的value值,this指向元素自己,要求必須有返回值。


<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>data-duplex-changed</title>
        <script src="avalon.js"></script>
    </head>
    <body ms-controller="duplex">
        <input ms-duplex="username" data-duplex-changed="callback">
        <script type="text/javascript">
            avalon.define({
                $id: "duplex",
                username : "司徒正美",
                callback : function(val){
                    avalon.log(val)
                    avalon.log(this)
                    return this.value = val.slice(0, 10)//不能超過10個字符串
                }
            });

        </script>

    </body>
</html>
        

樣式綁定(ms-css)

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

注意:屬性值不能加入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)

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


布爾屬性綁定1.3.5後,它們都吞入ms-attr-*

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


字符串屬性綁定1.3.5後,除了ms-src, ms-href,其餘都吞入ms-attr-*

這主要涉及到幾個很是經常使用的字符串屬性,即href, src, alt, title, value, 分別使用ms-href, ms-src, ms-alt, ms-title, ms-value。它們的值的解析狀況與其餘綁定不同,若是值沒有{{}}插值表達式,那麼就當成VM中的一個屬性,而且能夠與加號,減號混用, 組成表達式,若是裏面有表達式,整個當成一個字符串。

    <a ms-href="aaa + '.html'">xxxx</a>
    <a ms-href="{{aaa}}.html">xxxx</a>
        

屬性綁定(ms-attr)

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

循環綁定(ms-repeat)

用法爲ms-repeat-xxx="array", 其中xxx能夠隨意命名(注意,不能出現大寫,由於屬性名在HTML規範中,會所有轉換爲小寫,詳見這裏),如item, el。 array對應VM中的一個普通數組或一個監控數組。監控數組擁有原生數組的全部方法,而且比它還多了set, remove, removeAt, removeAll, ensure, pushArray與 clear方法 。詳見這裏

在早期,avalon提供了一個功能類似的ms-each綁定。ms-each與ms-repeat的不一樣之處在於,前者循環它的孩子(如下圖爲例,可能包含LI元素兩邊的空白),後者循環它自身。

注意,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.array = [1,2,3]
            vm.rendered = function(action){
               if(action === "add"){
                   avalon.log("渲染完畢")//注意,咱們經過vm.array.push(4,5)添加元素,會連續兩次觸發rendered,第一次add,第二次爲index
               }
           }
           <li data-repeat-rendered="rendered" ms-repeat="array">{{el}}</li>
    

<!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>
    

 
<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body ms-controller="page">
        <h3>ms-each實現數組循環</h3>
        <div ms-each="arr">
            {{$index}} <button ms-click="$remove">{{el}} 點我刪除</button>
        </div>
        <h3>ms-repeat實現數組循環</h3>
        <table border="1" width="800px" style="background:blueviolet">
            <tr>
                <td ms-repeat="arr">
                    {{el}}  {{$first}} {{$last}}
                </td>
            </tr>
        </table>
        <h3>ms-repeat實現數組循環</h3>
        <ul>
            <li ms-repeat="arr"><button ms-click="$remove">測試{{$index}}</button>{{el}}</li>
        </ul>
        <h3>ms-repeat實現對象循環</h3>
        <ol >
            <li ms-repeat="object">{{$key}}:{{$val}}</li>
        </ol>
        <h3>ms-with實現對象循環</h3>
        <ol ms-with="object">
            <li>{{$key}}:{{$val}}</li>
        </ol>
        <h3>經過指定data-with-sorted規定只輸出某一部分建值及它們的順序,只能循環對象時有效</h3>
        <ol ms-with="bigobject" data-with-sorted="order" title='with'>
            <li>{{$key}}:{{$val}}</li>
        </ol>
        <ol title='repeat'>
            <li ms-repeat="bigobject" data-with-sorted="order">{{$key}}:{{$val}}</li>
        </ol>
        <h3>ms-repeat實現數組雙重循環</h3>
        <table border="1" style="background:yellow" width="400px">
            <tr ms-repeat="dbarray"><td ms-repeat-elem="el.array">{{elem}}</td></tr>
        </table>
        <h3>ms-each實現數組雙重循環</h3>
        <table border="1" style="background:green" width="400px">
            <tbody  ms-each="dbarray">
                <tr ms-each-elem="el.array"><td>{{elem}}</td></tr>
            </tbody>
        </table>
        <h3>ms-with實現對象雙重循環,並經過$outer訪問外面的鍵名</h3>
        <div ms-repeat="dbobjec">{{$key}}:<strong ms-repeat="$val">{{$key}} {{$val}} <span style="font-weight: normal">{{$outer.$key}}</span>| </strong></div>
        <script src="avalon.js"></script>
        <script>
            var model = avalon.define({
                $id: "page", 
                arr : ["a", "b", "c", "d", "e", "f", "g", "h"],
                object : {
                    "kkk": "vvv", "kkk2": "vvv2", "kkk3": "vvv3"
                },
                aaa : {
                    aaa2: "vvv2",
                    aaa21: "vvv21",
                    aaa22: "vvv22"
                },
                bigobject : {
                    title: 'xxx',
                    name: '777',
                    width: 30,
                    align: 'center',
                    sortable: true,
                    cols: "cols3",
                    url: 'data/stockQuote.json',
                    method: 'get',
                    remoteSort: true,
                    sortName: 'SECUCODE',
                    sortStatus: 'asc'
                },
                order : function() {
                    return ["name", "sortStatus", "sortName", "method", "align"]
                },
                dbobjec : {
                    aaa: {
                        aaa2: "vvv2",
                        aaa21: "vvv21",
                        aaa22: "vvv22"
                    },
                    bbb: {
                        bbb2: "ccc2",
                        bbb21: "ccc21",
                        bbb22: "ccc22"
                    }
                },
                dbarray : [
                    {
                        array: ["a", "b", "c"]
                    },
                    {
                        array: ["e", "f", "d"]
                    }
                ]
            });
            setTimeout(function() {
                model.object = {
                    a1: 4444,
                    a2: 5555
                }
                model.bigobject = {
                    title: 'yyy',
                    method: 'post',
                    name: '999',
                    width: 78,
                    align: 'left',
                    sortable: false,
                    cols: "cols5",
                    url: 'data/xxx.json',
                    remoteSort: false,
                    sortName: 'FAILURE',
                    sortStatus: 'bbb'
                }
            }, 3000)
        </script>
    </body>
</html>
    

數組循環綁定(ms-each)

語法與ms-repeat幾乎一致,建議用ms-repeat代替。

對象循環綁定(ms-with)

語法爲 ms-with="obj" 子元素裏面用$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 type='text/javascript' src="avalon.js"></script>
        <script>
            var a = avalon.define({
                $id: "xxx",
                obj: {
                    aaa: "xxx",
                    bbb: "yyy",
                    ccc: "zzz"
                },
                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>
    

有關ms-each, ms-repeat, ms-with更高的用法,如雙重循環什麼的,能夠看這裏

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({
                    $id: "test",
                    $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>
 
        

若是你想拿到組件的VM,可使用onInit回調實現, 詳情見這裏   或者這裏

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

avalon內置了一個強大的自定義事件系統,它在綁定在每個VM上。每個VM都擁有$watch, $unwatch, $fire這三個方法,及一個$events對象。$events是用於儲存各類回調。先從單個VM提及,若是一個VM擁有aaa這個屬性,若是咱們在VM經過$watch對它監控,那麼當aaa改變值時,它對應的回調就會被觸發!

           var vmodel = avalon.define({
                $id: "test", 
                aaa: 111
            })
             vmodel.$watch("aaa", function(newValue, oldValue){
                  avalon.log(newValue) //222
                  avalon.log(oldValue) //111
              })
            setTimeout(function(){
                vmodel.aaa = 222
            }, 1000)
    

注意,它只能監聽當前屬性的變更。

咱們還能夠經過$unwatch方法,移除對應的回調。若是傳入兩個參數,第一個是屬性名,第二個是回調,那麼只移除此回調;若是隻傳入一個屬性名,那麼此屬性關聯的全部回調都會被移除掉。

有時,咱們還綁定了一些與屬性名無關的事件回調,想觸發它,那隻能使用$fire方法了。$fire方法第一個參數爲屬性名(自定義事件名),其餘參數隨意。

           var vmodel = avalon.define({
                $id: "test", 
                aaa: 111
            })
            vmodel.$watch("cat", function(){
                avalon.log(avalon.slice(arguments)) //[1,2,3]
             })
            setTimeout(function(){
                vmodel.$fire("cat",1,2,3)
            }, 1000)
    

更高級的玩法,有時咱們想在任何屬性變化時都觸發某一個回調,這時咱們就須要$watch一個特殊的屬性了——「$all」。不一樣的是,$watch回調的參數多了一個屬性名,排在最前面。

           var vmodel = avalon.define({
                $id: "test",
                aaa: 111,
                bbb: 222,
               
            })
            vmodel.$watch("$all", function(){
                avalon.log(avalon.slice(arguments)) 
                    // ["aaa", 2, 111]
                    // ["bbb", 3, 222]
            })
            setTimeout(function(){
                vmodel.aaa = 2
                vmodel.bbb = 3
            }, 1000)
    

手動觸發$fire是位隨着高風險的,框架內部是作了處理(只有先後值發生變化纔會觸發),所以萬不得已使用它,但又爆發死循環怎麼辦?這樣就須要暫時中斷VM的屬性監控機制。使用$unwatch(),它裏面什麼也不傳,就暫時將監控凍結了。恢復它也很簡單,使用$watch(),裏面也什麼也不傳!

不過最強大的用法是實現模塊間的通訊(由於在實際項目中,一個頁面可能很是大,有多少人分塊製做,每一個人本身寫本身的VM,這時就須要經過某種機制來進行數據與方法的聯動了),這是使用$fire方法達成的。只要在$fire的自定義事件名前添加"up!", "down!", "all!"前綴,它就能實現angular類似的$emit,$broadcast功能。

<!DOCTYPE html>
<html>
    <head>
        <title>by 司徒正美</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="avalon.js"></script>
        <script>
          var vm1 = avalon.define({
                $id: "ancestor", 
                aaa : '1111111111',
                click : function() {
                    avalon.log("向下廣播")
                    vm1.$fire("down!aaa", "capture")
                }
            })
            vm1.$watch("aaa", function(v) {
                    avalon.log(v)
                    avalon.log("ancestor.aaa事件被觸發了")
            })
            var vm2 = avalon.define({
                $id: "parent", 
                text : "222222222"
                aaa : '3333333333',
                click : function() {
                    console.log("全局擴播")
                    vm2.$fire("all!aaa", "broadcast")
                }
            })
            vm2.$watch("aaa", function(v) {
                   avalon.log(v)
                   avalon.log("parent.aaa事件被觸發了")
             })
            var vm3 = avalon.define(
                $id: "son", 
                click : function() {
                    console.log("向上冒泡")
                    vm3.$fire("up!aaa", "bubble")
                }
            })
            vm3.$watch("aaa", function(v) {
                    avalon.log(v)
                    avalon.log("son.aaa事件被觸發了")
              })
        </script>
        <style>

        </style>
    </head>
    <body class="ms-controller"   ms-controller="ancestor">
        <h3>avalon vm.$fire的升級版 </h3>
        <button type="button" ms-click="click">
            capture
        </button>
        <div ms-controller="parent">
            <button type="button" ms-click="click">broadcast</button>
            <div ms-controller="son">
                <button type="button" ms-click="click">
                    bubble
                </button>
            </div>
        </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;
               }
    

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.modern.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','domReady!'], 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','domReady!'], 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","domReady!"], function($) {
                 alert($.fn.attr + ""); 
                 alert($.fn.prop + "");
             });
        
例子

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

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

加載CSS文件。

             require(["jquery", "domReady!", "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","domReady"], 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("回調調起成功");
                 })
             }()
        

若是你想用其餘AMD加載器,最好的辦法仍是建議直接打開源碼,拉到最底幾行,把加載器禁用了!

    avalon.config({
        loader: false//原來是true!!!!!!!!!!1
    })
    

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
             });
        

avalon與seajshttps://github.com/RubyLouvre/avalon/issues/313

咱們也能夠在源碼裏面直接移除AMD加載器模塊

路由系統

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


AJAX

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

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


           $.ajax({
                url: url,
                data: JSON.parse(JSON.stringify(vm.$model)), //去掉數據模型中的全部函數
                success: function(ajaxData) {
                    //須要本身在這裏定義一個函數,將缺乏的屬性補上,無用的數據去掉,
                    //格式不正確的數據轉換好 ajaxData最後必須爲一個對象
                    ajaxData = filterData(ajaxData)
                    //先已有的數據,新的數據,所有拷貝到一個全新的空對象中,再賦值,防止影響原來的$model
                    var newData = avalon.mix(true, {}, vm.$model, ajaxData)
                    for (var i in newData) {
                        if (vm.hasOwnProperty(i) && i !== "hasOwnProperty"){//安全更新數據
                            vm[i] = newData[i]
                        }
                    } 
                    
                }
            })

提交VM中的數據到後臺,要當心死循環,詳見這裏

文件上傳要用mmRequest的upload方法

擴展功能

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

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

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

添加新綁定難一點,框架bindingHandlers要求對應的處理函數有兩個參數,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。

bindingHandlers裏的函數用於初始化綁定,它會對綁定屬性作一些分解,放進parseExprProxy中,parseExprProxy會再調用parseExpr,將它轉換爲求值函數,放進行對應VM屬性的subscribers數組內(操做方爲registerSubscriber)。

bindingExecutors裏的的函數爲真正的視圖刷新函數,每當VM發生改變後,都會被執行(操做方爲notifySubscribers)。

可看這裏

如今avalon擁有如此多綁定:

在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})

權限控制

將頁面模塊化,大量使用ms-include-src,沒有權限就返回空頁面,權限夠了,但不是最高級,那它返回的模板文件也不同/p>

更多學習資料

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

相關文章
相關標籤/搜索