avalonJS入门
前端神器avalonJS入门(一)
avalonJS是司徒正美开发和维护的前端mvvm框架,可以轻松实现数据的隔离和双向绑定,相比angularJS等前端框架它有如下优势:
1.压缩后仅有60多kb,而angular的min版是100多kb;
2.兼容IE6+,符合天朝市场需求;
3.效率更高,跑起来比angular和knockout都要更快,在移动端上该优势会更大(avalon有移动端专版的avalon.modern.js)。关于其性能更详细的介绍可以看这里;
4.涵盖了angular的大部分功能,且实现方式更为便捷、上手更容易;
5.有配套的UI库(当然这个按需选择即可),由司徒正美及其“去哪儿”团队维护,有相关的中文文档(下方会提到),除了在github提交issue,你也可以加入正美的Q群79641290 来交流问题或提交bug。
(这位兄台,谷歌送温暖,开门查水表)
然而avalon也有自己的劣势——知名度较低。不过毕竟国产的东西没经BAT推广,要像seaJS那样驰名中外倒是不容易。
相关中文文档
正美其实私下写了不少avalon的官方api和教程,大家可以访问如下地址:
GitHub(下载最新的avalon以及实例(examples文件夹里),通过实例来掌握某些功能的实现是很好的学习途径)
Avalon快速入门(比较快捷的入门课程,只用了几篇文章来介绍了最常用的一些功能)
API文章(正美的博文,篇幅较大,涵盖知识点很多,可以当作API来查阅,只是正美的博客排版真的。。。看起来略吃力),也可以在这里查看更规范的API。
Avalon乱炖(强烈推荐,用了20多篇文章较详细地、渐进地介绍avalon,必读的就是啦)
Avalon入门视频(推荐)
本系列初衷
虽然正美已经细心编写了不少中文文档,不过有的文章技术门槛有点高,不太适合初学者,另外作为avalon的用户,以用户的角度来较详细地介绍avalon或许会更合适些。
本系列相比正美的教程,会更侧重于“怎么用”,而非其机制或原理的介绍。
另外也希望本系列能为推广avalon出一份绵薄之力,希望能让更多的前端爱好者开始接触avalon,并喜欢上这个前端利器。
本系列技术需求
本系列除了avalonJS之外,还会搭配requireJS做辅助,特别是后面我们会使用avalon的路由系统,来做一个单页面站点(放到移动端就是SPA了),需要requireJS及其插件来按需加载脚本和样式文件,故建议查阅本系列的朋友要多多少少会一点requireJS的知识。
个人还是觉得avalon搭配requireJS的话,在前端可谓hold住全场了(咱忽略node及其框架...)~
开始
我们可以在这里获取最新版本的avalonJS,然后将其引入页面中(本章先不考虑搭配requireJS,仅仅先玩一玩、介绍下):
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>初玩阿瓦隆</title>
<script type="text/javascript" src="avalon.js"></script>
</head>
<body>
<div></div>
</body>
</html>
接着,类似于ng的“ng-controller”,avalon的控制域属性名叫做“ms-controller”,你可以把它当作一个监听器,把它绑定到一个容器后,avalon就能扫描和监听这个容器内的所有(绑定了avalon方法或带有插值表达式的)元素了。
我们来给div加上这个监听器,并在里面写一个avalon插值表达式{{XXX}}:
<div ms-controller="wrap">{{a}}</div>
你现在运行它的话,还没有任何效果,因为我们还没有写脚本来让avalon工作起来。我们可以来这么一段简单的脚本:
<body>
<div ms-controller="wrap">{{a}}</div>
<script type="text/javascript">
var abc = avalon.define({ //abc是随便起的一个名字,用作该Model的载体
$id: "wrap", //告诉avalon这个Model是作用于哪个ms-controller的
a: "你好啊" //定义一个avalon对象属性“a”,其值是“你好啊”
});
avalon.scan(); //这句话可以不加,因为avalon有自己的DOMReady模块,会自动扫描全文。
</script>
</body>
在avalon中,我们用 avalon.define({ ... }) 的形式来定义一个Model实例(其参数可以看做一个avalon数据对象),其中的 $id 是内置属性,对应所要扫描和监控的控制域名。
我们还在内部定义了一个属性"a",故在对应的控制域(如这段代码对应的域是绑定ms-controller="wrap"的div标签)里,我们使用avalon插值表达式{{a}}的话,可以自动绑定其值“你好啊”。
注:最后一句 avalon.scan(); 可以不加上,不过后续我们会使用requireJS来配合使用avalon,届时会建议删掉avalon的DOMReady模块,故先养成加上scan的习惯。
上述代码运行效果如下:
当然,avalon有着更类似ng的写法:
avalon.define("wrap", function(vm) {
......
})
但个人觉得没必要,还是下方的写法来的简单(本系列后续的实例也将遵从该写法):
var vm = avalon.define({
$id:wrap,
......
})
下面这段代码可以帮你更好了解avalon的控制域:
<body>
<div ms-controller="wrap">{{a}}</div>
<div ms-controller="wrap2">
{{a}}
<span>{{b}}</span>
</div>
<script type="text/javascript">
var abc = avalon.define({
$id: "wrap",
a: "你好啊"
});
var def = avalon.define({
$id: "wrap2",
a: "大家好",
b: "哈哈哈"
});
avalon.scan();
</script>
</body>
执行效果如下:
两个作用域(ms-controller)之间可以互相访问彼此的数据,还记得我们给avalon.define的前面定义了一个载体么(var XX = avalon.define),利用它就能轻松获取:
<body>
<div ms-controller="wrap">{{a}}</div>
<div ms-controller="wrap2">
{{a}}
<span>{{b}}</span>
</div>
<script type="text/javascript">
var abc = avalon.define({
$id: "wrap",
a: "你好啊"
});
var def = avalon.define({
$id: "wrap2",
a: "大家好",
b: abc.a //获取第一个Model里的属性值
});
avalon.scan();
</script>
</body>
执行效果:
数据和视图同步
上方我们实现了非常简单的数据绑定,将一个avalon对象属性a绑定到DOM元素上。不过avalon更有趣和实用的地方是它实现了数据与视图的同步,说的简单点,我们用脚本修改了a的值,那么DOM上绑定的数据也会跟着改变(当然反过来也是一样的):
<body>
<div ms-controller="wrap">
<span>{{a}}</span>
<input ms-duplex="a" />
</div>
<script type="text/javascript">
var abc = avalon.define({
$id: "wrap",
a: "你好啊"
});
avalon.scan();
</script>
</body>
注意我们这里增加了一个 <input ms-duplex="a" /> ,其中的 ms-duplex 是avalon的双工绑定属性,它除了负责将VM中对应的值(如本例是a)放到表单元素的value中,还对元素偷偷绑定一些事件,用于监听用户的输入从而自动刷新VM。
执行如下:
实例
利用avalon数据-视图同步的特性,我们可以更便捷地、更少代码地实现某些功能。举个例子,我们来实现一个选项卡的功能:
如上图的选项卡你会如何实现呢?可能你会写两个ul来对应下方两个选项卡列表,每个ul里都写上4个li(或者让后端人员通过后端框架来写loop,从而动态生成li),然后你再把第二个ul隐藏了,接着写个方法,让鼠标移到第二个选项卡标题时,第一个ul隐藏,第二个ul显示,对吧。
还有右上角的 “更多XX” 的连接,也可以通过隐藏-显示的方式来实现
你的DOM代码可能是这样的:
<div>
<span id="gg">公告</span><span id="bd">媒体报道</span>
<a id="more_gg" href="#!/gg">更多公告</a><a id="more_bd" href="#!/bd">更多报道</a>
<ul id="gg_list">
<li><a href="#!/gg/1" title="公告文章标题1">公告文章标题1</a></li>
<li><a href="#!/gg/2" title="公告文章标题2">公告文章标题2</a></li>
<li><a href="#!/gg/3" title="公告文章标题3">公告文章标题3</a></li>
<li><a href="#!/gg/4" title="公告文章标题4">公告文章标题4</a></li>
</ul>
<ul id="bd_list">
<li><a href="#!/bd/1" title="媒体报道文章标题1">媒体报道文章标题1</a></li>
<li><a href="#!/bd/2" title="媒体报道文章标题2">媒体报道文章标题2</a></li>
<li><a href="#!/bd/3" title="媒体报道文章标题3">媒体报道文章标题3</a></li>
<li><a href="#!/bd/4" title="媒体报道文章标题4">媒体报道文章标题4</a></li>
</ul>
</div>
但使用avalon的话,一切都更简单:
<div ms-controller="list">
<span ms-mouseover="changeUl(gg)">公告</span>
<span ms-mouseover="changeUL(bd)">媒体报道</span>
<a ms-href="'#!/'+ more_name">{{more_text}}</a>
<ul>
<li ms-repeat="infoList">
<a ms-href="'#!/'+ more_name + '/' + el.id" ms-title="el.title">{{el.title}}</a>
</li>
</ul>
</div>
首先它只有一个“更多XX”的<a>标签,而且只有一个<ul>,而且不存在任何后端标签的介入就能实现数据循环绑定。
它的优势在数据越多的时候会越明显(例如每个ul要显示100条li)。
我们来看看avalon的脚本应当怎么写:
<body>
<script type="text/javascript">
var gg=[{"id":"1","title":"公告文章标题1"},{"id":"2","title":"公告文章标题2"},{"id":"3","title":"公告文章标题3"},{"id":"4","title":"公告文章标题4"}];
var bd=[{"id":"1","title":"媒体报道文章标题1"},{"id":"2","title":"媒体报道文章标题2"},{"id":"3","title":"媒体报道文章标题3"},{"id":"4","title":"媒体报道文章标题4"}];
</script>
<div ms-controller="list">
<span ms-mouseover="changeUl(1)">公告</span>
<span ms-mouseover="changeUl(0)">媒体报道</span>
<a ms-href="'#!/'+ more_name">{{more_text}}</a>
<ul>
<li ms-repeat="infoList">
<a ms-href="'#!/'+ more_name + '/' + el.id" ms-title="el.title">{{el.title}}</a>
</li>
</ul>
</div>
<script type="text/javascript">
var vm = avalon.define({
$id: "list",
more_name: "gg",
more_text: "更多公告",
gg:gg,
bd:bd,
infoList:gg,
changeUl:function(flag){
if(flag){ //鼠标移过“公告”选项卡头部
vm.more_name = "gg";
vm.more_text = "更多公告";
vm.infoList = vm.gg;
}else{ //鼠标移过“媒体报道”选项卡头部
vm.more_name = "bd";
vm.more_text = "更多报道";
vm.infoList = vm.bd;
}
}
});
avalon.scan();
</script>
</body>
执行效果:
我们来逐步分析下上方的代码。首先看第一段脚本:
<script type="text/javascript">
var gg=[{"id":"1","title":"公告文章标题1"},{"id":"2","title":"公告文章标题2"},{"id":"3","title":"公告文章标题3"},{"id":"4","title":"公告文章标题4"}];
var bd=[{"id":"1","title":"媒体报道文章标题1"},{"id":"2","title":"媒体报道文章标题2"},{"id":"3","title":"媒体报道文章标题3"},{"id":"4","title":"媒体报道文章标题4"}];
</script>
这里的 gg 表示“公告”的列表JSON数据,bd 则是“媒体报道”的列表JSON数据,你可以让后端的朋友直接在此处提供JSON数据过来。我们后续会利用avalon把这些数据绑定到页面视图上。
我们再看DOM结构:
<div ms-controller="list">
<span ms-mouseover="changeUl(1)">公告</span>
<span ms-mouseover="changeUl(0)">媒体报道</span>
<a ms-href="'#!/'+ more_name">{{more_text}}</a>
<ul>
<li ms-repeat="infoList">
<a ms-href="'#!/'+ more_name + '/' + el.id" ms-title="el.title">{{el.title}}</a>
</li>
</ul>
</div>
<span>中的 ms-mouseover 是avalon的“onmouseover”方法,其值 changeUl(X) 是我们在最后的avalon脚本中定义的一个事件方法,然后比如当鼠标移到“媒体报道”的span上,会触发绑定是 changeUl(0) 事件。
我们再看看 <a ms-href="'#!/'+ more_name">{{more_text}}</a> 这里的 ms-href 自然也是avalon中的“href”属性,可以植入avalon对象属性(如这里的more_name),也可以加上字符串(如这里的'#!/'),但要用引号括起来,不然会被当作avalon对象属性处理。
接着是最重要的部分:
<li ms-repeat="infoList">
<a ms-href="'#!/'+ more_name + '/' + el.id" ms-title="el.title">{{el.title}}</a>
</li>
我们使用了 ms-repeat="XX" 属性来绑定要重复显示的哈希数据,同时会生成一个代理VM对象,该代理对象拥有el,$index, $first, $last, $remove 等属性(点这里查看详细),其中我们用到的 el 表示指向当前的数据元素,从而可以通过 el.id ,el.title 来获取infoList数组对象的具体元素。
最后咱再看看avalon脚本:
<script type="text/javascript">
var vm = avalon.define({
$id: "list",
more_name: "gg",
more_text: "更多公告",
gg:gg, //获取公告JSON数据
bd:bd, //获取媒体报道JSON数据
infoList:gg, //infoList缺省值为公告JSON数据
changeUl:function(flag){
if(flag){ //鼠标移过“公告”选项卡头部
vm.more_name = "gg";
vm.more_text = "更多公告";
vm.infoList = vm.gg; //infoList变为公告JSON数据
}else{ //鼠标移过“媒体报道”选项卡头部
vm.more_name = "bd";
vm.more_text = "更多报道";
vm.infoList = vm.bd; //infoList变为媒体报道JSON数据
}
}
});
avalon.scan();
</script>
这里要注意的是我们用了
gg:gg, //获取公告JSON数据
bd:bd, //获取媒体报道JSON数据
来获取和存储“公告/媒体报道”的JSON数据到avalon对象的属性中(左侧的gg和bd是avalon对象属性,右侧的gg和bd是全局变量),这样做的原因是后续的回调事件changeUl(flag)要通过参数来判断和修改vm.infoList的值,而其值应同为avalon对象属性。如果把代码改为这样会出错(刚用avalon的朋友可能就会这样写):
<script type="text/javascript">
var vm = avalon.define({
$id: "list",
more_name: "gg",
more_text: "更多公告",
infoList:gg, //infoList缺省值为公告JSON数据
changeUl:function(flag){
if(flag){ //鼠标移过“公告”选项卡头部
vm.more_name = "gg";
vm.more_text = "更多公告";
vm.infoList = gg;
}else{ //鼠标移过“媒体报道”选项卡头部
vm.more_name = "bd";
vm.more_text = "更多报道";
vm.infoList = bd;
}
}
});
avalon.scan();
</script>
执行效果如下:
是的,鼠标第一次移上去的时候是无误的,但再移到其它选项卡的时候就不按常理出牌了,这是为什么?
这是因为当我们定义 “vm.infoList=gg” 时,vm.infoList 自然指向了 gg(注意gg是一个对象类型),若vm.infoList的值改变了,那么其指向的 gg 对象的值自然也会跟着改变。你可以这样来调试:
var vm = avalon.define({
$id: "list",
more_name: "gg",
more_text: "更多公告",
infoList:gg,
changeUl:function(flag){
console.table(gg); //console出“公告”变量的数据信息
if(flag){
vm.more_name = "gg";
vm.more_text = "更多公告";
vm.infoList = gg;
}else{
vm.more_name = "bd";
vm.more_text = "更多报道";
vm.infoList = bd;
}
}
});
avalon.scan();
结果(第一次回调事件的gg是正常的,但第二次开始就变成了媒体报道的数据):
因此我们要记得,若存在外部引入的数据,应用一个avalon对象属性保存起来。
前端神器avalonJS入门(二)
本章开始搭配requireJS来使用avalon,开始之前,我们可以对avalon进行精简改造(注:新版的avalon已提供了shim版本,无需再做如下的精简了,直接点这里获取)。
avalon源码里有自己的AMD加载器和DOMReady模块,使用requireJS来加载各依赖脚本的话,使用其配套插件DOMReady来ready执行会更合适,而且avalon的AMD模块也多余了。故我们可以把这俩块代码咔嚓掉。
只需要在源码里搜索“AMD”和“DOMReady”就能找到位于尾部的这俩模块,删掉它们可以节省400多行代码(大约是第4358行到4777行):
要注意的是avalon的DOMReady模块里带了自扫描机制,它能在DOMReady后自动执行avalon.scan()来扫描和执行文档中的avalon功能属性:
所以我们删掉源码中DOMReady模块的话,之后编写avalon脚本应当在最后手动加上一句 avalon.scan() 或 avalon.scan(document.getElementById("XX")) ,前者会扫描整个文档,后者只会扫描所选DOM内区域,我个人是推荐使用后者。
其实在第一篇的时候就有提过这个问题了,只是没细说:
删掉avalon自带的AMD和DOMReady模块后,还有一个小地方也建议修改下(非必须项),它是avalon内置的调试方法,为不干扰后续自己的脚本调试,可以把它注释掉。
方法是直接在源码搜索“console”,大约在46行的位置注释掉log()方法里的调试代码即可:
需求
本章要用到的脚本除了精简后的avalonJS,还需要requireJS及其插件DomReady.js(后续篇章还会用到css.js来按需加载样式文件)。
你可以在我的Github上直接下载本章的Demo,也省去了搜罗上述脚本的过程。
示例
我们依旧拿上一篇的选项卡来做本章示例即可,本章主要介绍的仅为如何在使用requireJS的基础上使用avalon:
我们本章示例的文件系统可以是这样的:
其中的js/page文件夹下的 index.js 作为 index.html 页面的脚本入口(requireJS的main入口)。js/tool文件夹下则存放要用到的工具。这样还蛮简洁直观的对吧。
在 index.html 页面里,我们仅需引入配置了入口的requireJS文件即可:
<script src="js/tool/require.js" type="text/javascript" data-main="js/page/index"></script>
然后加入我们上一篇avalon文章中写好的内容,整体是这样的:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>初玩阿瓦隆</title>
<script src="js/tool/require.js" type="text/javascript" data-main="js/page/index"></script>
</head>
<body>
<script type="text/javascript">
//这里给后端提供数据接口
var conf = {
gg:[{"id":"1","title":"公告文章标题1"},{"id":"2","title":"公告文章标题2"},{"id":"3","title":"公告文章标题3"},{"id":"4","title":"公告文章标题4"}],
bd:[{"id":"1","title":"媒体报道文章标题1"},{"id":"2","title":"媒体报道文章标题2"},{"id":"3","title":"媒体报道文章标题3"},{"id":"4","title":"媒体报道文章标题4"}]
};
</script>
<div ms-controller="list">
<span ms-mouseover="changeUl(1)">公告</span>
<span ms-mouseover="changeUl(0)">媒体报道</span>
<a ms-href="'#!/'+ more_name">{{more_text}}</a>
<ul>
<li ms-repeat="infoList">
<a ms-href="'#!/'+ more_name + '/' + el.id" ms-title="el.title">{{el.title}}</a>
</li>
</ul>
</div>
</body>
</html>
主页面写好了,我们再看看requireJS的入口脚本 js/page/index.js 怎么写。
首先是配置我们要调用到的文件(比如avalonJS和domReadyJS):
require.config({
baseUrl: 'js/', //相对于index.html页面文件的地址
paths:{ //这里配置的地址,都是相对于上方的baseUrl的
avalon: 'tool/avalon',
domReady:'tool/domReady'
},
shim:{
avalon: { exports: "avalon" }
}
});
其中的baseUrl是当前config的全局路径,后面paths里定义的路径都是相对于baseUrl的,baseUrl的好处就是如果路径很长的话,可以拿它当前缀变量。
例如上述代码paths里定义avalon的地址是 "tool/avalon",实际运行的时候会给它加上前缀baseUrl(当然也会加上省略的后缀名.js)变成 "js/tool/avalon.js"。
shim指的是给非严格AMD标准的文件加上一层“壳”,增强代码健壮性。
接着就是我们对avalon脚本的常规调用了:
require(['avalon',"domReady!"], function() {
var vm = avalon.define({
$id: "list",
more_name: "gg",
more_text: "更多公告",
gg:conf.gg,
bd:conf.bd,
infoList:conf.gg,
changeUl:function(flag){
if(flag){
vm.more_name = "gg";
vm.more_text = "更多公告";
vm.infoList = vm.gg;
}else{
vm.more_name = "bd";
vm.more_text = "更多报道";
vm.infoList = vm.bd;
}
}
});
avalon.scan();
});
注意要用
require(['依赖文件1',"依赖文件2"], function() {
//回调
})
来处理模块依赖,然后注意domReadyJS的模块引用写法是“domReady!”,即在最后加了一个感叹号,表示优先使用其作为DOMReady处理模块。
整理一下,我们的 js/page/index.js 文件最终是这样的:
运行index.html,会发现一切顺利:
本章就先简单介绍在requireJS下如何使用avalon,下一篇avalon文章打算给大家介绍非常了得的东西——avalon的前端路由。
前端神器avalonJS入门(三)
本章将介绍如何使用avalon来实现前端路由功能。
我们需要用到两个avalon路由配套模块—— mmHistory.js 和 mmRouter.js 。其中mmHistory是用于历史管理,它会劫持页面上所有点击链接的行为,当这些链接是以 #/ 、#!/ 开头,就尝试匹配路由规则,阻止页面刷新(通过hash方式或HTML5的replaceState方式)。mmRouter是给我们定义路由规则,路由规则可以更精细地指定每个参数(param)的匹配规则,如果符合就执行对应的回调,如果不符合,就进入error回调。
关于该路由系统更具体的描述,可以查阅这里。
作为示例,我们打算制作一个网站的 “用户中心” 页面,其中左侧为导航列表,右侧为受左侧列表控制的内容显示区域:
该“用户中心”页面有这么几个要求:
⑴ 页面不跳转,仅做局部(即内容区域部分)刷新;
⑵ 可以通过不同的url进入对应的页面(即内容区域显示对应的内容);
⑶ 浏览器能记住url状态,比如从“账户详情”点入“我要充值”页面,然后再点击浏览器返回按钮,可以正确回到“账户详情”页面。
由于不是石器时代,自然不会再选择iframe这种内耗高、不友好的元素来架构页面(而且iframe也实现不了后面两个需求呀)。那么我们会很快联想到Ajax技术,这个想法很本质,不过单纯的Ajax也没办法达到我们的要求,所以才需要引入开头提到的两个avalon路由模块。
我们可以先写出简单的页面原型:
index.html:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>账户中心</title>
<link rel="stylesheet" href="css/user.css">
<script src="js/lib/require.js" type="text/javascript" data-main="js/page/user"></script>
</head>
<body ms-controller="user" class="ms-controller">
<script type="text/javascript">
//这里给后端提供数据接口
var conf = {
username: {"id": "11", "name": "VaJoy"}
}
</script>
<header>
<span>{{username.name}}你好,欢迎来到账户中心</span>
</header>
<nav>
<ul>
<li><a href="#!/index">我的首页</a></li>
<li><a href="#!/detail">账户详情</a></li>
<li><a href="#!/recharge">我要充值</a></li>
</ul>
</nav>
<article>
内容...
</article>
</body>
</html>
user.js:
require.config({
baseUrl: 'js/lib/',
paths:{
avalon: 'avalon',
domReady:'domReady',
mmHistory: 'mmHistory',
mmRouter: 'mmRouter',
jquery: 'jq'
},
shim:{
avalon: { exports: "avalon" },
mmHistory:{ deps: ['avalon']},
mmRouter:{ deps: ['avalon']}
}
}); require(['avalon',"domReady!"], function() {
var vm = avalon.define({
$id: "user",
username:conf.username
});
avalon.scan();
});
user.css:
body,html{padding: 0;margin:0;background: #EEE;}
.ms-controller{visibility: hidden;}
header{height: 50px;background: white;}
header>span{display:block;padding: 16px;}
nav{position: absolute;left:0;margin-top:50px;width: 200px;}
nav>ul>li{margin-top: 12px;}
nav>ul>li>a{text-decoration: none;color:blue;}
nav>ul>li>a:hover{color:red;}
article{padding: 15px;margin-left:200px;min-height: 600px;background: white;}
运行结果如下:
接着我们要新建三个页面——mine.html、detail.html 和 recharge.html ,分别对应“我的首页”、“账户详情” 和 “我要充值” 的右侧内容,咱在里面随便写点内容意思意思即可,比如mine.html我就写了一句话:
接着我们默认先把mine.html引入到index.html中,这里我们借助avalon的 ms-include-src 接口,修改下index.html:
<nav>
<ul>
<li><a href="#!/index">我的首页</a></li>
<li><a href="#!/detail">账户详情</a></li>
<li><a href="#!/recharge">我要充值</a></li>
</ul>
</nav>
<article ms-include-src="pageUrl"> <!--这里使用ms-include-src接口,它会引入pageUrl属性所对应的文件-->
</article>
接着修改 user.js的部分:
require(['avalon',"domReady!"], function() {
var vm = avalon.define({
$id: "user",
username:conf.username,
pageUrl:"mine.html" //默认为mine.html
});
avalon.scan();
});
运行如下:
接着是时候让 mmHistory.js 和 mmRouter.js 发挥它们的作用了,我们修改下user.js的部分代码:
require(['mmHistory','mmRouter',"domReady!"], function() {
var vm = avalon.define({
$id: "user",
username:conf.username,
pageUrl:"mine.html" //默认为mine.html
});
function callback() {
if(this.path==="/index"){
vm.pageUrl="mine.html";
}else {
var path_tail = this.path.replace(/\//, "");
vm.pageUrl = path_tail + ".html"; //动态修改pageUrl属性值
}
}
avalon.router.get("/*path", callback); //劫持url hash并触发回调
avalon.history.start(); //历史记录堆栈管理
avalon.scan();
});
注意由于在 require.config 的 shim 中我们已经定义了 mmHistory.js 和 mmRouter.js 是依赖于avalon的,故此处无须再引入avalon模块,requireJS执行该代码段之前会先加载好avalon的。
我们通过这两行代码执行了路由和历史记录的管理:
avalon.router.get("/*path", callback); //劫持url hash并触发回调
avalon.history.start(); //历史记录堆栈管理
其中router.get() 的第一个参数表示路由匹配规则,比如这里的“/*path”表示匹配全部路径,匹配到了就触发回调callback函数。
更多的匹配规则我们可以直接在 mmRouter.js 中查看注释信息:
router.get() 在触发callback前会生成一个this.path属性供callback调用(你也可以给回调函数定义一个参数,其默认值等同与path),其值为当前匹配到的路径,比如当url后缀变成 #!/recharge 的时候,this.path的值为匹配到的"/recharge" 。了解了这个之后,callback 函数也很好理解了:
function callback() {
if(this.path==="/index"){
vm.pageUrl="mine.html"; //如果url后缀变成"#!/index",则pageUrl为“mine.html”
}else {
var path_tail = this.path.replace(/\//, ""); //去掉this.path值的第一个斜杠
vm.pageUrl = path_tail + ".html"; //动态修改pageUrl属性值
}
}
这时候的运行结果如下所示:
自此便实现了我们的需求。但是这样还不够完美——每个页面的样式咋处理呢?
我们可以直接在页面上写<style>标签,或者直接写个<link>引入外部样式文件,但前者不好维护,后者毕竟不是插入到head中的不太规范。那么我们能否也用requireJS模块化动态引入样式文件呢?答案是肯定的,不过得借助于其组件css.js。
以“账户详情”(detail.html)为例,我们创建一个detail.css文件,里面设置 .detail{color:red;}。
先确保require.config中的paths里加上了该组件:
paths:{ //这里配置的地址,都是相对于上方的baseUrl的
avalon: 'avalon',
domReady:'domReady',
mmHistory: 'mmHistory',
mmRouter: 'mmRouter',
css: 'css' //加上css.js
}
然后修改detail.html页面内容:
<section ms-controller="detail" class="detail ms-controller">
哟哟哟,这里是详情页面,{{username.name}}你好
</section>
<script>
require(['avalon','css!../../css/detail.css'], function(){
//下面的其实建议写成一个模块detail.js然后由require引入
avalon.define({
$id: "detail",
username: conf.username
});
avalon.scan();
})
</script>
“css!/XXX.css” 是css.js的写法,注意以"css!"开头即可。
运行结果如下:
以上便是avalon前端路由的简单实现,本章的示例代码可以从这里下载。
后续章节可能会开始写一写avalon的API。共勉~
avalonJS入门的更多相关文章
- avalonJS入门(一)
前端神器avalonJS入门(一) posted @ 2014-10-31 17:44 vajoy 阅读(1665) 评论(32) 编辑 收藏 avalonJS是司徒正美开发和维护的前端mvvm框 ...
- 前端神器avalonJS入门(一)
转自:http://www.cnblogs.com/vajoy/p/4063824.html avalonJS是司徒正美开发和维护的前端mvvm框架,可以轻松实现数据的隔离和双向绑定,相比angula ...
- 迷你MVVM框架 avalonjs 入门教程
新官网 请不要无视这里,这里都是链接,可以点的 OniUI组件库 学习教程 视频教程: 地址1 地址2 关于AvalonJs 开始的例子 扫描 视图模型 数据模型 绑定 作用域绑定(ms-contro ...
- 前端神器avalonJS入门(二)
本章开始搭配requireJS来使用avalon,开始之前,我们可以对avalon进行精简改造(注:新版的avalon已提供了shim版本,无需再做如下的精简了,直接点这里获取). avalon源码里 ...
- 前端神器avalonJS入门(三)
本章将介绍如何使用avalon来实现前端路由功能. 我们需要用到两个avalon路由配套模块—— mmHistory.js 和 mmRouter.js .其中mmHistory是用于历史管理,它会劫持 ...
- MVC、MVP、MVVM、Angular.js、Knockout.js、Backbone.js、React.js、Ember.js、Avalon.js、Vue.js 概念摘录
注:文章内容都是摘录性文字,自己阅读的一些笔记,方便日后查看. MVC MVC(Model-View-Controller),M 是指业务模型,V 是指用户界面,C 则是控制器,使用 MVC 的目的是 ...
- Java 学习文章汇总
目前JAVA可以说是产业界和学术界最热门的语言,许多人都很急切想把JAVA学好. 但学习是需要步骤的,除非像电影中演的那样,能够把需要的专业技巧下载到脑海:主角只花了几秒下载资料,就马上具备飞行员的技 ...
- 迷你MVVM框架 avalonjs1.5 入门教程
avalon经过几年以后,已成为国内一个举足轻重的框架.它提供了多种不同的版本,满足不同人群的需要.比如avalon.js支持IE6等老旧浏览器,让许多靠政府项目或对兼容性要求够高的公司也能享受MVV ...
- 使用MVVM框架(avalonJS)进行快速开发
背景 在运营活动开发中,因为工作的重复性很大,同时往往开发时间短,某些情况下也会非常紧急,导致了活动开发时间被大大压缩,同时有些活动逻辑复杂,数据或者状态变更都需要手动渲染,容易出错,正是因为这些问题 ...
随机推荐
- Codeforces Round #487 (Div. 2)
A. A Blend of Springtime(暴力/模拟) 题目大意 给出$n$个花,每个点都有自己的颜色,问是否存在连续大于等于三个花颜色均不相同 sol 直接模拟判断即可 #include&l ...
- zabbix 3.x 监控日志文件
1.启用zabbix主动模式 在zabbix agent端,修改/etc/zabbix/zabbix_agentd.conf ServerActive=服务端IP Hostname=tspnginx0 ...
- 在SQL Server中批量修改有规律列的定义
)=N'要修改的表名'; --修改所有以sl结尾的列名的小数位数为4位 select syscolumns.name into #t1 from syscolumns,systypes where s ...
- Spring的绿草丛
Spring 轻量级框架,JavaEE的春天,当前主流框架 “站立式”的企业应用开发框架 目标 实现有的技术更加易用,推进编码最佳实践 内容:loC容器,AOP实现,数据访问支持:简化JDBC/ORM ...
- oracle 用户尝试登录失败锁定策略及修改
-- 修改密码的有效期策略, 永不过期SQL> ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED;Profile altered ...
- 【Android】Android Studio真机调试的问题统整
真机调试需要注意以下几个问题 [1]手机的USB调试需开启 [2]手机不能是仅充电模式,需要传输数据模式 [3]有些USB线会偷工减料,请拿一条没问题的线,例如买手机时原厂给的配线 [4]在PC端需要 ...
- 北京Uber优步司机奖励政策(3月19日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- 北京Uber优步司机奖励政策(12月8日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- CodeForces 547D Mike and Fish 思维
题意: 二维平面上给出\(n\)个点,然后对每个点进行染色:红色和蓝色,要求位于同一行或同一列的点中,红色点和蓝色点的个数相差不超过1 分析: 正解是求欧拉路径,在这篇博客中看到一个巧妙的思路: 对于 ...
- 8、Java并发编程:同步容器
Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...