迷你MVVM框架 avalonjs 组件编写指南
avalon经过半年的宣传,已经有不少公司在使用avalon应用于它们内外网应用或移动项目,比较大牌的客户有百度,搜狐,金山,边缘,去哪儿……最近成为去哪儿的前端架构师后,掌握更多资源,可以随使抓个人帮忙写文档做测试写UI,之前的种种诰病都会迅速被解决掉的。因此大家不需要担心什么,放心试用avalon吧!
说说去哪儿的情况吧,现在我所在的酒店部门有一个40多号人的前端组,并且不断壮大。很早之前,他们就用我的avalon重构他们的组件库OnionUI 。对于一个公司来说,组件库是一个重要的财富,能让我们更快地进行开发迭代。至于一般的业务开发,藉凭avalon操作数据即操作DOM的机制,也是不费吹灰之力就能搞定。大头的还是组件,像阿狸的Kissy,成熟无比,组件应有尽用,这是我们奋斗的目标。因为去哪儿前端团队很早就使用avalon的UI绑定了。
avalon的UI绑定的语法如下:
ms-widget="uiName, id?, optsName? "
- uiName,必选,一定要全部字母小写,表示组件的类型
- id 可选 这表示新生成的VM的$id,方便我们从avalon.vmodels[id]中获取它操作它,如果它等于$,那么表示它是随机生成,与不写这个效果一样,框架会在uiName加上时间截,生成随机ID
- optName 可选, 配置对象的名字。这是指框架会找离它最近那个VM作为目标,然后在上面找与它同名的一个对象。如果你没有指定, 那么这个配置对象的名字与组件的名字同名。
<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实例的。
<input ms-controller="bbb" ms-widget="datepicker" data-datepicker-date-format="yyyy-MM-dd">
好了,我们正式介绍如何编写组件本身。我们要记住一点,avalon所有操作都与扫描机制息息相关,就像jQuery喜欢把它的API选择器引擎绑架在一起。为什么这样说呢,因为视图与代码分定义在不同的地方,只有经过扫描后,视图中的绑定才会挟持它们所在的元素节点与VM关联在一起。框架会默认在domReady之后扫描一次。如果这时我们用到的组件所对应的JS文件还没有加载好,那么当加载好后我们需要自己手动扫描。
require("avalon.dialog", function() {
avalon.define("test", function(vm) {
vm.$skipArray = ["dialog"]
vm.dialog = {
buttons: [{text: "ok"}, {text: "cancel"}]
}
})
avalon.scan()
})
组件大抵都是以下样子,留意一下里面的注释:
//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。我们可以靠它们做更多的事。
在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组件。
迷你MVVM框架 avalonjs 组件编写指南的更多相关文章
- 迷你MVVM框架 avalonjs 0.95发布
迷你MVVM框架 avalonjs 0.95发布 本版本最主要的改进是ms-with 深层绑定的实现,至少,avalon1.0所有重要的feature已经开发完毕,之后就是小补小漏,性能优化了. ms ...
- 迷你MVVM框架 avalonjs 0.85发布
迷你MVVM框架 avalonjs 0.85发布 本版本对循环绑定做了巨大改进,感谢@soom, @limodou, @ztz, @Gaubee 提供的大量测试文件. fix scanNodes, 在 ...
- 迷你MVVM框架 avalonjs 0.82发布
迷你MVVM框架 avalonjs 0.82发布 本版本最大的改进是启用全新的parser. parser是用于干什么的?在视图中,我们通过绑定属性实现双向绑定,比如ms-text="fir ...
- 迷你MVVM框架 avalonjs 1.3.7发布
又到每个月的15号了,现在avalon已经固定在每个月的15号发布新版本.这次发布又带来许多新特性,让大家写码更加轻松,借助于"操作数据即操作DOM"的核心理念与双向绑定机制,现在 ...
- 迷你MVVM框架 avalonjs 1.3.3发布
大家可以在仓库中看到,多出了一个叫avalon.observe的东西,它是基于Object.observe,dataset, Promise等新API实现.其中,它也使用全新的静态收集依赖的机制,这个 ...
- 迷你MVVM框架 avalonjs 1.2发布
avalon1.2 带来了许多新特性,让开发更轻松!详见如下: 升级路由系统与分页组件. 对ms-duplex的绑定值进行增强,以前只能prop或prop.prop2,现在可以prop["x ...
- 迷你MVVM框架 avalonjs 1.2.5发布
avalon1.2.5发布,升级ms-widget,整合avalon.require.text到核心,并且修复了avalon.mobile的avalon.innerHTML方法的BUG,让它能执行脚本 ...
- 迷你MVVM框架 avalonjs 1.3.9发布
本次升级,avalon改进了许多内部方法,大大提升性能,并且带来异步刷新视图的新功能. ms-html内部不再使用异步 head元素中的avalon元素加入ms-skip指令 重构计算属性,现在超级轻 ...
- 迷你MVVM框架 avalonjs 1.3.8发布
avalon1.3.8主要是在ms-repeat. ms-each. ms-with等循环绑定上做重大性能优化,其次是对一些绑定了事件的指令添加了roolback,让其CG回收更顺畅. 重构ms-re ...
随机推荐
- Hash学习小结
Hash 简要说明 \(OI\)中一般采用进制\(hash\).模数可以用\(unsigned \ long \ long\)自然溢出,也可以使用大质数.值得一提的是,\(unsigned\ long ...
- spring mvc框架web.xml配置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http:// ...
- Nginx——Nginx概述(一)
1.什么是Nginx? Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行.其特点是占有内存少,并发能力强,事实上n ...
- HDU4864 Task
题意 Today the company has m tasks to complete. The ith task need xi minutes to complete. Meanwhile, t ...
- ffmpeg从USB摄像头采集一张原始图片(转)
本文讲解使用ffmpeg从USB摄像头中采集一帧数据并写入文件保存,测试平台使用全志A20平台,其他平台修改交叉工具链即可移植.开发环境使用eclipse+CDT.交叉工具链使用arm-Linux-g ...
- BZOJ4974:[lydsy1708月赛]字符串大师
浅谈\(KMP\):https://www.cnblogs.com/AKMer/p/10438148.html 题目传送门:https://lydsy.com/JudgeOnline/problem. ...
- Unit01: Web概述 、 HTML概述 、 文本处理 、 图像和超链接 、 表格 、 表单
Unit01: Web概述 . HTML概述 . 文本处理 . 图像和超链接 . 表格 . 表单 demo1.html <!-- 声明网页的版本(文档类型) --> <!doctyp ...
- Linux下的lds链接脚本详解
1. 概论2. 基本概念3. 脚本格式4. 简单例子5. 简单脚本命令6. 对符号的赋值7. SECTIONS命令8. MEMORY命令9. PHDRS命令10. VERSION命令11. 脚本内的表 ...
- apktool重新打包错误
E:\apktool-install-windows-r05-ibot\apktool-install-windows-r05-ibot\.\test_apk_name\res\layout-larg ...
- sprinboot+redis
(1)pom.xml引入jar包,如下: <dependency> <groupId>org.springframework.boot</groupId> < ...