迷你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回调

相关文章
相关标签/搜索