Code Monkey home page Code Monkey logo

avalon's Issues

改回0.82的scanNodes

在0.85中,发现一个BUG,可能是重构了ms-each绑定引发的,导致循环生成节点在旧式IE下卡死。于是使用nextTick进行解围。见下:

//0.85
    function scanNodes(parent, vmodels, callback) {
        var tags = []
        for (var i = 0, node; node = parent.childNodes[i++]; ) {
            if (node.nodeType === 1) {
                tags.push(node)
            } else if (node.nodeType === 3) {
                scanText(node, vmodels)
            }
        }
        callback && callback();
        tags.forEach(function(node) {
            if (W3C) {
                scanTag(node, vmodels) //扫描元素节点
            } else {
                avalon.nextTick(function() {
                    scanTag(node, vmodels) //扫描元素节点
                })
            }
        })
    }

随着对ms-each不断优化,决定换回0.82的版本,因为它的体积更小

//0.82
    function scanNodes(parent, vmodels, callback) {
        var nodes = []
        for (var i = 0, node; node = parent.childNodes[i++]; ) {
            nodes.push(node);
        }
        callback && callback();
        for (var i = 0; node = nodes[i++]; ) {
            if (node.nodeType === 1) {
                scanTag(node, vmodels) //扫描元素节点
            } else if (node.nodeType === 3) {
                scanText(node, vmodels) //扫描文本节点
            }
        }
    }

由于在新的ms-each绑定中不需要回调,因此第三个参数可略去

//0.9 pre
    function scanNodes(parent, vmodels) {
        var nodes = []
        for (var i = 0, node; node = parent.childNodes[i++]; ) {
            nodes.push(node);
        }
        for (var i = 0; node = nodes[i++]; ) {
            if (node.nodeType === 1) {
                scanTag(node, vmodels) //扫描元素节点
            } else if (node.nodeType === 3) {
                scanText(node, vmodels) //扫描文本节点
            }
        }
    }

$watch 兼容性问题

vm.$watch('name',function(a,b){console.log(a,b)}); 


IE7报SCRIPT16389: 未指明的错误。 

firefox 19-20.0.1 ms-visible tr colspan miss

<!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>
    <style type="text/css">
    .grid{
        border:1 solid #000;

    }
    </style>
</head> 
<body>
    <div ms-controller="grid" >
        <table cellpadding="0" border="1" cellspacing="0">
            <thead>
                <tr>
                    <td>A</td>
                    <td>B</td>
                    <td>C</td>
                </tr>
            </thead>
            <tbody ms-each-item="data">
                <tr ms-visible="item.show">
                    <td colspan="3">
                        $index
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    <script type="text/javascript">
    var $a = avalon;
    $a.ready(function(){

        var $vm = $a.define('grid',function(vm){
            vm.data = [
                {show:true},
                {show:false},
            ]
        });
        setTimeout(function(){
            $vm.data[1].show = true;
        },2000);
        $a.scan();
    });

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

modelfactory

modelfactory中

notifySubscribers(accessor);
model.$events && model.$fire(name, value, old);

是否可以理解为 前者为通知顶层的改变 后者为触发自身的事件,那为什么顶层的改变不是直接绑定到这一层上呢?,就只用一个触发

移除elem.canHaveChildren === false判定

在IE中,它有一个叫canHaveChildren的属性,对于半闭合的元素类型,如HR,IMG, BR, INPUT等返回false,但在IE9+起,它都统统返回true,因此没有意义了,移除此判定!

重构scanExpr

原来的scanExpr在IE10下有时解释出错,决定放弃正则,使用纯字符串方法处理。

//v0.84
  function scanExpr(value) {
        var tokens = [],
                left
        if (rexpr.test(value)) {
            do {
                value.replace(rexpr, function(a, b) {
                    left = RegExp.leftContext
                    value = RegExp.rightContext
                    if (left) {
                        tokens.push({
                            value: left,
                            expr: false
                        })
                    }
                    if (b) {
                        var leach = []
                        if (b.indexOf("|") > 0) { // 注意排除短路与
                            b = b.replace(rfilters, function(c, d, e) {
                                leach.push(d + (e || ""))
                                return c.charAt(0)
                            })
                        }
                        tokens.push({
                            value: b,
                            expr: true,
                            filters: leach.length ? leach : void 0
                        })

                    }
                    return ""
                });
            } while (value.indexOf(closeTag) > -1);

            if (value) {
                tokens.push({
                    value: value,
                    expr: false
                })
            }
        }

        return tokens
    }

新的实现:

function scanExpr(str) {
        var tokens = [], value, start = 0, stop
        if (rexpr.test(str)) {
            do {
                var stop = str.indexOf(openTag, start)
                if (stop == -1) {
                    break
                }
                value = str.slice(start, stop)
                if (value) {// {{ 左边的文本
                    tokens.push({
                        value: value,
                        expr: false
                    })
                }
                start = stop + openTag.length
                stop = str.indexOf(closeTag, start)
                if (stop == -1) {
                    break
                }
                value = str.slice(start, stop)
                if (value) {//{{ }} 之间的表达式
                    var leach = []
                    if (value.indexOf("|") > 0) { // 注意排除短路与
                        value = value.replace(rfilters, function(c, d, e) {
                            leach.push(d + (e || ""))
                            return c.charAt(0)
                        })
                    }
                    tokens.push({
                        value: value,
                        expr: true,
                        filters: leach.length ? leach : void 0
                    })
                }
                start = stop + closeTag.length;
            } while (1);
            value = str.slice(start);
            if (value) { //}} 右边的文本
                tokens.push({
                    value: value,
                    expr: false
                })
            }
        }
        return tokens
    }

理解 $scope 和 $json

  1. vm.$json ==> ( vm.normal || vm.pure || vm.source || vm.static || vm.original || vm.native || vm.proto ) 是否更好理解?
  2. 临时的vm 是不是也有 this.$scope.$json呢?
  3. 在dom中需要自定义 ms-each-el 用el来引用这没问题
    但在绑定事件里面,能不能用this.$scope.el ==> this.vmthis 来脱离和自定义名字el的关系?
  4. this.vm ==> this.$scope 让每一层的vm 都还是叫做 vm 是否更好?
  5. 最后,我这样子想 有什么问题吗?

ms-each 嵌套循环和$last bug

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head><title></title></head>
<body>
<table border="0" cellpadding="0" cellspacing="0" width="100%" id="news_table" style="border-collapse: collapse;">
                                    <thead>
                                        <tr id="news_head">
                                            <td>
                                                <div>
                                                    栏目1</div>
                                                    <div class="triangle">&nbsp;</div>
                                            </td>
                                            <td>
                                                <div>
                                                    栏目2</div>
                                                    <div class="triangle">&nbsp;</div>
                                            </td>
                                            <td>
                                                <div>
                                                    栏目3</div>
                                                    <div class="triangle">&nbsp;</div>
                                            </td>
                                            <td>
                                                <div>
                                                    栏目4</div>
                                                    <div class="triangle">&nbsp;</div>
                                            </td>
                                        </tr>
                                    </thead>
                                    <tbody ms-controller="market.news" ms-each-item="news">
                                        <tr ms-class="last_row:$last" ms-each-data="item">
                                            <td>
                                                {{data.Title|truncate(14,"……")}}<p class="news_date">
                                                    {{data.StartDate|truncate(9,"")}} 至 {{data.EndDate|truncate(9,"")}} /$last</p>
                                            </td>                                            
                                        </tr>                                        
                                    </tbody>
                                </table>

<script type="text/javascript">
var $info = avalon.define("market.news", function ($v) {
                $v.news = [];
            });
            $info.news.push([{
                Title:1,
                StartDate:'2013-06-18',
                EndDate:'2013-08-18'
            },
            {
                Title: 2,
                StartDate: '2013-06-18',
                EndDate: '2013-08-18'
            },
            {
                Title: 3,
                StartDate: '2013-06-18',
                EndDate: '2013-08-18'
            },{
                Title:4,
                StartDate:'2013-06-18',
                EndDate:'2013-08-18'
            }]);

$info.news.push([{
                Title:1,
                StartDate:'2013-06-18',
                EndDate:'2013-08-18'
            },
            {
                Title: 2,
                StartDate: '2013-06-18',
                EndDate: '2013-08-18'
            },
            {
                Title: 3,
                StartDate: '2013-06-18',
                EndDate: '2013-08-18'
            },{
                Title:4,
                StartDate:'2013-06-18',
                EndDate:'2013-08-18'
            }]);
            avalon.scan();
</script>
</bodu>
</html>

avalon.draggable有关滚动条的处理

if(p.scroll)
        {
            s = this.getScroll(curX, curY);
            if((s[0] != 0) || (s[1] != 0))
            {
                window.scrollTo(window.scrollX + s[0], window.scrollY + s[1]);
            }
        }

    this.getScroll = function(pX, pY)
    {
        //read window variables
        var sX = window.scrollX;
        var sY = window.scrollY;

        var wX = window.innerWidth;
        var wY = window.innerHeight;

        //set contants      
        var scroll_amount = 10; //how many pixels to scroll
        var scroll_sensitivity = 100; //how many pixels from border to start scrolling from.

        var delX = 0;
        var delY = 0;       

        //process vertical y scroll
        if(pY - sY < scroll_sensitivity)
        {
            delY = -scroll_amount;
        }
        else
        if((sY + wY) - pY < scroll_sensitivity)
        {
            delY = scroll_amount;
        }

        //process horizontal x scroll
        if(pX - sX < scroll_sensitivity)
        {
            delX = -scroll_amount;
        }
        else
        if((sX + wX) - pX < scroll_sensitivity)
        {
            delX = scroll_amount;
        }

        return [delX, delY]
    }
http://www.gotproject.com/blog/post2.html

ViewModel中vm作用域bug

描述可能不准确,具体看代码
html 代码:

<form ms-controller="login">        
  <input type="text" ms-model="user" ms-on-blur="validate.user"/>      
</form>

js 代码:

 var login=avalon.define('login',function(vm){
            vm.user='';
            vm.validate={
                user:function(){
                    avalon.log(vm.user);//取不到值
                    avalon.log(login.user);//正常
                }                
            }  
 })

异步赋值失败

var model = avalon.define("showBox", function(vm) {

    vm.array = {};
    /* 异步赋值失败 */
    setTimeout(function(){
        var data = [{"Enable":"1","Index":"0","Name":"标题"}];
        vm.array = data;
    })
});

赋值后获取不到任何属性
去掉setTimeout就可以正常使用。

影响到ajax获取数据的赋值操作

关于文档的建议

现在已经有不少文档了,不过感觉有些细节还是不太清楚。所以建议文档可以支持评论,这样可以让大家及时反馈。同时文档如果也放到github上可以让大家来修复。已经发现一些bug了,如include中,定义为tpl,但是引用的示例写为tml了。

计算属性的$watch不触发BUG

这是杭州-东灵提的BUG

<!DOCTYPE HTML>
<html id="html">
<head>
  <meta charset="utf-8">
  <title>测试用例-yuewang</title>
</head>
<body>
  <div ms-controller="test">
    <button ms-click="one">测试1</button>
    <button ms-click="two">测试2</button>
    <button ms-click="one">测试3</button>
    <br>test1: {{test1}}
    <br>test2: {{test2}}
    <br>依次点击测试1->测试2->测试3,会发现watch事件没有触发
  </div>

  <script src="avalon.mobile.js"></script>
  <script>
  // 这里的逻辑是test2的数据来自test1,test1的数据来自test0
 var model = avalon.define("test", function(vm) {
    vm.test0 = false;//false, false false   --> true true true
    vm.test1 = {
      set: function(val) {
        this.test0 = val;
        console.log(this.test0)
      },
      get: function() {
        return this.test0;
      }
    };
    vm.test2 = false;
    vm.$watch('test1', function(val) {
      console.log(222);
      vm.test2 = val;
    });
    vm.one = function() {
      console.log(111);
      vm.test1 = !vm.test1;
      console.log(333);
    };
    vm.two = function() {
      vm.test0 = !vm.test0;

    };
  });
  </script>
</body>
</html>

没修之前,最后结果是true, false,现在好了,为 true, true

isArrayLike

原来的isArrayLike出自jQuery,依赖两个比较复杂的辅助方法 getType与isWindow,性能够次

    function isArraylike(obj) {
        var length = obj.length,
                type = getType(obj)

        if (avalon.isWindow(obj)) {
            return false
        }

        if (obj.nodeType === 1 && length) {
            return true
        }

        return type === "array" || type !== "function" &&
                (length === 0 ||
                        typeof length === "number" && length > 0 && (length - 1) in obj)
    }

改成

    function isArrayLike(obj) {
        if (obj) {
            var n = obj.length
            if (+n === n && !(n % 1) && n >= 0) {
                if ({}.propertyIsEnumerable.call(obj, 'length')) {
                    return Array.isArray(obj) || typeof obj !== "function" && /^\s?function/.test(obj.item || obj.callee)
                }
                return true
            }
        }
        return false
    }

只让节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象通过

HTML插值绑定有BUG

photo.js

vm.monument = [{title:"",descriptions:""}];//结构为数组型,内Object数量会变动(但是这个并不是BUG的来源),descriptions是HTML格式的数据

photo.html

<div ms-each-md="monument" class="row">
  <div class="span4">
    <h2>{{md.title}}</h2>
    <p ms-visible="md.ishowDetail" ms-html="md.descriptions"></p> <!-- 使用ms-html -->
    <p ms-visible="md.ishowDetail">{{md.descriptions|html}}</p> <!-- 直接插入 -->
  </div>
</div>

初次显示很OK,但是当vm.monument进行变动时:

vm.monument = [{title:"",descriptions:""},{title:"",descriptions:""}];

如果使用{{md.descriptions|html}} 会抛出异常:

Uncaught Error: NotFoundError: DOM Exception 8 avalon.js:1575
(anonymous function) avalon.js:1575
updateView avalon.js:1285
notifySubscribers avalon.js:1041
accessor avalon.js:881
updateViewModel avalon.js:803
Collection.collection.set avalon.js:1894
updateViewModel avalon.js:798
accessor avalon.js:871
routerHash photo.js:376
avalon.Router.navigate avalon.router.js:375
self.checkUrl avalon.router.js:124

类型转换时存在Bug

源代码

    var monumentsVModel = avalon.define("monumentsDetail", [], function(vm) {
        vm.monument = false;
        vm.title = "相册集";
        vm.allMonument = res;
    });

这里vm.monument是在路由的时候进行绑定,开始默认为false,意思是 空数据集,相对性的有ms-visible="monumentDetail" 来判定是否要显示模块。

在路由判定绑定数据后,vm.monument 转为嵌套式的Array类型,数据示例如下:

{
        name: "普罗旺斯",
        details: [{
                title: "城市简介",
                descriptions: "普罗旺斯(Provence)\n是罗马帝国的一个行省,英文简称PACA,现为法国东南部的一个地区,毗邻地中海,和意大利接壤。从阿尔卑斯山经里昂南流的隆河(Rhone),在普罗旺斯附近分为两大支流,然后注入地中海。普罗旺斯是世界闻名的薰衣草故乡、旅游胜地。\n普罗旺斯是欧洲的“骑士之城”,位于法国东南部,[1]毗邻地中海和意大利,从地中海沿岸延伸到内陆的丘陵地区,中间有大河“Rhone”流过。自古就以靓丽的阳光和蔚蓝的天空,迷人的地中海和心醉的薰衣草,令世人惊艳。是中世纪重要文学体裁骑士抒情诗的发源地。历史上的普罗旺斯地域范围变化很大,古罗马时期普罗旺斯行省北至阿尔卑斯山,南抵比利牛斯山脉,包括整个法国南部。18世纪末法国大革命时,普罗旺斯成为5个行政省份之一。到了1960年代,法国被重新划分为22个大区,普罗旺斯属于普罗旺斯-阿尔卑斯-蓝色海岸大区。\n浪漫之城如果有人说普罗旺斯是彻底的浪漫,大概也不过分,因为这里除了很久流传的浪漫爱情传奇,还有因《马赛曲》而闻名的马赛,因《基督山伯爵》而为众人皆知的依夫岛,还有儒雅的大学城艾克斯和阿维尼翁,回味久远的中世纪山庄,街边舒适的小咖啡馆。\n普罗旺斯历史上有包括塞尚、凡高、莫奈、毕加索、夏卡尔等人均在普罗旺斯展开艺术生命的新阶段,蔚蓝海岸的享乐主义风气,也吸引了美国作家费兹杰罗、英国作家劳伦斯、科学家赫胥黎、德国诗人尼采等人前来朝圣,当然,还囊括了《山居岁月》,将普罗旺斯推向巅峰的彼得·梅尔。\n"
            }, {
                title: "地理位置",
                descriptions: "普罗旺斯位于法国南部,从诞生之日起,就谨慎地保守着她的秘密,直到英国人彼得·梅尔的到来,普罗旺斯许久以来独特生活风格的面纱才渐渐揭开。在梅尔的笔下“普罗旺斯”已不再是一个单纯的地域名称,更代表了一种简单无忧、轻松慵懒的生活方式,一种宠辱不惊,闲看庭前花开花落,去留无意,漫随天外云卷云舒的闲适意境。如果旅行是为了摆脱生活的桎梏,普罗旺斯会让你忘掉一切。\n整个普罗旺斯地区因极富变化而拥有不同寻常的魅力——天气阴晴不定,时而暖风和煦,时而海风狂野,地势跌宕起伏,平原广阔,峰岭险峻,寂寞的峡谷,苍凉的古堡,蜿蜒的山脉和活泼的都会,全都在这片法国的大地上演绎万种风情。7~8月间的薰衣草迎风绽放,浓艳的色彩装饰翠绿的山谷,微微辛辣的香味混合着被晒焦的青草芬芳,交织成法国南部最令人难忘的气息。\n"
            }, {
                title: "气候环境",
                descriptions: "气候状况\n整个普罗旺斯地区因极富变化而拥有不同寻常的魅力--天气阴晴不定,时而暖风和煦,时而冷风狂野,地势跌宕起伏,平原广阔,峰岭险峻,寂寞的峡谷,苍凉的古堡,蜿蜒的山脉和活泼的都会--全都在这片法国的大地上演绎万种风情。7-8月间的薰衣草迎风绽放,浓艳的色彩装饰翠绿的山谷,微微辛辣的香味混合着被晒焦的青草芬芳,交织成法国南部最令人难忘的气息。在美食方面,普罗旺斯最大的优势在于农产品丰富,新鲜的蔬菜水果、橄榄油、大蒜、海鲜、香料组合成食客的天堂。\n普罗旺斯地区属地中海气候,夏季干燥,冬季温和,每年日照达到300天以上。夏季通常为7月到9月,白天气温一般都在30度以上。冬季(12月-2月)气温通常在10-15度左右。尽管普罗旺斯南北气候有所差异,但总体上来说常年适合旅游,尤其是春夏秋三季的旺季。普罗旺斯冬季的风非常著名,尤其是冬季从阿尔卑斯山脉吹来的风,顺着Rhone山谷畅通无阻,有时风速甚至可以达到每小时100公里。\n地理状况\n普罗旺斯(Provence)位于法国南部。最初的普罗旺斯北起阿尔卑斯山,南到比利牛斯山脉,包括法国的整个南部区域。罗马帝国时期,普罗旺斯就被列为其所属的省份。18世纪末大革命时期,法国被分成5个不同的行政省份,普罗旺斯是其中之一。到了20世纪60年代,行政省份又被重新组合划分成22个大区,于是有了现在的普罗旺斯-阿尔卑斯大区。在温文尔雅的大学名城艾克斯、教皇之城亚维农的前后,还有那些逃过世纪变迁的中世纪小村落和古老的山镇。\n普罗旺斯出品优质葡萄美酒,其中20$Cts%$为高级和顶级酒种。由于地中海阳光充足,普罗旺斯的葡萄含有较多的糖分,这些糖转变为酒精,使普罗旺斯酒的酒精度比北方的酒高出2度。略带橙黄色的干桃红酒是最具特色的。常见的红酒有:Cotes de Provence, Coteaux d $Cts'$Aix en Provence, Bandol。普罗旺斯地理位置  普罗旺斯地理位置\n从地中海沿岸延伸到内陆的丘陵地区,中间有大河“Rhone”流过,很多历史城镇,自古就以靓丽的阳光和蔚蓝的天空,迷人的地中海和心醉的薰衣草,令世人惊艳。\n薰衣草的传说\n话说普罗旺斯的村里有个少女,一个人独自在寒冷的山中采著含苞待放的花朵,但是却遇到了一位来自远方但受伤的旅人,少女一看到这位青年,整颗心便被他那风度翩翩的笑容给俘虏了!\n於是少女便将他请到家中,也不管家人的反对,坚持要照顾他直到痊愈,而过了几天後,青年旅人的伤也已经康复,但两人的恋情却急速蔓延,已经到了难分难舍的地步。\n不久後的某日,青年旅人向少女告别离去,而正处於热恋中的少女却坚持要随青年离去,虽然亲人们极力挽留,但她还是坚持要和青年一起到开满玫瑰花的故乡!就在少女临走的前一刻,村子里的老太太给了她一束薰衣草,要她用这束薰衣草来试探青年旅人的真心,因为...传说薰衣草的香气能让不洁之物现形.\n正当旅人牵起她的手准备远行时,少女便将藏在大衣里的薰衣草丢掷在青年的身上,没想到,青年的身上发出一阵紫色的轻烟之後,就随著风烟消云散了!而少女在山谷中还彷佛隐隐的听到青年爽朗的笑声,就这样,留下了少女一人孤形影单......\n没过多久,少女竟也不见踪影,只留下一句“其实我就是你想远行的心”.\n有人认为她和青年一样幻化成轻烟消失在山谷中,也有人说,她循著玫瑰花香去寻找青年了......\n"
            }, {
                title: "风景名胜",
                descriptions: "薰衣草观赏:\n吕贝隆山区(Luberon)Sault修道院的花田是该区最著名的薰衣草观赏地,也是《山居岁月》一书的故事背大片的薰衣草  大片的薰衣草\n景,号称全法国最美丽的山谷之一。山上有一座12世纪的修道院,塞南克修道院前方有一大片的薰衣草花田,是由院里的修道士栽种的,有不同颜色的薰衣草。\n施米雅那山区(Simiane-la-Rotonde)的施米雅那是一座极具特色的山城,山顶矗立着一座建于12至13世纪的城堡罗通德,环绕着一大片的薰衣草花田。站在施米雅那城镇里,随处可见到紫色花田,无边无际地蔓延。\n周边小城游:\nLuberon(吕贝隆)是沃克里兹省的南部地区,彼得·梅尔的《普罗旺斯的一年》中所写的地方就在这里。Roussillon(鲁西庸)是座彩色的村庄,桃红、鲜橙、明黄的房子像天使的玩具,随意散落在村中。\nGordes是座岩石山庄,Gordes村里还有一座薰衣草博物馆,门口一辆老式的薰衣草压油机。博物馆里展示了薰衣草农田里的各种用具。\n艾克斯市是画家保尔·塞尚的故乡,自中世纪就是一座大学城,也是著名的“泉城”。这里是罗马普罗旺斯的古都。该市以独特的烹饪、玫瑰红葡萄酒,以及特贝的语言--普罗旺斯方言闻名。在奥郎日,你可以坐在罗马时代的圆形露天剧场看戏;在阿尔,你可以坐在咖啡厅里消磨一个下午。这里每年7月,还会举办一个很时髦石头城的国际摄影节,在石头古巷和小广场上,展览当今缔造潮流的大摄影师和风流人物。\n波城古堡游:\n波城古堡是指位于亚耳附近地区的波城·普罗旺斯(Les Baux de Provence)的古城塞遗迹。波城这里曾经是被诗人米斯特拉称为“鹫族”的英勇的波城一族驻守的城塞,后来经历了无数次战波城古堡  波城古堡\n争硝烟的洗礼,波城古堡于路易13世在位期间毁于战火,现在保留的是当年的古堡废墟可供游人参观。\n波城古堡的入口处是波城历史博物馆(Musée d'Histoire des Baux),展示城堡当年鼎盛时期的历史资料与文物,站在城堡顶端,环顾四方,亚耳古城等周边风光尽收眼底,据说北方的地狱谷(Va d'Enfer)是触发但丁撰写《神曲·地狱篇》的地方。\n"
            }
        ]
    }

这里相对定的HTML代码如下(夹杂着测试代码):

        <div ms-each-md="monument.details" class="row">
          <span>{{monument.name}}</span>
          <div class="span4">
            <h2>{{monument.details}}</h2>
            <h2>{{$index}}</h2>
            <h2>{{md}}</h2>
            <h2>{{md.title}}</h2>
            <p>{{md.descriptions}}</p>
          </div>
        </div>

如上,单层的 {{monument.name}};{{monument.details}}可以正常显示,但是 {{md.title}};{{md.descriptions}} 无法正常显示。

在已经变成Array的基础上,后面再次绑定其他数据也不行。

如果将 vm.monument = res[0]; 将数据的json结构定下来后(不是空Array),后期就可以正常显示。

不知道是不是我书写违规,这种写法我也是在DEMO里面学的,就是ms-each会出问题。其他情况获取单层的数据很正常。

avalon支持CSS3 keyframe动画结束事件了

你可以在JS中使用 avalon.bind(el, "animationed" , callback),框架就帮你做好适配
或直接在视图中使用ms-animationend绑定

CSS3 keyframe动画可运行于IE10, chrome4+, safari5+, firefox5+, opera12+

<!DOCTYPE html>
<html>
    <head>
        <title>by 司徒正美</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="avalon.js"></script>
        <script src="page.js"></script>
        <script type="text/javascript">


        </script>
        <style>

            .panels div:nth-child(1){
                background:green;
            }
            .panels div:nth-child(2){
                background:blue;
            }
            .panels div:nth-child(3){
                background:violet;
            }
            .panels  div:nth-child(4){
                background:red;
            }
            .parent{
                width:800px;
                height:400px;
                position: relative;
                 overflow: hidden;
            }
            .pt-perspective {
                position: relative;
                width: 100%;
                height: 100%;
                -webkit-perspective: 1200px;
                -moz-perspective: 1200px;
                perspective: 1200px;
            }

            .pt-page {
                width: 100%;
                height: 100%;
                position: absolute;
                top: 0;
                left: 0;
                visibility: hidden;
                overflow: hidden;
                -webkit-backface-visibility: hidden;
                -moz-backface-visibility: hidden;
                backface-visibility: hidden;
                -webkit-transform: translate3d(0, 0, 0);
                -moz-transform: translate3d(0, 0, 0);
                transform: translate3d(0, 0, 0);
                -webkit-transform-style: preserve-3d;
                -moz-transform-style: preserve-3d;
                transform-style: preserve-3d;
            }
            .pt-page-current {
                visibility: visible;
                z-index: 1;
            }
            .pt-page-moveToLeft {
                -webkit-animation: moveToLeft .6s ease both;
                -moz-animation: moveToLeft .6s ease both;
                animation: moveToLeft .6s ease both;

                -webkit-animation-fill-mode: backwards;
                -moz-animation-fill-mode: backwards;
                animation-fill-mode: backwards;
            }

            .pt-page-moveFromRight {
                -webkit-animation: moveFromRight .6s ease both;
                -moz-animation: moveFromRight .6s ease both;
                animation: moveFromRight .6s ease both;
                -webkit-animation-fill-mode: backwards;
                -moz-animation-fill-mode: backwards;
                animation-fill-mode: backwards;
            }

            @-webkit-keyframes moveToLeft {
                to { -webkit-transform: translateX(-100%); }
            }
            @-moz-keyframes moveToLeft {
                to { -moz-transform: translateX(-100%); }
            }
            @keyframes moveToLeft {
                to { transform: translateX(-100%); }
            }

            @-webkit-keyframes moveFromRight {
                from { -webkit-transform: translateX(100%); }
            }
            @-moz-keyframes moveFromRight {
                from { -moz-transform: translateX(100%); }
            }
            @keyframes moveFromRight {
                from { transform: translateX(100%); }
            }

        </style>
    </head>
    <body ms-controller="apphopeui">

        <div class="tabs" ms-each-elem="panels">
            <button type="button" ms-click="changePanelIndex">按钮{{$index}}</button>
        </div>
        <br/>
        <div class="parent">
            <div class="panels pt-perspective" ms-each-el="panels">
                <div class="pt-page" 
                    ms-class-pt-page-current="0 == $index" 
                    ms-animationend="removeClass" >
                    面板{{$index}}  {{$index}}  {{$index}}  {{$index}} {{$index}}  {{$index}}  {{$index}}  {{$index}}
                    {{$index}}  {{$index}}  {{$index}}  {{$index}} {{$index}}  {{$index}}  {{$index}}  {{$index}}
                    {{$index}}  {{$index}}  {{$index}}  {{$index}} {{$index}}  {{$index}}  {{$index}}  {{$index}}
                    {{$index}}  {{$index}}  {{$index}}  {{$index}} {{$index}}  {{$index}}  {{$index}}  {{$index}}
                    {{$index}}  {{$index}}  {{$index}}  {{$index}} {{$index}} 司徒正美  {{$index}}  {{$index}}  {{$index}}
                    {{$index}}  {{$index}}  {{$index}}  {{$index}} {{$index}}  {{$index}}  {{$index}}  {{$index}}
                </div>
            </div>
        </div>
    </body>
</html>

JS中, 我们需要巧妙利用$watch方法取得前后两个要切换的页面的索引值

avalon.ready(function() {

     var outClass, inClass, pages, lock = 0, lastIndex = 0
    avalon.define("apphopeui", function(vm) {
        vm.panels = [0, 1, 2, 3]
        vm.currentPageIndex = 0;

        vm.changePanelIndex = function() {
             if (!lock) {
                        lock = 1
                        var index = this.$vmodel.$index;
                        if (lastIndex !== index) {
                            lastIndex = index
                            vm.currentPageIndex = index;
                        } else {
                            lock = 0
                        }
             }
        }
        vm.removeClass = function() {
                   lock++
                    var className = this.animClass;
                    if (className === outClass) {
                        this.classList.remove("pt-page-current")
                    }
                    var el = this
                    className.replace(/[\w-]+/g, function(c) {
                        el.classList.remove(c)
                    })

                    if (lock === 3) {
                        lock = 0;
                    }
        }
        vm.$watch("currentPageIndex", function(next, curr) {
                     if (next > curr) {
                        outClass = 'pt-page-moveToLeft';
                         inClass = 'pt-page-moveFromRight';
                    } else {
                          inClass = 'pt-page-moveFromRight';
                         outClass = 'pt-page-moveToLeft';
                    }
                    var currPage = pages[curr]
                    var nextPage = pages[next]

                    currPage.animClass = outClass;
                    outClass.replace(/[\w-]+/g, function(c) {
                        currPage.classList.add(c)
                    })

                    currPage.classList.add("pt-page-current")

                    nextPage.animClass = inClass
                    inClass.replace(/[\w-]+/g, function(c) {
                        nextPage.classList.add(c)
                    })
                    nextPage.classList.add("pt-page-current")
        })

    })

    avalon.scan()
    avalon.nextTick(function() {//因为页面只有一个切换板做模板,只有扫描后才动态生成四个
        pages = document.querySelectorAll(".panels>div")
    })
})



ms-visible 可以支持简单动画吗?

ms-visible的时候能不能加些动画?
比如:ms-visible-500="show"
vm.show = true;
效果类似 $(dom).show(500);
vm.show = false;
效果类似 $(dom).hide(500);

require 冲突bug

<!DOCTYPE html>
<html>

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

</head>
<body>
    <div ms-controller="main" ms-click="click">
        <p>
            <a href="#" >点击我</a>
        </p>
    </div>
    <script type="text/javascript">
    $.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)
            })
        });

    avalon.require('jquery',function(){
         avalon.log('加载jq了啊……')
    })

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

ms-class 一个不合预期的地方

测试

    <div ms-controller="test">
        <!-- 例1 -->
        <div ms-class="a: isA"></div>
        <!-- 例2 -->
        <div ms-class="{{isA ? 'a' : 'b'}}"></div>  
    </div>
    <script>
        var test = avalon.define('test', function(vm){
            vm.isA = true
        })
        setTimeout(function() {
            test.isA = false
        }, 1000)        
    </script>

结果,例1符合预期;例2预期得到 class="b" ,结果是 class="a b"

ms-each生成结果有问题

我的测试代码很简单:

<html>
<head>
<script src="avalon.js"></script>
</head>
<body>
<ol ms-controller="function">
    <li ms-each-item="numbers">{{item}}</li>
</ol>

</div>
<script>
avalon.ready(function()
{
    avalon.define("function", function(vm) {
        vm.numbers = [1,2,3,4]
    });
    avalon.scan();
});
</script>
</body>
</html>

但是结果不是我想要的生成4行,而是只有一行,并且数字都写到一块去了。

image

不知道是为什么。我使用了最新的git版本试了也不行,是我哪里写得不对吗?

源代码架构

你好,在阅读您的代码时遇到一些障碍,在阅读您的博客后,已经了解了基本**,能否提供下源代码的架构图,或者更详细些的资料,谢谢

avalon v4的早期实现

avalon原来是mass Framework的一个子模块,v4之前是采用knockout那样笨重的属性变方法的方式实现依赖收集与通知变更。v4时突发奇想,然后Object.defineProperties与VBS重载等于号,实现更优雅的依赖收集与通知变更。接口设计上从angular,js与rivets.js扒了不少好东西,形成现在的样子。现保存在这里,让大家看看这伟大历程

define("mvvm", "$event,$css,$attr".split(","), function($) {

    var prefix = "ms-";
    var avalon = $.avalon = {
        models: {},
        filters: {
            uppercase: function(str) {
                return str.toUpperCase()
            },
            lowercase: function(str) {
                return str.toLowerCase();
            },
            number: function(str) {
                return isFinite(str) ? str : "";
            },
            aaa: function(str) {
                return str + "AAA"
            }
        }
    };
    var blank = " ";
    var obsevers = {};
    var Publish = {};//将函数放到发布对象上,让依赖它的函数
    var expando = new Date - 0;
    var subscribers = "$" + expando;
    /*********************************************************************
     *                            View                                    *
     **********************************************************************/
    var regOpenTag = /([^{]*)\{\{/;
    var regCloseTag = /([^}]*)\}\}/;
    function hasExpr(value) {
        var index = value.indexOf("{{");
        return index !== -1 && index < value.indexOf("}}");
    }
    function forEach(obj, fn) {
        if (obj) {//不能传个null, undefined进来
            var isArray = isFinite(obj.length), i = 0
            if (isArray) {
                for (var n = obj.length; i < n; i++) {
                    fn(i, obj[i]);
                }
            } else {
                for (i in obj) {
                    if (obj.hasOwnProperty(i)) {
                        fn(i, obj[i]);
                    }
                }
            }
        }
    }
    //eval一个或多个表达式
    function watchView(text, scope, scopes, data, callback, tokens) {
        var updateView, target, filters = data.filters;
        var scopeList = [scope].concat(scopes);
        if (!filters) {
            for (var i = 0, obj; obj = scopeList[i++]; ) {
                if (obj.hasOwnProperty(text)) {
                    target = obj;//如果能在作用域上直接找到,我们就不需要eval了
                    break;
                }
            }
        }
        if (target) {
            updateView = function() {
                callback(target[text]);
            };
        } else {
            updateView = function() {

                if (tokens) {
                    var val = tokens.map(function(obj) {
                        return obj.expr ? evalExpr(obj.value, scopeList, data) : obj.value;
                    }).join("");
                } else {
                    val = evalExpr(text, scopeList, data);
                }

                callback(val);
            };
        }
        Publish[ expando ] = updateView;
        updateView();
        delete  Publish[ expando ];
    }
    function evalExpr(text, scopeList, data) {
        console.log(text)
        var uniq = {
            $occoecatio: 1
        }, names = [], args = [];

        scopeList.forEach(function(scope) {
            scope.$occoecatio = true;
            forEach(scope, function(key, val) {
                if (!uniq[key]) {
                    names.push(key);
                    args.push(val);
                    uniq[key] = 1;
                }
            });
            delete scope.$occoecatio;
        });

        if (data.compileFn) {
            console.log(data.compileFn+"")
            args.push(avalon.filters)
            return data.compileFn.apply(data.compileFn, args);
        }
        if (data.filters) {
            var random = new Date - 0, textBuffer = [], fargs;
            textBuffer.push("var ret", random, "=", text, "\r\n");
            for (var i = 0, f; f = data.filters[i++]; ) {
                var start = f.indexOf("(");
                if (start !== -1) {
                    fargs = f.slice(start + 1, f.lastIndexOf(")")).trim();
                    fargs = "," + fargs;
                    f = f.slice(0, start).trim();
                } else {
                    fargs = "";
                }
                textBuffer.push(" if(filters", random, ".", f, "){\r\n\ttry{ret", random,
                        " = filters", random, ".", f, "(ret", random, fargs, ")}catch(e){};\r\n}\r\n");
            }
            textBuffer.push("\treturn ret", random);
            text = textBuffer.join("");
            names.push("filters" + random);
            args.push(avalon.filters);
            delete data.filters;//释放内存
        } else {
            text = "\treturn " + text;
        }
        try {
            var fn = Function.apply(Function, names.concat(text));
            var val = fn.apply(fn, args);
            data.compileFn = fn;//缓存,防止二次编译
        } catch (e) {
            data.compileFn = function() {
                return "";
            };
            val = "";
        }
        uniq = textBuffer = names = null;//释放内存
        return val;
    }

    var bindingHandlers = avalon.bindingHandlers = {
        //将模型中的字段与input, textarea的value值关联在一起
        "model": function(data, scope, scopes) {
            var element = data.element;
            var tagName = element.tagName;
            if (typeof  modelBinding[tagName] === "function") {
                var array = [scope].concat(scopes);
                var name = data.node.value, model;
                array.forEach(function(obj) {
                    if (!model && obj.hasOwnProperty(name)) {
                        model = obj;
                    }
                });
                model = model || {};
                modelBinding[tagName](element, model, name);
            }
        },
        //抽取innerText中插入表达式,置换成真实数据放在它原来的位置
        //<div>{{firstName}} + java</div>,如果model.firstName为ruby, 那么变成
        //<div>ruby + java</div>
        "text": function(data, scope, scopes) {
            var node = data.node;
            watchView(data.value, scope, scopes, data, function(val) {
                node.nodeValue = val;
            });
        },
        //控制元素显示或隐藏
        "toggle": function(data, scope, scopes) {
            var element = $(data.element);
            watchView(data.value, scope, scopes, data, function(val) {
                element.toggle(!!val);
            });
        },
        //这是一个字符串属性绑定的范本, 方便你在title, alt,  src, href添加插值表达式
        //<a href="{{url.hostname}}/{{url.pathname}}.html">
        "href": function(data, scope, scopes) {
            //如果没有则说明是使用ng-href的形式
            var text = data.value.trim();
            var node = data.node;
            var simple = node.name.indexOf(prefix) === 0;
            var name = data.type;
            if (!simple && /^\{\{([^}]+)\}\}$/.test(text)) {
                simple = true;
                text = RegExp.$1;
            }
            watchView(text, scope, scopes, data, function(val) {
                data.element[name] = val;
            }, simple ? null : scanExpr(data.value));
        },
        //这是一个布尔属性绑定的范本,布尔属性插值要求整个都是一个插值表达式,用{{}}包起来
        //布尔属性在IE下无法取得原来的字符串值,变成一个布尔,因此需要用ng-disabled
        //text.slice(2, text.lastIndexOf("}}"))
        "disabled": function(data, scope, scopes) {
            var element = data.element, name = data.type,
                    propName = $.propMap[name] || name;
            watchView(data.value, scope, scopes, data, function(val) {
                element[propName] = !!val;
            });
        },
        //切换类名,有三种形式
        //1、ms-class-xxx="flag" 根据flag的值决定是添加或删除类名xxx 
        //2、ms-class=obj obj为一个{xxx:true, yyy:false}的对象,根据其值添加或删除其键名
        //3、ms-class=str str是一个类名或多个类名的集合,全部添加
        //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html
        "class": function(data, scope, scopes) {
            var element = $(data.element);
            watchView(data.value, scope, scopes, data, function(val) {
                if (data.args) {//第一种形式
                    element.toggleClass(data.args.join(""), !!val);
                } else if (typeof val === "string") {
                    element.addClass(val);
                } else if (val && typeof val === "object") {
                    forEach(val, function(cls, flag) {
                        if (flag) {
                            element.addClass(cls);
                        } else {
                            element.removeClass(cls);
                        }
                    });
                }
            });
        },
        //控制流程绑定
        "skip": function() {
            arguments[3].stopBinding = true;
        },
        "if": function(data, scope, scopes) {
            var element = data.element;
            var fragment = element.ownerDocument.createDocumentFragment();
            watchView(data.value, scope, scopes, data, function(val) {
                if (val) {
                    while (fragment.firstChild) {
                        element.appendChild(fragment.firstChild);
                    }
                } else {
                    while (element.firstChild) {
                        fragment.appendChild(element.firstChild);
                    }
                }
            });

        },
        "each": function(data, scope, scopes, flags) {
            var args = data.args, itemName = args[0] || "$data", indexName = args[1] || "$index";
            var parent = data.element;
            var scopeList = [scope].concat(scopes);
            var list = evalExpr(data.value, scopeList, data);
            var doc = parent.ownerDocument;
            var fragment = doc.createDocumentFragment();
            while (parent.firstChild) {
                fragment.appendChild(parent.firstChild);
            }
            function updateListView(method, args, len) {
                var listName = list.name;
                switch (method) {
                    case "push":
                        $.each(args, function(index, item) {
                            updateView(len + index, item);
                        });
                        break;
                    case "unshift"  :
                        list.insertBefore = parent.firstChild;
                        $.each(args, function(index, item) {
                            updateView(index, item);
                        });
                        resetIndex(parent, listName);
                        delete list.insertBefore;
                        break;
                    case "pop":
                        var node = findIndex(parent, listName, len - 1);
                        if (node) {
                            removeView(parent, listName, node);
                        }
                        break;
                    case "shift":
                        removeView(parent, listName, 0, parent.firstChild);
                        resetIndex(parent, listName);
                        break;
                    case "clear":
                        while (parent.firstChild) {
                            parent.removeChild(parent.firstChild);
                        }
                        break;
                    case "splice":
                        var start = args[0], second = args[1], adds = [].slice.call(args, 2);
                        var deleteCount = second >= 0 ? second : len - start;
                        var node = findIndex(parent, listName, start);
                        if (node) {
                            removeViews(parent, listName, node, deleteCount);
                            resetIndex(parent, listName);
                            if (adds.length) {
                                node = findIndex(parent, listName, start);
                                list.insertBefore = node;
                                $.each(adds, function(index, item) {
                                    updateView(index, item);
                                });
                                resetIndex(parent, listName);
                                delete list.insertBefore;
                            }
                        }
                        break;
                    case "reverse":
                    case "sort":
                        while (parent.firstChild) {
                            parent.removeChild(parent.firstChild);
                        }
                        $.each(list, function(index, item) {
                            updateView(index, item);
                        });
                        break;
                }
            }
            var isList = Array.isArray(list[ subscribers ] || {});
            if (isList) {
                list[ subscribers ].push(updateListView);
            }

            function updateView(index, item, clone, insertBefore) {
                var newScope = {}, textNodes = [];
                newScope[itemName] = item;
                newScope[indexName] = index;
                if (isList) {
                    var comment = doc.createComment(list.name + index);
                    if (list.insertBefore) {
                        parent.insertBefore(comment, list.insertBefore);
                    } else {
                        parent.appendChild(comment);
                    }
                }
                for (var node = fragment.firstChild; node; node = node.nextSibling) {
                    clone = node.cloneNode(true);
                    if (clone.nodeType === 1) {
                        scanTag(clone, newScope, scopeList, doc);//扫描元素节点
                    } else if (clone.nodeType === 3) {
                        textNodes.push(clone);
                    }
                    if (list.insertBefore) {
                        parent.insertBefore(clone, list.insertBefore);
                    } else {
                        parent.appendChild(clone);
                    }
                }
                for (var i = 0; node = textNodes[i++]; ) {
                    scanText(node, newScope, scopeList, doc);//扫描文本节点
                }
            }
            forEach(list, updateView);
            flags.stopBinding = true;
        }
    };
    //重置所有路标
    function resetIndex(elem, name) {
        var index = 0;
        for (var node = elem.firstChild; node; node = node.nextSibling) {
            if (node.nodeType === 8) {
                if (node.nodeValue.indexOf(name) === 0) {
                    if (node.nodeValue !== name + index) {
                        node.nodeValue = name + index;
                    }
                    index++;
                }
            }
        }
    }
    function removeView(elem, name, node) {
        var nodes = [], doc = elem.ownerDocument, view = doc.createDocumentFragment();
        for (var check = node; check; check = check.nextSibling) {
            //如果到达下一个路标,则断开,将收集到的节点放到文档碎片与下一个路标返回
            if (check.nodeType === 8 && check.nodeValue.indexOf(name) === 0
                    && check !== node) {
                break
            }
            nodes.push(check);
        }
        for (var i = 0; node = nodes[i++]; ) {
            view.appendChild(node);
        }
        return [view, check];
    }
    function removeViews(elem, name, node, number) {
        var ret = [];
        do {
            var array = removeView(elem, name, node);
            if (array[1]) {
                node = array[1];
                ret.push(array[0]);
            } else {
                break
            }
        } while (ret.length !== number);
        return ret;
    }
    function findIndex(elem, name, target) {
        var index = 0;
        for (var node = elem.firstChild; node; node = node.nextSibling) {
            if (node.nodeType === 8) {
                if (node.nodeValue.indexOf(name) === 0) {
                    if (node.nodeValue == name + target) {
                        return node;
                    }
                    index++;
                }
            }
        }
    }
    //循环绑定其他布尔属性
    var bools = "autofocus,autoplay,async,checked,controls,declare,defer,"
            + "contenteditable,ismap,loop,multiple,noshade,open,noresize,readonly,selected";
    bools.replace($.rword, function(name) {
        bindingHandlers[name] = bindingHandlers.disabled;
    });
    //建议不要直接在src属性上修改,因此这样会发出无效的请求,使用ms-src
    "title, alt, src".replace($.rword, function(name) {
        bindingHandlers[name] = bindingHandlers.href;
    });

    var modelBinding = bindingHandlers.model;
    //如果一个input标签添加了model绑定。那么它对应的字段将与元素的value连结在一起
    //字段变,value就变;value变,字段也跟着变。默认是绑定input事件,
    //我们也可以使用ng-event="change"改成change事件
    modelBinding.INPUT = function(element, model, name) {
        if (element.name === void 0) {
            element.name = name;
        }
        var type = element.type, ok;
        function updateModel() {
            model[name] = element.value;
        }
        function updateView() {
            element.value = model[name];
        }
        if (/^(password|textarea|text)$/.test(type)) {
            ok = true;
            updateModel = function() {
                model[name] = element.value;
            };
            updateView = function() {
                element.value = model[name];
            };
            var event = element.attributes[prefix + "event"] || {};
            event = event.value;
            if (event === "change") {
                $.bind(element, event, updateModel);
            } else {
                if (window.addEventListener) { //先执行W3C
                    element.addEventListener("input", updateModel, false);
                } else {
                    element.attachEvent("onpropertychange", updateModel);
                }
                if (window.VBArray && window.addEventListener) { //IE9
                    element.attachEvent("onkeydown", function(e) {
                        var key = e.keyCode;
                        if (key === 8 || key === 46) {
                            updateModel(); //处理回退与删除
                        }
                    });
                    element.attachEvent("oncut", updateModel); //处理粘贴
                }
            }

        } else if (type === "radio") {
            ok = true;
            updateView = function() {
                element.checked = model[name] === element.value;
            };
            $.bind(element, "click", updateModel);//IE6-8
        } else if (type === "checkbox") {
            ok = true;
            updateModel = function() {
                if (element.checked) {
                    $.Array.ensure(model[name], element.value);
                } else {
                    $.Array.remove(model[name], element.value);
                }
            };
            updateView = function() {
                element.checked = !!~model[name].indexOf(element.value);
            };
            $.bind(element, "click", updateModel);//IE6-8
        }
        Publish[ expando ] = updateView;
        updateView();
        delete Publish[ expando ];
    };
    modelBinding.SELECT = function(element, model, name) {
        var select = $(element);
        function updateModel() {
            model[name] = select.val();
        }
        function updateView() {
            select.val(model[name]);
        }
        $.bind(element, "change", updateModel);
        Publish[ expando ] = updateView;
        updateView();
        delete Publish[ expando ];
    };
    modelBinding.TEXTAREA = modelBinding.INPUT;
    /*********************************************************************
     *                    Collection                                    *
     **********************************************************************/
    //http://msdn.microsoft.com/en-us/library/windows/apps/hh700774.aspx
    //http://msdn.microsoft.com/zh-cn/magazine/jj651576.aspx
    //Data bindings 数据/界面绑定
    //Compatibility 兼容其他
    //Extensibility 可扩充性
    //No direct DOM manipulations 不直接对DOM操作
    function Collection(list, name) {
        var collection = list.concat();
        collection[ subscribers ] = [];
        collection.name = "#" + name;
        String("push,pop,shift,unshift,splice,sort,reverse").replace($.rword, function(method) {
            var nativeMethod = collection[ method ];
            collection[ method ] = function() {
                var len = this.length;
                var ret = nativeMethod.apply(this, arguments);
                notifySubscribers(this, method, arguments, len);
                return ret;
            };
        });
        collection.clear = function() {
            this.length = 0;
            notifySubscribers(this, "clear", []);
            return this;
        };
        collection.sortBy = function(fn, scope) {
            var ret = $.Array.sortBy(this, fn, scope);
            notifySubscribers(this, "sort", []);
            return ret;
        };
        collection.ensure = function(el) {
            var len = this.length;
            var ret = $.Array.ensure(this, el);
            if (this.length > len) {
                notifySubscribers(this, "push", [el], len);
            }
            return ret;
        };
        collection.update = function() {//强制刷新页面
            notifySubscribers(this, "sort", []);
            return this;
        };
        collection.removeAt = function(index) {//移除指定索引上的元素
            this.splice(index, 1);
        };
        collection.remove = function(item) {//移除第一个等于给定值的元素
            var index = this.indexOf(item);
            if (index !== -1) {
                this.removeAt(index);
            }
        };
        return collection;
    }
    /*********************************************************************
     *                            Subscription                           *
     **********************************************************************/
    /*
     为简单起见,我们把双向绑定链分成三层, 顶层, 中层, 底层。顶层是updateView, updateListView等需要撷取底层的值来更新自身的局部刷新函数, 中层是监控数组与依赖于其他属性的计算监控属性,底层是监控属性。高层总是依赖于低层,但高层该如何知道它是依赖哪些底层呢?

     在emberjs中,作为计算监控属性的fullName通过property方法,得知自己是依赖于firstName, lastName。
     App.Person = Ember.Object.extend({
     firstName: null,
     lastName: null,

     fullName: function() {
     return this.get('firstName') +
     " " + this.get('lastName');
     }.property('firstName', 'lastName')
     });

     在knockout中,用了一个取巧方法,将所有要监控的属性转换为一个函数。当fullName第一次求值时,它将自己的名字放到一个地方X,值为一个数组。然后函数体内的firstName与lastName在自身求值时,也会访问X,发现上面有数组时,就放进去。当fullName执行完毕,就得知它依赖于哪个了,并从X删掉数组。
     var ViewModel = function(first, last) {
     this.firstName = ko.observable(first);
     this.lastName = ko.observable(last);

     this.fullName = ko.computed(function() {
     // Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName.
     return this.firstName() + " " + this.lastName();
     }, this);
     };
     详见 subscribables/observable.js subscribables/dependentObservable.js

     */
    //http://www.cnblogs.com/whitewolf/archive/2012/07/07/2580630.html
    function getSubscribers(accessor) {
        if (typeof accessor === "string") {
            return obsevers[accessor] || (obsevers[accessor] = []);
        } else {
            return accessor[ subscribers ];
        }
    }
    function collectSubscribers(accessor) {//收集依赖于这个域的函数
        if (Publish[ expando ]) {
            var list = getSubscribers(accessor);
            $.Array.ensure(list, Publish[ expando ]);
        }
    }
    function notifySubscribers(accessor) {//通知依赖于这个域的函数们更新自身
        var list = getSubscribers(accessor);
        if (list && list.length) {
            var args = [].slice.call(arguments, 1);
            var safelist = list.concat();
            for (var i = 0, fn; fn = safelist[i++]; ) {
                if (typeof fn === "function") {
                    fn.apply(0, args); //强制重新计算自身
                }
            }
        }
    }
    /*********************************************************************
     *                            Model                                   *
     **********************************************************************/
    $.model = function(name, obj) {
        name = name || "root";
        if (avalon.models[name]) {
            $.error('已经存在"' + name + '"模块');
        } else {
            var model = modelFactory(name, obj, $.skipArray || []);
            model.$modelName = name;
            return avalon.models[name] = model
        }
    };
    var startWithDollar = /^\$/;
    function modelFactory(name, obj, skipArray) {
        var model = {}, first = [], second = [];
        forEach(obj, function(key, val) {
            //如果不在忽略列表内,并且没有以$开头($开头的属性名留着框架使用)
            if (skipArray.indexOf(key) === -1 && !startWithDollar.test(key)) {
                //相依赖的computed
                var accessor = name + key, old;
                if (Array.isArray(val) && !val[subscribers]) {
                    model[key] = Collection(val, accessor);
                } else if (typeof val === "object") {
                    if ("set" in val && Object.keys(val).length <= 2) {
                        Object.defineProperty(model, key, {
                            set: function(neo) {
                                if (typeof val.set === "function") {
                                    val.set.call(model, neo); //通知底层改变
                                } else {
                                    obj[key] = neo;
                                }
                                if (old !== neo) {
                                    old = neo;
                                    notifySubscribers(accessor); //通知顶层改变
                                }
                            },
                            //get方法肯定存在,那么肯定在这里告诉它的依赖,把它的setter放到依赖的订阅列表中
                            get: function() {
                                var flagDelete = false;
                                if (!obsevers[accessor]) {
                                    flagDelete = true;
                                    Publish[ expando ] = function() {
                                        notifySubscribers(accessor); //通知顶层改变
                                    };
                                    obsevers[accessor] = [];
                                }
                                old = val.get.call(model);
                                obj[name] = old;
                                if (flagDelete) {
                                    delete Publish[ expando ];
                                }
                                return old;
                            },
                            enumerable: true
                        });
                        second.push(key);
                    } else {

                    }
                } else if (typeof val !== "function") {
                    Object.defineProperty(model, key, {
                        set: function(neo) {
                            if (obj[key] !== neo) {
                                obj[key] = neo;
                                //通知中层,顶层改变
                                notifySubscribers(accessor);
                            }
                        },
                        get: function() {
                            //如果中层把方法放在Publish[ expando ]中
                            if (!obj.$occoecatio){//为了防止它在不合适的时候收集订阅者,添加$occoecatio标识让它瞎掉
                                collectSubscribers(accessor);
                            }

                            return obj[key];
                        },
                        enumerable: true
                    });
                    first.push(key);
                }
            }
        });
        first.forEach(function(key) {
            model[key] = obj[key];
        });
        second.forEach(function(key) {
            first = model[key];
        });
        return  model;
    }
    /*********************************************************************
     *                           Scan                                     *
     **********************************************************************/
    function scanTag(elem, scope, scopes, doc) {
        scopes = scopes || [];
        var flags = {};
        scanAttr(elem, scope, scopes, flags);//扫描特点节点
        if (flags.stopBinding) {//是否要停止扫描
            return false;
        }
        if (flags.newScope) {//更换作用域, 复制父作用域堆栈,防止互相影响
            scopes = scopes.slice(0);
            scope = flags.newScope;
        }
        if (elem.canHaveChildren === false || !stopScan[elem.tagName.toLowerCase()]) {
            var textNodes = [];
            for (var node = elem.firstChild; node; node = node.nextSibling) {
                if (node.nodeType === 1) {
                    scanTag(node, scope, scopes, doc);//扫描元素节点
                } else if (node.nodeType === 3) {
                    textNodes.push(node);
                }
            }
            for (var i = 0; node = textNodes[i++]; ) {//延后执行
                scanText(node, scope, scopes, doc);//扫描文本节点
            }
        }
    }
    var stopScan = $.oneObject("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,wbr,script,style".toUpperCase());
    //扫描元素节点中直属的文本节点,并进行抽取
    function scanText(textNode, scope, scopes, doc) {
        var bindings = extractTextBindings(textNode, doc);
        if (bindings.length) {
            executeBindings(bindings, scope, scopes);
        }
    }
    function scanExpr(value) {
        var tokens = [];
        if (hasExpr(value)) {
            //抽取{{ }} 里面的语句,并以它们为定界符,拆分原来的文本
            do {
                value = value.replace(regOpenTag, function(a, b) {
                    if (b) {
                        tokens.push({
                            value: b,
                            expr: false
                        });
                    }
                    return "";
                });
                value = value.replace(regCloseTag, function(a, b) {
                    if (b) {
                        var filters = []
                        if (b.indexOf("|") > 0) {
                            b = b.replace(/\|\s*(\w+)\s*(\([^)]+\))?/g, function(c, d, e) {
                                filters.push(d + e)
                                return ""
                            });
                        }
                        tokens.push({
                            value: b,
                            expr: true,
                            filters: filters.length ? filters : void 0
                        });
                    }
                    return "";
                });
            } while (hasExpr(value));
            if (value) {
                tokens.push({
                    value: value,
                    expr: false
                });
            }
        }
        return tokens;
    }

    function scanAttr(el, scope, scopes, flags) {
        var bindings = [];
        for (var i = 0, attr; attr = el.attributes[i++]; ) {
            if (attr.specified) {
                var isBinding = false, remove = false;
                if (attr.name.indexOf(prefix) !== -1) {//如果是以指定前缀命名的
                    var type = attr.name.replace(prefix, "");
                    if (type.indexOf("-") > 0) {
                        var args = type.split("-");
                        type = args.shift();
                    }
                    remove = true;
                    isBinding = typeof bindingHandlers[type] === "function";
                } else if (bindingHandlers[attr.name] && hasExpr(attr.value)) {
                    type = attr.name; //如果只是普通属性,但其值是个插值表达式
                    isBinding = true;
                }
                if (isBinding) {
                    bindings.push({
                        type: type,
                        args: args,
                        element: el,
                        node: attr,
                        remove: remove,
                        value: attr.nodeValue
                    });
                }
                if (!flags.newScope && type === "controller") {//更换作用域
                    var temp = avalon.models[attr.value];
                    if (typeof temp === "object" && temp !== scope) {
                        scopes.unshift(scope);
                        flags.newScope = scope = temp;
                    }
                }
            }
        }
        executeBindings(bindings, scope, scopes, flags);
    }

    function executeBindings(bindings, scope, scopes, flags) {
        bindings.forEach(function(data) {
            bindingHandlers[data.type](data, scope, scopes, flags);
            if (data.remove) {//移除数据绑定,防止被二次解析
                data.element.removeAttribute(data.node.name);
            }
        });
    }

    function extractTextBindings(textNode, doc) {
        var bindings = [], tokens = scanExpr(textNode.nodeValue);
        if (tokens.length) {
            var fragment = doc.createDocumentFragment();
            while (tokens.length) {//将文本转换为文本节点,并替换原来的文本节点
                var token = tokens.shift();
                var node = doc.createTextNode(token.value);
                if (token.expr) {
                    bindings.push({
                        type: "text",
                        node: node,
                        element: textNode.parentNode,
                        value: token.value,
                        filters: token.filters
                    }); //收集带有插值表达式的文本
                }
                fragment.appendChild(node);
            }
            textNode.parentNode.replaceChild(fragment, textNode);
        }
        return bindings;
    }
/* 例子
    var model = $.model("app", {
        firstName: "xxx",
        lastName: "oooo",
        bool: false,
        array: [1, 2, 3, 4, 5, 6, 7, 8],
        select: "test1",
        color: "green",
        vehicle: ["car"],
        fullName: {
            set: function(val) {
                var array = val.split(" ");
                this.firstName = array[0] || "";
                this.lastName = array[1] || "";
            },
            get: function() {
                return this.firstName + " " + this.lastName;
            }
        }
    });
    $.model("son", {
        firstName: "yyyy"
    });
    $.model("aaa", {
        firstName: "6666"
    });
    scanTag(document.body, model, [], document);
    setTimeout(function() {
        model.firstName = "setTimeout";
    }, 2000);

    setTimeout(function() {
        model.array.reverse()
        // console.log(obsevers.applastName.join("\r\n"))
    }, 3000);
   */
});

tr visible colspan miss

<title></title> <script src="avalon.js" type="text/javascript"></script> <style type="text/css"> .grid{ border:1 solid #000;
}
</style>
A B C
$index
<script type="text/javascript"> var $a = avalon; $a.ready(function(){
    var $vm = $a.define('grid',function(vm){
        vm.data = [
            {show:true},
            {show:false},
        ]
    });
    setTimeout(function(){
        $vm.data[1].show = true;
    },2000);
    $a.scan();
});

</script>

让一个数组基于另一个数组进行排序

这是avalon v0.85另一个重大优化。当我们对 VM的数组进行排序时,要求DOM树上的节点也按这个顺序排列,这时就要用到这个算法了。原来比较慢,现在这个应该是最快的了,没有比它更快的了。

            var aaa = [1, 2, 3, 4, 5, 1]
            var bbb = [{v: 2}, {v: 3}, {v: 1}, {v: 1}, {v: 5}, {v: 4}]
            var swapTime = 0
            var isEqual = Object.is || function(x, y) {//主要用于处理NaN 与 NaN 比较
                if (x === y) {
                    return x !== 0 || 1 / x === 1 / y;
                }
                return x !== x && y !== y;
            };
            for (var i = 0, n = bbb.length; i < n; i++) {
                var a = aaa[i];
                var b = bbb[i]
                var b = b && b.v ? b.v : b
                if (!isEqual(a, b)) {
                    console.log(++swapTime)
                    var index = getIndex(a, bbb, i);
                    var el = bbb.splice(index, 1)
                    bbb.splice(i, 0, el[0])
                }
            }
            function getIndex(a, bbb, start) {
                for (var i = start, n = bbb.length; i < n; i++) {
                    var b = bbb[i];
                    var check = b && b.v ? b.v : b
                    if (isEqual(a, check)) {
                        return i
                    }
                }
            }
            console.log(JSON.stringify(bbb))

在框架中,aaa为数据模型M中的数组,bbb为视图模型VM中的数组

全新的parser

为了解决soom提出的BUG, 由于短路与或短路或导致一开始无法进入某些分支,就无法取得其依赖关系。另外,之前的使用with,效率有点低。

<!DOCTYPE HTML>
<html id="html">
<head>
  <meta charset="utf-8">
  <title>测试用例</title>
</head>
<body>
  <div ms-controller="test">
    <button ms-click="one">测试1</button>
    <button ms-click="two">测试2</button>
    <br>test1: {{test1}}
    <br>test2: {{test2}}
    <br>上边两个变量变true的时候,下面的表达式是false的
    <br>test1 && test2, result: {{test1 && test2}} and {{test2 && test1}}
    <br>ps: {{test2 || test1}}
  </div>

  <script src="avalon.mobile.js"></script>
  <script>
  avalon.define("test", function(vm) {
    vm.test1 = false;
    vm.test2 = false;
    vm.one = function() {
      vm.test1 = false;
      vm.test2 = false;

      vm.test1 = true;
      vm.test2 = true;
    };
    vm.two = function() {
      vm.test1 = false;
      vm.test2 = false;

      vm.test2 = true;
      vm.test1 = true;
    };
  });
  avalon.scan();
  </script>
</body>
</html>

新的parser, 设法取得里面的变量,然后把所有赋值语句放在前面,从而解决这问题

    var KEYWORDS =
            // 关键字
            'break,case,catch,continue,debugger,default,delete,do,else,false'
            + ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
            + ',throw,true,try,typeof,var,void,while,with'

            // 保留字
            + ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
            + ',final,float,goto,implements,import,int,interface,long,native'
            + ',package,private,protected,public,short,static,super,synchronized'
            + ',throws,transient,volatile'

            // ECMA 5 - use strict
            + ',arguments,let,yield'

            + ',undefined';
    var REMOVE_RE = /\/\*(?:.|\n)*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|'[^']*'|"[^"]*"|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g;
    var SPLIT_RE = /[^\w$]+/g;
    var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g');
    var NUMBER_RE = /\b\d[^,]*/g;
    var BOUNDARY_RE = /^,+|,+$/g;
    var getVariables = function(code) {
        code = code
                .replace(REMOVE_RE, '')

                .replace(SPLIT_RE, ',')


                .replace(KEYWORDS_RE, '')
                .replace(NUMBER_RE, '')
                .replace(BOUNDARY_RE, '');

        code = code ? code.split(/,+/) : [];

        return code;
    };


    function addScope(vars, scope, index) {
        var ret = [], prefix = " = vm" + index + "."
        for (var i = vars.length, name; name = vars[--i]; ) {
            name = vars[i]
            if (scope.hasOwnProperty(name)) {
                ret.push(name + prefix + name)
                vars.splice(i, 1)
            }
        }
        return ret

    }
    function getValueFunction(code, scopes) {
        var vars = getVariables(code), ret, variables = [], uniq = {};
        vars = vars.filter(function(el){
            if(!uniq[el]){
                uniq[el] = 1
                return true;
            }
            return false
        })
        var args = []
        for (var i = 0, scope, n = scopes.length; i < n; i++) {
            if (vars.length) {
                args.push("vm" + i)
                variables.push.apply(variables, addScope(vars, scopes[i], i))
            }
        }
        var pre = variables.join(", ")
        if (pre) {
            pre = "var " + pre
        }
        args.push(pre + "\nreturn " +code)
        return Function.apply(Function,args)
    }
    var obj = {test1:23, test2:"sdfsd"}

 var fn = getValueFunction("test1 && test2 ",[obj])  

 console.log(fn+"")

 fn(obj)

生成的求值函数为:

function anonymous(vm0) {
      var test2 = vm0.test2, test1 = vm0.test1
      return test1 && test2 
}

如果存在过滤器,那么应该生成

    function anonymous(vm0, filters123143213) {
        var test1= vm0.test1
        var ret123456 = test1
        if (filters123143213.html) {
            ret123456 = filters123143213.html(ret123456)
        }
        return ret123456
    }

ms-include 有bug

文档扫描时你把···{{}}···这样的标签全部给删光了,但是在<script type="text/avalon">这种标签应该直接Pass,直到include的时候才进行初始化。

比如

    <script type="text/avalon" id="index">
        <div ms-controller="index">
            <h2>{{message}}</h2>
            <input ms-model="message">
        </div>
    </script>
        var model = avalon.define("index", function(vm){
            vm.message = "index";
        }); 

通过

<div  ms-include="'index'"></div>

进行引入

在Chrome中出现的DOM是

<div>
        <div>
            <h2>message</h2>
            <input>
        </div>
    </div>

IE6-8是

<div>
        <div>
            <h2>index</h2>
            <input>
        </div>
    </div>

貌似IE是正确的,但是当我在input 中进行输入时,<h2>标签里面的内容没有发生任何改变。

ms-data绑定bug

avalon 0.9 独立版 by 司徒正美 2013.7.20

ms-data-xxx="/mod/list/"

当值中最后为 / 时 报错

代码:

<!DOCTYPE HTML>
<html id="html">
    <head>
        <meta charset="utf-8">
        <title>测试用例</title>
    </head>
    <body>
        <div ms-controller="test">
           <input type="text" ms-duplex="name" ms-data-path="/xxx/as/">
        </div>

        <script src="avalon.js"></script>
        <script>


            avalon.define("test", function(vm) {

               vm.name='';

                vm.$watch('name',function(a,b){console.log(a,b)});

            });
            avalon.scan();
        </script>
    </body>
</html>

在更新数组时,不是采用替換,而是更新,造成数据不正确

<div ms-controller="test">
<ul ms-each-item="items">
    <li ms-click="edit1">{{item.name}}:{{item.choices}}</li>
</ul>
<a href="#" ms-click="change">change</a>
</div>
<script>
avalon.define('test', function(vm){
    vm.items = [{'name':'a1', 'choices':1}, 
        {'name':'a2', 'choices':2}];
    vm.edit1 = function(){
        console.log(this.$vmodel.item)
    }
    vm.change = function(){
        vm.items = [{'name':'a3', 'choices':3}, 
        {'name':'a4'}];

    }
});
</script>

以上是一个示例。我的想法是点击change之后,更新items数据。但是注意,更新后,有一个值只有 {'name':'a4'},它并没有包含 'choices'。但是发现虽然我没有设置choices属性,但是在更新后仍然带有这个属性。并且它是上一次对应序号的值留下来的,即它是a2的choices值。

大概看了一下代码,好象是updateViewModel是更新,不是替換造成的。

ms-if很严重的BUG

版本 v0.73

在ms-each中,如果配合ms-if,由于元素未被PUSH入DOM中,replace无效。

Uncaught Error: NotFoundError: DOM Exception 8     avalon.js:1529

全新的ms-with绑定,用于遍历对象

语法为 ms-with="obj" 子元素里面用$key, $val分别引用键名,键值
例子:

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

三元运算符+filter

<!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="grid" ms-each-item="data ">
        <p>{{item.a && item.b ? item.b / item.a : '' |percents}}</p>
    </div>
    <script type="text/javascript">
    var $a = avalon;
        $a.filters.percents = function(val){
return val + "&";
}
        var $vm = $a.define('grid',function(vm){
            vm.data = [
                {a:10,b:5},
                {a:'',b:''},
            ]
        });

        $a.scan();

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

av性能问题

正妹,发现chrome 26.0.1410.64 m 版本下,创建的vm数组有太多元素的话会,打开新的tab页时会导致浏览器崩溃。

同事提出的一个怪异现象

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="UTF-8">
        <title>scope</title>
        <script src="avalon.js" type="text/javascript"></script>
        <script>

            avalon.ready(function() {
                var a = avalon.define("simple", function(vm) {

                    vm.obj = "" //如果是vm.obj = {}就不行
                });
                setTimeout(function() {

                    a.obj = {
                        aaa: "xxxxxxxxxx"
                    }

                }, 1000)


                avalon.scan();
            })
        </script>
    </head>
    <body>
        <fieldset ms-controller="simple">
            <legend>例子</legend>
            <div id="aaa">{{obj.aaa}}</div>
        </fieldset>

    </body>
</html>

IE8无法运行

很奇怪的情况,在IEtest下,IE6、7、8并没有报错。
但是在XP的 原生IE8 下error了,错误信息如下(只是单纯引入文件就报错,v0.8.3):

网页错误详细信息

用户代理: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET4.0C; .NET4.0E)
时间戳: Sun, 7 Jul 2013 07:28:28 UTC


消息: 缺少 ')'
行: 79
字符: 5
代码: 0
URI: file:///E:/Gaubee/develop/avalon/avalon.js

ms-class绑定的新语法

之前ms-class-class1-class2-class3="prop",每次只能绑定一个类名,这个类名为"class1-class2-class3",prop为VM的属性或表达式,并且类名不能为大写(因为属性名一律被小写化了)

现在引入一种新语法,为

ms-class="aaa bbb ccc: true"  ---> addClass("aaa bbb ccc")
ms-class="xx yyy  zzzz"       ---> addClass("xx yyy zzzz")
ms-class="xxx yyy{{index}}"  --> vm.index = 200 , addClass("xxx yyy200")
ms-class="xxx yyy{{index}}: 0"--> vm.index = 200 , removeClass("xxx yyy200")
ms-class="aaa bbb: prop > 100" --> vm.prop = 1,   , removeClass("aaa bbb")
ms-class="{{1?'aaa': 'bbb'}}"  ---> addClass("aaa")
ms-class="{{1?'aaa aaaa': 'bbb bbbb bbbbb'}}" ---> addClass("aaa aaaa")

当ms-class后面不接东西,那么就进行新语法分支
属性值 变划分为两部分,以冒号分开,后一部分是可选 的
如果没有后一部分,为addClass操作,
有则根据后面部分的真假决定addClass 还是removeClass

数据仓库模块的模拟实现

简单的模拟代码:

var modelWarehouse = avalon.modelWarehouse = {
    watchers : {},
    set : function(dataName,data){
        this[dataName] = data;
        var watchersItem = this.watchers[dataName];
        console.log(watchersItem)
        if (watchersItem) {
            var watcherLen = watchersItem.length;
            for (var i = 0,watch; i < watcherLen; i+=1) {
                watch = watchersItem[i];
                if (avalon.models[watch.viewModel][watch.prototype]!=data) {
                    avalon.models[watch.viewModel][watch.prototype] = data;
                }
            };
        }
    },
    get : function(dataName){
        return this[dataName]||[];
    },
    watch : function(vmName,prototype,dataName){
        var watch = {
            viewModel:vmName,
            prototype:prototype
        };
        var watchersItem = this.watchers[dataName];
        if(watchersItem){
            for(var i=0;item = watchersItem[i];i+=1){
                if (item.viewModel === watch.viewModel&&item.prototype === watch.prototype) {
                    break;
                }
            }
            (i===watchersItem.length)&&(watchersItem[watchersItem.length] = watch);
        }else{
            this.watchers[dataName] = [watch];
        }
        return this[dataName]||[];
    }
};

初始化数据结构:

modelWarehouse.set("blogMessage",{name:"bangeel",other:{date:(new Date()).toString(),id:"2"}})
modelWarehouse.set("postsData",[]);//由于avalon对数组只进行单层的采样,所以对内部各个元素的结构使用动态的设定

使用方式:

avalon.define("postsController",function(vm){
    vm.title = "It's my blogs";
    vm.posts = modelWarehouse.watch("postsController","posts","postsData");
    vm.message = modelWarehouse.watch("postsController","message","blogMessage");
});

avalon.define("postsList",function(vm){
    vm.title = "blogs list";
    vm.posts = modelWarehouse.watch("postsList","posts","postsData");
});

avalon.scan();

更新单一的数据来源:

var postsData = [
    {
        title:"hello",
        date:"2012-12-07 18:38:37",
        content:"nothing.sorry!"
    },
    {
        title:"okkok",
        date:"2013-07-07 18:39:13",
        content:"nothing.sorry!"
    },
];
var blogMessage = {
    name:"Gaubee",
    other:{
        date:(new Date()).toString(),
        id:"3"
    }
}
setTimeout(function(){
    modelWarehouse.set("postsData",postsData);
    modelWarehouse.set("blogMessage",blogMessage);
},1000)

以上为数据管理模块,即数据仓库,保证数据源的一致性,并自动触发所有数据依赖的更新。
PS:跟Ember学习的概念

利用ms-include与监控数组实现一个树

<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="avalon.js"></script>
        <script>
            avalon.define("tree", function(vm) {
                vm.tree = [
                    {text: "aaa", subtree: [
                            {text: 1111, subtree: []},
                            {text: 2222, subtree: [
                                    {text: 777, subtree: []}
                                ]},
                            {text: 3333, subtree: [
                                    {text: 8888, subtree: []},
                                    {text: 999, subtree: []}
                                ]}
                        ]},
                    {text: "bbb", subtree: [
                            {text: 4444, subtree: []},
                            {text: 5555, subtree: []},
                            {text: 6666, subtree: []}
                        ]},
                    {text: "ccc", subtree: []},
                    {text: "ddd", subtree: []},
                    {text: "eee", subtree: []},
                    {text: "fff", subtree: []}
                ]

            })
        </script>
    </head>
    <body ms-controller="tree">
        <script type="avalon" id="tmpl">
            <ul ms-each-el="el.subtree">
            <li>{{el.text}}<div ms-include="'tmpl'" ms-visible="el.subtree.length" ></div></li>
            </ul>
        </script>
        <ul ms-each-el="tree">
            <li>{{el.text}}<div ms-include="'tmpl'" ms-visible="el.subtree.length" ></div></li>
        </ul>
    </body>
</html>

关键是如何递归调用自己

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.