avalonJS是司徒正美開發和維護的前端mvvm框架,能夠輕鬆實現數據的隔離和雙向綁定,相比angularJS等前端框架它有以下優點:javascript
1.壓縮後僅有60多kb,而angular的min版是100多kb;css
2.兼容IE6+,符合天朝市場需求;html
3.效率更高,跑起來比angular和knockout都要更快,在移動端上該優點會更大(avalon有移動端專版的avalon.modern.js)。關於其性能更詳細的介紹能夠看這裏;前端
4.涵蓋了angular的大部分功能,且實現方式更爲便捷、上手更容易;java
5.有配套的UI庫(固然這個按需選擇便可),由司徒正美及其「去哪兒」團隊維護,有相關的中文文檔(下方會提到),除了在github提交issue,你也能夠加入正美的Q羣79641290 來交流問題或提交bug。node
(這位兄臺,谷歌送溫暖,開門查水錶)jquery
然而avalon也有本身的劣勢——知名度較低。不過畢竟國產的東西沒經BAT推廣,要像seaJS那樣馳名中外卻是不容易。git
相關中文文檔github
正美其實私下寫了很多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對象屬性保存起來。
本章開始搭配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的前端路由。
本章將介紹如何使用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。共勉~