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组件。