avalon通過半年的宣傳,已經有很多公司在使用avalon應用於它們內外網應用或移動項目,比較大牌的客戶有百度,搜狐,金山,邊緣,去哪兒……最近成爲去哪兒的前端架構師後,掌握更多資源,能夠隨使抓我的幫忙寫文檔作測試寫UI,以前的種種誥病都會迅速被解決掉的。所以你們不須要擔憂什麼,放心試用avalon吧!javascript
說說去哪兒的狀況吧,如今我所在的酒店部門有一個40多號人的前端組,而且不斷壯大。很早以前,他們就用個人avalon重構他們的組件庫OnionUI 。對於一個公司來講,組件庫是一個重要的財富,能讓咱們更快地進行開發迭代。至於通常的業務開發,藉憑avalon操做數據即操做DOM的機制,也是不費吹灰之力就能搞定。大頭的仍是組件,像阿狸的Kissy,成熟無比,組件應有盡用,這是咱們奮鬥的目標。由於去哪兒前端團隊很早就使用avalon的UI綁定了。html
avalon的UI綁定的語法以下:前端
ms-widget="uiName, id?, optsName? "
<div ms-controller="xxx"> <div ms-controller="yyy"> <div ms-widget="dialog,$,$opt"> </div> </div> </div> <script> avalon.define("xxx", function(vm){ vm.uuu = "ssdf" }) avalon.define("yyy", function(vm){ vm.dfd = "sdfdf" vm.$opt = {//這個對象是做爲dialog的配置對象而存在 width: 400, height: 200, toggle: false } }) </script>
編寫一個組件,咱們很是注重它的可配置性。avalon的UI綁定擁有三處用於定義配置的地方。第一處,就是上面提到的,在一個已存的VM中定義一個對象(最好將它定義不可監聽的,以$開頭或放在$skipArray數組中)。 它至關於一個父類。讓一組UI共享相同的配置。第二處是位於UI綁定的構造器中,咱們能夠經過avalon.ui[widgetName].defaults訪問到。它是讓同一種組件的全部實例都共享相同的配置。第三處是在定義ms-widget所在的元素上,添加一些HTML5 data-*屬性,格式爲data- widgetName - optionName,好比你想爲suggest組件定義一個叫toggle的配置項,那麼就應該寫做data-suggest-toggle,若是是一個叫currentValue,那麼要將它改爲"連字符風格",即將大寫變小寫前面再加一橫槓,data-suggest-current-value。它們是用來制定當前UI實例的。java
<input ms-controller="bbb" ms-widget="datepicker" data-datepicker-date-format="yyyy-MM-dd">
好了,咱們正式介紹如何編寫組件自己。咱們要記住一點,avalon全部操做都與掃描機制息息相關,就像jQuery喜歡把它的API選擇器引擎綁架在一塊兒。爲何這樣說呢,由於視圖與代碼分定義在不一樣的地方,只有通過掃描後,視圖中的綁定纔會挾持它們所在的元素節點與VM關聯在一塊兒。框架會默認在domReady以後掃描一次。若是這時咱們用到的組件所對應的JS文件尚未加載好,那麼當加載好後咱們須要本身手動掃描。git
require("avalon.dialog", function() { avalon.define("test", function(vm) { vm.$skipArray = ["dialog"] vm.dialog = { buttons: [{text: "ok"}, {text: "cancel"}] } }) avalon.scan() })
組件大抵都是如下樣子,留意一下里面的註釋:github
//avalon 1.2.5 2014.4.2 define(["avalon", "text!avalon.tabs.tab.html", //這是組件用到的VM "text!avalon.tabs.panel.html", "text!avalon.tabs.close.html"], function(avalon, tabHTML, panelHTML, closeHTML) { var widget = avalon.ui.tabs = function(element, data, vmodels) { var options = data.tabsOptions//★★★取得配置項 var vmodel = avalon.define(data.tabsId, function(vm) { avalon.mix(vm, options)//這視狀況使用淺拷貝或深拷貝avalon.mix(true, vm, options) vm.$init = function() {//初始化組件的界面,最好定義此方法,讓框架對它進行自動化配置 avalon(element).addClass("ui-tabs ui-widget ui-widget-content ui-corner-all") // ★★★設置動態模板,注意模塊上全部佔位符都以「MS_OPTION_XXX」形式實現 var tablist = tabHTML .replace("MS_OPTION_EVENT", vmodel.event) .replace("MS_OPTION_REMOVABLE", vmodel.removable ? closeHTML : "") //決定是重複利用已有的元素,仍是經過ms-include-src引入新內部 var contentType = options.contentType === "content" ? 0 : 1 var panels = panelHTML.split("MS_OPTION_CONTENT")[contentType] element.innerHTML = vmodel.bottom ? panels + tablist : tablist + panels element.setAttribute("ms-class-1", "ui-tabs-collapsible:collapsible") element.setAttribute("ms-class-2", "tabs-bottom:bottom") avalon.scan(element, [vmodel].concat(vmodels)) } vm.$remove = function() {//清空構成UI的全部節點,最好定義此方法,讓框架對它進行自動化銷燬 element.innerHTML = "" } //其餘屬性與方法 vm.tabs = [] vm.tabpanels = [] vm.disable = function(index, disable) { //具體實現 } vm.enable = function(index) { //具體實現 } vm.add = function(config) { //具體實現 } vm.remove = function(config) { //具體實現 } vm.activate = function(event, index) { //具體實現 } }) return vmodel//必須返回組件VM } widget.defaults = {//默認配置項 collapsed: false, active: 0, //默認打開第幾個面板 event: "click", //切換面板的事件,移過(mouseenter)仍是點擊(click) collapsible: false, //當切換面板的事件爲click時, bottom: false, //按鈕位於上方仍是上方 removable: false, //按鈕的左上角是否出現X,用於移除按鈕與對應面板 activate: avalon.noop, // 切換面板後觸發的回調 contentType: "content" } return avalon })
這裏須要着重留意的是data裏面有兩個屬性,一個叫"組件名+Options",是一個對象,若是它裏面有widget+"Id"這個屬性,那麼新生成的VM就是用它做爲它的$id。 一個叫"組件名+Id",就是新生成的VM的$id。組件必須註冊到avalon.ui上,它的構造器必須定義一個叫defaults的默認配置項。數組
另外,因爲掃描從外到裏,當它掃描了ms-widget所在的元素,若是此元素裏面還有子元素, 而且它們的綁定屬性須要用到新VM的某一些字段,這時讓它繼續掃下去就有出錯的危險。咱們能夠先它的全部子元素放到一個文檔碎片中。待到新VM中出來後,再插回原地置,而後手動掃描。架構
在avalon1.2.4中,ms-widget所在的元素還添加了一個msData對象,保存着全部ms-*屬性,此外data屬性還添加了ms-widget-id屬性,保存着新生成的組件VM的ID。咱們能夠靠它們作更多的事。app
在avalon1.2.5中,要求組件做者最好爲VM添加$init, $remove方法,方便自動化初始化組件與對它進行銷燬。銷燬的條件是定義ms-widget的那個元素被移出DOM。有時咱們不得不對此元素進行移動(移動時也會發生移出DOM樹的操做),這時就須要注意啦,爲元素添加一個msRetain,值爲true,爲能夠避銷自動化銷燬。當它再插回DOM樹,記得將此屬性去掉。框架
下面是ms-widget的部分源碼
data[widget + "Id"] = args[1] data[widget + "Options"] = avalon.mix({}, constructor.defaults, vmOptions, widgetData) element.removeAttribute("ms-widget") var vmodel = constructor(element, data, vmodels)//防止組件不返回VM data.evaluator = noop element.msData["ms-widget-id"] = vmodel.$id if (vmodel.hasOwnProperty("$init")) {//初始化組件 vmodel.$init() } if (vmodel.hasOwnProperty("$remove")) { var offTree = function() {//CG回收 vmodel.$remove() element.msData = {} delete VMODELS[vmodel.$id] } if (supportMutationEvents) { element.addEventListener("DOMNodeRemoved", function(e) { if (e.target === this && !this.msRetain) { offTree() } }) } else { element.offTree = offTree launchImpl(element)//若是不支持DOMNodeRemoved事件,使用一個全局的定時器進行輪詢 } }
下面是avalon.dialog的部分源碼
vm.$init = function() { //CSS自適應容器的大小 if (options.height === "auto") { var style = element.style style.width = style.height = "auto" style.minHeight = element.clientHeight + "px" } element.msRetain = true //▲▲▲▲防止被銷燬 vmodel.parent = vmodel.parent === "parent" ? element.parentNode : document.body element.removeAttribute("title") avalon(element).addClass("ui-dialog-content ui-widget-content") dialog.appendChild(element) avalon.ready(function() { vmodel.parent.appendChild(dialog) element.msRetain = false//▲▲▲▲▲ vmodel.fullScreen = /body|html/i.test(dialog.offsetParent.tagName) if (vmodel.fullScreen) { dialog.setAttribute("data-drag-containment", "window") } //。。。。。。。略 }) }
爲了方便你們編寫組件,avalon還暴露了 getWidgetData, parseExprProx等接口,讓你們自行解析綁定屬性。
你們能夠參考下面官方組件編寫本身的UI組件。