Extjs容器的渲染机制
日期:2015-08-25点击次数:17380
在ExtJS中,容器是一种特殊的组件,容器与一般组件的根本差别在于,它的内部可以包含其他组件(包括容器)作为其“子组件(items)”。容器提供添加、删除、插入子组件的方法和相关的事件。此外,容器还引入了“容器布局”,专门处理子组件的大小、位置。
考虑下面的例子:
Ext.create( 'Ext.container.Container', {
renderTo : Ext.getBody(),
id : 'c0',
width : 250,
height : 250,
style : {
backgroundColor : '#f88'
},
html:'This is a container',
items : [
{
id : 'c0-b0',
xtype : 'box',
width : 100,
height : 100,
style : {
backgroundColor : '#8f8'
}
}, {
id : 'c0-b1',
xtype : 'box',
width : 100,
height : 100,
style : {
backgroundColor : '#88f'
}
}
]
} );
这是包含两个Component(b0,b1)的Container(c0),使用Auto布局。下面是最终渲染效果和DOM结构:
在容器c0的初始化(主要是initComponent)阶段,包含以下重要逻辑:
1. 调用getLayout()初始化布局对象
1. 使用静态函数Ext.layout.Layout.create()创建布局对象,由于未指定布局方式,创建为Ext.layout.container.Auto
2. 调用AbstractContainer.setLayout()设置容器与布局的关联性。注意布局和容器是一对一的关联关系
2. 调用initItems()初始化子组件
1. initItems()调用AbstractContainer.add()
2. add()调用prepareItems(),把b0、b1添加为子组件,并完成子组件的初始化阶段:
1. 调用lookupComponent()查找组件:如果子组件存在于ComponentManager中,则获取之,否则构造之
2. 在本例中需要构造,调用子组件的constructor、initComponent
1. 如果子组件也是容器(本例不是),则又是一个递归的过程,直到最底层的子代组件被初始化
3. add()遍历所有被初始化完毕的子组件,执行:
1. 对于浮动组件,转移到floatingItems集合中,并调用子组件onAdded()模板
2. 对于非浮动组件,存放在items集合中,分别:
1. 触发beforeadd事件、调用onBeforeAdd模板,只要任一返回false,取消添加
2. 调用子组件onAdded(),导致子组件added事件触发
3. 调用容器onAdd()
4. 调用布局onAdd()
5. 发布add事件
3. 由于配置了renderTo,触发渲染阶段开始
渲染阶段会自上而下的onRender,并自下而上的完成afterRender,完成整个组件层次的渲染。渲染的核心是渲染树,渲染树包括tpl属性,是一个XTemplate模板,以及从Layout等对象获取过来控制渲染细节的若干函数。
渲染树tpl.html定义了模板内容,在填充渲染树模板过程中,通过调用renderContainer(),容器的渲染行为会最终委托给布局对象,而布局也是使用模板机制进行渲染。
renderContainer()方法是容器渲染(即生成DOM)结构的核心,其逻辑包括渲染树的生成、渲染树插入DOM两个部分。
渲染树生成的详细步骤如下:
1. 如果容器使用模拟的圆角外框(注意,如果支持CSS3则绝不会通过图片去模拟圆角外框效果),调用initFramingTpl()
1. 获取Renderable.frameTpl或者frameTplTable
2. 调用Renderable.setupFramingTpl()对frameTpl进行预处理:
1. 把模板成员函数applyRenderTpl关联到this.doApplyRenderTpl
2. 把模板成员函数renderDockedItems关联到this.doRenderFramingDockedItems
3. frameTpl中包含对applyRenderTpl()的调用代码,这段代码的前后则是圆角外框效果的HTML
4. applyRenderTpl()就是doApplyRenderTpl(),后者则转调initRenderTpl()
5. 至此,可以看到initRenderTpl()是核心所在
2. 如果容器不使用模拟的圆角外框,直接调用initRenderTpl()
1. 调用getTpl()把上表中AbstractContainer.renderTpl获取作为模板
2. 调用setupRenderTpl()
1. 获取容器布局对象
2. 调用Renderable覆盖版本:设置tpl.renderBody=tpl.renderContent=this.doRenderContent
3. 调用layout.setupRenderTpl
1. tpl.renderBody = layout.doRenderBody
2. tpl.renderContainer = layout.doRenderContainer
3. tpl.renderItems = layout.doRenderItems
4. tpl.renderPadder = layout.doRenderPadder
渲染树插入DOM的具体过程如下:
1. 获取c0的渲染树后,Renderable.render()调用Ext.DomHelper.append()进行DOM插入
2. 调用 Ext.DomHelper.insert()
3. 调用Ext.DomHelper.markup(),调用generateMarkup()生成渲染树的HTML字符串,具体如下:
1. 输出c0的封装元素:<tree.tag,即 <div
2. 遍历tree的属性,判断哪些需要作为封装元素的属性
1. cls作为属性,输出: class="x-container x-container-default"
2. style作为属性,输出: style="background-color:#f88;width:250px;height:250px;"
3. id作为属性,输出: id="c0"
3. 输出 > ,关闭封装元素的开始标签
4. 调用tree.tpl.applyOut,填充模板并输出:
1. 调用renderContainer(),这一步是容器渲染核心的起点
2. 上一步即调用Auto布局父类layout.container.Container.doRenderContainer()
1. 由于this指针的问题,从模板数据中取得$comp.layout,即当前布局对象
2. 获取布局的渲染模板lt,调用layout.getRenderTpl(),结果如上表Auto
3. 调用layout.owner.setupRenderTpl()对lt进行预处理,owner即c0
1. 设置lt.renderBody=lt.renderContent=c0.doRenderContent
2. 调用layout.setupRenderTpl(),设置:
1. lt.renderBody = layout.doRenderBody
2. lt.renderContainer = layout.doRenderContainer
3. lt.renderItems = layout.doRenderItems
4. lt.renderPadder = layout.doRenderPadder
4. 以上3步完成布局渲染模板的初始化后,调用layout.getRenderData()初始化渲染上下文:
1. $comp = c0
2. $layout = c0.layout
3. ownerId = c.id
5. 执行布局渲染模板的applyOut()
1. 调用lt.renderBody(),即layout.doRenderBody()
1. 调用lt.renderItems(),即layout.doRenderItems()渲染子组件
2. 调用lt.renderContent(),即c0.doRenderContent()渲染容器内容
2. 输出: <div id="c0-clearEl" class="x-clear" role="presentation"></div>
5. 输出c0的封装元素的关闭标签: </div>
4. Renderable.render()调用wrapPrimaryEl(),至此,c0.getEl()不再返回undefined
5. 调用Renderable.finishRender(),完成渲染过程
至此,容器的渲染阶段完毕,后续将根据需要进行容器及其子组件的展示。
软件部 汪震