代码拉取完成,页面将自动刷新
同步操作将从 roscoe/avalonjs.github.io 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
<h1 class="page-header">组件</h1>
<h2>介绍</h2>
<p>avalon1.5新增的特性, 旨在简化原ms-widget复杂的组件定义方式,使用更直观的自定义标签来创建组件.</p>
<p>avalon的组件是以UI库为单位设计,换言之,一个组件必须隶属于某一个UI库.默认,avalon已经创建好一个叫ms的UI库.
我们可以通过avalon.library方法来创建自己的组件库
</p>
<xmp class="javascript">
avalon.library("oni", {
$init: function(){},
$childReady: function(){},
$ready: function(){},
$dispose: function(){}
})
</xmp>
<ul>
<li>$init: Function 该组件开始渲染时调用的回调</li>
<li>$childReady: Function 该组件的子组件渲染完毕,冒泡上来的回调</li>
<li>$ready: Function 该组件渲杂完毕时调用的回调,它位于其所有子组件的$ready之后</li>
<li>$dispose: Fuction 该组件被移出DOM树,并且元素不存在msRetain属性,才会调用的回调</li>
</ul>
<p>我们通过avalon.component 来创建一个组件</p>
<xmp class="javascript">
avalon.component("ms:button", {
a: 1,
$replace: 1,
$ready: function () {
console.log("BUTTON构建完成")
},
$template: "<button type='button' >{{a}}<ms:text/></button>"
})
</xmp>
<p>配置对象有如下几个特殊的配置项:</p>
<ul>
<li>$replace: Boolean, 真值时表示替换其容器</li>
<li>$slot: String 默认插入点的名字</li>
<li>$template: String 组件的模板</li>
<li>$extend: String 指定要继承的组件名</li>
<li>$container: DOM 插入元素的位置,比如dialog就不一定在使用它的位置插入,通常放在body中</li>
<li>$construct: Function 用于调整三个配置项的合并,默认是function:(a, b,c ){return avalon.mix(a, b,c)}</li>
<li>$$template Function 用于微调组件的模板,传入$template与当前元素</li>
<li>$init: Function 刚开始渲染时调用的回调, 传入vm与当前元素</li>
<li>$childReady: Function 当其子组件$ready完毕后, 会冒泡到当前组件触发的回调, 传入vm与当前元素</li>
<li>$ready: Function 当组件的所有子组件都调用其$ready回调后才触发的回调, 传入vm与当前元素</li>
<li>$dispose: Function 该组件被移出DOM树,并且元素不存在msRetain属性时的销毁回调, 传入vm与当前元素</li>
</ul>
<p>当vm构建好时, $replace, $slot, $template,$container, $construct会消失</p>
<p>我们可以在</p>
<ul>
<li>$init中添加各种$watch回调, 为IE6-8的VBscript函数的this指向不正确进行bind fix</li>
<li>$ready中重新计算组件的高宽</li>
<li>$dispose中将VM中的元素节点置为null,移除各种dom事件,清空元素内部,方便GC</li>
</ul>
<p>此外,每个组件VM,还添加了一个叫 <strong>$refs</strong> 的非监控对象属性,用于存放子组件的VM。</p>
<h2 id="callbacks">所有回调的执行顺序</h2>
<xmp class="javascript">
组件自身的$$template -->
组件自身的$init --> 库的全局$init -->
组件自身的$childReady --> 库的全局$childReady -->
组件自身的$ready --> 库的全局$ready -->
组件自身的$dispose--> 库的全局$dispose
</xmp>
<h2 id="config">组件的配置</h2>
<xmp class="javascript">
avalon.component("ms:arcus", {
a: 1,
$template: "<button type='button'>{{a}}</button>"
})
</xmp>
<p>这里配置<ms:arcus>这种类型的基本属性与方法,目前,其中a一个属性</p>
<p>然后,我们在页面上调用它时,可以通过<strong>config</strong>属性指定它在VM中的业务数据</p>
<xmp class="html">
<ms:button config="buttonOpts"></ms:button>
</xmp>
<xmp class="javascript">
avalon.define({
$id: "test",
$skipArray:["butttonOpts"],
buttonOpts: {
a: 555
}
})
</xmp>
<p>如果你有多个ms:button组件,每个都有不同的配置对象,你可以使用ms-attr-config来动态生成config的值。</p>
<xmp class="html">
<div ms-repeat=array><ms:button ms-attr-config="opts{{$index}}"></div>
</xmp>
<p>如果你嫌这样麻烦,不想在VM上加这配置属性,还可以使用ms-data-button-a来生成这个值。</p>
<xmp class="html">
<div ms-repeat=array><ms:button ms-data-button-a="$index+ 10"></div>
</xmp>
<p>如果你连这个也不想,那么框架就会在已有的VM链上寻找<b>默认配置对象</b>。比如你的自定义签标的tagName为<ms:dropdown>,
那你在已有VM上定义一个叫ms:dropdown的对象就行了。如果你的自定义标签存在横线,比如ms:color-picker,那么它的默认配置对象
应为ms:colorPicker。
</p>
<p>因此通过,在avalon.component定义的共公配置对象,加上 VM上指定的个性化配置对象,
加上每个元素上使用ms-data指定的dataset配置对象,能让每个button实例都尽然不同!
</p>
<p>此外,我们还可以使用<strong>$id或identifier</strong>属性来指定组件VM的$id,不过当我们用ms-attr-$id动态生成$id可能有问题,
因为$是DOM属性中不是一个合法字符,我们只能用ms-attr-identifier来动态生成它。</p>
<xmp class="html">
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="avalon.js"></script>
</head>
<body>
<script>
var vm = avalon.define({
$id: "test",
array: [1, 2, 3],
$skipArray: ["x0", "x1", "x2"],
x0: {//x0, x1, x2为ms:button的配置对象
a: "aaa",
"ms:text": {//这个为ms:button的ms:text的默认配置对象
b: 44
}
},
x1: {
a: "222",
"ms:text": {
b: 55
}
},
x2: {
a: "333",
"ms:text": {
b: 66
}
}
})
avalon.component("ms:button", {
a: 1,
$replace: 1,
$ready: function (vm, elem) {
console.log("BUTTON构建完成")
},
$template: "<button type='button'><span>|<ms:text></ms:text>|</span>{{a}}<ms:text/></button>"
})
avalon.component("ms:text", {
b: "默认值",
$replace: 1,
$ready: function () {
console.log("TEXT构建完成")
},
$template: "<strong>{{b}}</strong>"
})
</script>
<div ms-controller="test">
<ms:button ms-repeat="array" ms-attr-config="x{{$index}}" ></ms:button>
</div>
</body>
</html>
</xmp>
<p><img src="../../assets/css/component/component01.png" /></p>
<xmp class="html">
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
div {height:50px;width:200px;background:green;margin:5px;}
</style>
<script src="avalon.js"></script>
<script>
avalon.component('ms:abc', {
$template: '<div></div>',
onClick: function () {
},
$ready: function (vm, el) {
console.log(vm.$id)
}
});
var vm = avalon.define({
$id: 'test',
arr: [1, 2, 3]
})
</script>
</head>
<body ms-controller="test">
<header>
<ms:abc ms-repeat="arr" ms-attr-identifier="abc{{$index}}" ></ms:abc>
</header>
</body>
</html>
</xmp>
<p><img src="../../assets/css/component/component03.png" /></p>
<p>注意,$refs只保存其子组件的VM,不保存其子组件的子组件的VM!
并且$refs里面的内容是你自己在$childReady回调添加,框架不会帮你做的.</p>
<xmp class="html">
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="avalon.js"></script>
</head>
<body>
<script>
var vm = avalon.define({
$id: "test"
})
avalon.libraries.ms.$childReady = function (vm, e) {
var child = e.vm
vm.$refs[child.$id] = vm
}
avalon.component("ms:ancestor", {
a: 111,
$ready: function () {
console.log("ancestor构建完成")
},
$template: "<div><ms:parent></ms:parent>{{a}}<ms:parent></ms:parent></div>"
})
avalon.component("ms:parent", {
p: 222,
$ready: function () {
console.log("parent构建完成")
},
$template: "<div><ms:son></ms:son>{{p}}<ms:son></ms:son></div>"
})
avalon.component("ms:son", {
s: 333,
$ready: function () {
console.log("son构建完成")
},
$template: "<strong>{{s}}</strong>"
})
</script>
<div ms-controller="test">
<ms:ancestor $id="pope"/>
</div>
</body>
</html>
</xmp>
<p><img src="../../assets/css/component/component02.png" /></p>
<h2 id="slot">插入点的使用</h2>
<p>我们可以在代表组件的自定义标签的内部,添加一些带slot属性的标签,它们会自动赋给组件VM的同名属性中.</p>
<xmp class="html">
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="avalon.js"></script>
<script>
function heredoc(fn) {
return fn.toString()
.replace(/^[^\/]+\/\*!?\s?/, '')
.replace(/\*\/[^\/]+$/, '')
}
avalon.component("ms:dialog", {
header: "",
footer: "",
content: "",
onClick: function () {
console.log(this)
},
$template: heredoc(function (vm) {
/*
<div class="dialog">
{{header|html}}
{{content|html}}
{{footer|html}}
</div>
*/
})
})
avalon.define({
$id: "test",
a: "这是内容"
})
</script>
</head>
<body ms-controller="test">
<ms:dialog>
<div slot="header">弹出层的头部</div>
<div slot="content">
{{a}}
</div>
<div slot="footer">弹出层的底部</div>
</ms:dialog>
</body>
</html>
</xmp>
<p>这些带solt的元素叫做插入元素,它们在$init回调后进行收集, 收集完毕, 代表组件的自定义标签的innerHTML将会被其$template属性重写,
之后,这些插入元素会根据slot值进行分类,放到一个个文档碎片对象上.
然后这些文档碎片将插入到$template中的<strong ms-skip>{{xxx|html}}</strong>位置上.
</p>
<p><img src="../../assets/css/component/slot01.png" width="50%" height="50%"/></p>
<p><img src="../../assets/css/component/slot02.png" width="50%" height="50%" /></p>
<p><img src="../../assets/css/component/slot03.png" width="50%" height="50%"/></p>
<p>插入元素的定义有严格的要求,它必须是自定义元素的直接子节点. 如果这些节点中没有 slot 属性,并且你没有指定$slot的值 ,那么就没有插入点,
如果你指定了slot属性,并且也指定了$slot,那么除了那些指定了slot属性的节点,其他子节点都是$slot的
</p>
<p>在<b>avalon1.5.5</b>中,自定义标签支持由取其on-xxx属性,框架会将它转换为onXXX,属性值为一个函数名,框架会在之前的vmodels中查找,
最后将它合并到组件VM中.
</p>
<h2 id="getvm">获取组件VM</h2>
<xmp class="html">
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script type="text/javascript" src="avalon.js"></script>
<script type="text/javascript">
var tabVM
avalon.library("test", {
$ready: function (vm) {
if (vm.$id === "tab1") {
//在这里操作
tabVM = vm
console.log(vm.$id)
console.log(vm)
}
}
})
avalon.component("test:tab", {
$template: '<h1>{{content|html}}</h1>',
content: "", //插入点机制
$slot: "content", //插入点机制
$init: function (vm, el) {
},
$ready: function (vm, el) {
},
$dispose: function (vm, el) {
el.innerHTML = "";
}
});
</script>
<script type="text/javascript">
avalon.ready(function () {
var vm = avalon.define({
$id: "test",
onClick: function () {
if (tabVM) {
alert("onClick " + tabVM.$id)
}
}
});
avalon.scan(document.body, vm);
});
</script>
</head>
<body ms-controller="test">
<div ms-click="onClick">点我</div>
<test:tab $id="tab1" >这是tabs中的内容</test:tab>
</body>
</html>
</xmp>
<p>缺点是需要预先指定组件VM的$id</p>
<xmp class="html">
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script type="text/javascript" src="avalon.js"></script>
<script type="text/javascript">
var tabVM
avalon.component("test:tab", {
$template: '<h1>{{content|html}}</h1>',
content: "", //插入点机制
$slot: "content", //插入点机制
onInit: function () {
}, //必须定义此接口
$init: function (vm, el) {
},
$ready: function (vm, el) {
vm.onInit(vm)
},
$dispose: function (vm, el) {
el.innerHTML = "";
}
});
</script>
<script type="text/javascript">
avalon.ready(function () {
var vm = avalon.define({
$id: "test",
getVM: function (vm) {//用来重写onInit
tabVM = vm
//这里继续做其他事
},
onClick: function () {
if (tabVM) {
alert("onClick " + tabVM.$id)
}
}
});
avalon.scan(document.body, vm);
});
</script>
</head>
<body ms-controller="test">
<div ms-click="onClick">点我</div>
<test:tab on-init="getVM" >这是tabs中的内容</test:tab>
</body>
</html>
</xmp>
<p>此例则不需要指定VM,有点麻烦,但通用性更好</p>
<h2 id="construct">组件的构建全过程</h2>
<p>avalon的扫描器从上往下扫描, 如果遇到带<b>:</b>号的自定义标签,就默认为是组件,如果它上面存在一个叫msResolved属性,那么立即开始构建过程.</p>
<p>如果没有msResolved属性,就把此元素的相关信息放到一个叫componentQueue列队中.</p>
<p>待到该组件的JS文件加载下来,执行avalon.component方法时,componentQueue就会被重新检测,如果符合条件,就执行它的构建过程</p>
<p>构建过程以下:</p>
<fieldset>
<ol>
<li>设置一变量dependencies,其值为1</li>
<li>读取其所有属性,去掉其中的$id, config, id, class, tabindex, style,
ms-*属性,data-*属性,构成一个叫elemOpts的对象</li>
<li>读取其config属性, 从上层的VM中查找与其值同名的对象, 称之为vmOpts对象</li>
<li>将上面的对象与组件的定义对象,合并成一个对象, 这个合并过程可以通过$construct方法进行微调.
否则默认的$construct方法就相当于avalon.mix .</li>
<li>去掉合并对象中$replace, $slot, $template, $container,$construct属性, 然后放进avaon.define中转换一个VM, 称之为组件VM</li>
<li>将当前自定义元素的msResolved属性设置为1 </li>
<li>遍历其子节点,读取它们slot属性,然后 分类,并且一个个文档碎片对象,重赋对应的VM属性</li>
<li>将原$tempate属性放进行$$template方法中进行微调,然后用innerHTML改写自定义标签的内部 </li>
<li>如果用户设置了$replace属性,那么用该元素的第一个子元素代替它自身</li>
<li>如果用户设置了$container属性,那么将该元素移动到新容器之下</li>
<li>然后触发一个datasetchanged事件, 通知上层组件将dependencies减1</li>
<li>为自己绑定一个datasetchanged事件,用于接收下层组件发出来的datasetchanged事件.</li>
<li>开始扫描此元素及其下级 </li>
<li>如果此元素没有孩子,立即触发datasetchanged事件,为自己的dependencies减1,否则将
dependencies减1的逻辑放到一个renderCallbacks的列队中,此列队会在其子节点扫描后执行.
</li>
<li>
datasetchanged事件的逻辑为: 当该组件的子组件初始化时,它们就会为dependencies加1,
ready时就会减1, 并将子组件的VM储放到其$refs对象中, 并且触发一个叫$childReady的回调.
当dependencies为0时,触发自身的ready回调,并移除datasetchanged事件,将此元素放进销毁检测列队
</li>
<li>销毁检测列队是当元素被移出DOM树,并其msRetain属性没指定时,就进行销毁. 这个销毁的检测时机有两利,一是全局的定时轮询,
如果浏览器支持DOMNodeRemovedFromDocument事件,那么也为此元素添加此事件.
</li>
<li>销毁时,会调用$dispose回调,并从avalon.vmodels中将其ID删掉.</li>
</ol>
</fieldset>
<h2 id="ie68">IE6-8的特殊处理</h2>
<p>由于IE6-8下VM是一个VBScript对象 ,函数中的this并不是指向VM,而是window,需要我们在库的$init方法统一hack一下!</p>
<xmp class="javascript">
avalon. libraries.ms.$init = function(vm){
for(var i in vm){
if(vm.hasOwnProperty(i) && typeof vm[i] === "function"){
vm[i] = vm[i].bind(vm)
}
}
}
</xmp>
<p>如果你的组件是以ms为命名空间的话, 不需要使用avalon.librarys("ms"), 否则就要像下面这样做</p>
<xmp class="html">
<script src="avalon.js"></script>
<script>
avalon.library("oni") //这个东西必须跟挨着avalon库,并且放到head标签中
</script>
</xmp>
<p>如果你是使用requirejs加载avalon库, 那你需要在head标签中插入以下脚本</p>
<xmp class="html">
<script src="avalon.js"></script>
<script>
if (document.namespaces) {
document.namespaces.add("oni", 'http://www.w3.org/1999/xhtml')
}
</script>
</xmp>
<h2 id="tag">自定义标签是没有半闭合一说</h2>
<p>
<xx:aaa />浏览器总是将它解析成<xxx:aaa></xxx:aaa>,
如果它后跟着一个元素,就悲剧了!<xxx:aaa/><strong>111</strong>,
本来设想好的平级关系变成父子关系了,
<xxx:aaa><strong>111</strong></xxx:aaa>
</p>
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。