ExtJS 4的容器机制
容器是一种特殊的组件,容器与一般组件的根本差别在于,它的内部可以包含其他组件(包括容器)作为其“子组件(items)”。容器提供添加、删除、插入子组件的方法和相关的事件。此外,容器还引入了“容器布局”,专门处理子组件的大小、位置。
在ExtJS 4的组件机制一文中,包含了对三层嵌套的面板的渲染阶段的整体分析,请参考。
本节主要分析以下几点内容:
- 容器(及其子组件)的初始化过程
- 容器渲染阶段,如何得到和处理渲染树
- 渲染树如何委托布局来渲染子组件
考虑下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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)阶段,包含以下重要逻辑:
- 调用getLayout()初始化布局对象
- 使用静态函数Ext.layout.Layout.create()创建布局对象,由于未指定布局方式,创建为Ext.layout.container.Auto
- 调用AbstractContainer.setLayout()设置容器与布局的关联性。注意布局和容器是一对一的关联关系
- 调用initItems()初始化子组件
- initItems()调用AbstractContainer.add()
- add()调用prepareItems(),把b0、b1添加为子组件,并完成子组件的初始化阶段:
- 调用lookupComponent()查找组件:如果子组件存在于ComponentManager中,则获取之,否则构造之
- 在本例中需要构造,调用子组件的constructor、initComponent
- 如果子组件也是容器(本例不是),则又是一个递归的过程,直到最底层的子代组件被初始化
- add()遍历所有被初始化完毕的子组件,执行:
- 对于浮动组件,转移到floatingItems集合中,并调用子组件onAdded()模板
- 对于非浮动组件,存放在items集合中,分别:
- 触发beforeadd事件、调用onBeforeAdd模板,只要任一返回false,取消添加
- 调用子组件onAdded(),导致子组件added事件触发
- 调用容器onAdd()
- 调用布局onAdd()
- 发布add事件
- 由于配置了renderTo,触发渲染阶段开始
如ExtJS 4的组件机制一文所分析,渲染阶段会自上而下的onRender,并自下而上的完成afterRender,完成整个组件层次的渲染。渲染的核心是渲染树,渲染树包括tpl属性,是一个XTemplate模板,以及从Layout等对象获取过来控制渲染细节的若干函数。
渲染树tpl.html是模板内容,它看起来很简单,下表列出不同容器tree.tpl.html:
容器 | tree.tpl.html | ||
Container | 由AbstractContainer.renderTpl提供:
|
||
Panel | 由AbstractPanel.renderTpl提供,核心仍然是renderContainer,但是还包括dockedItems的渲染逻辑:
|
在填充渲染树模板过程中,通过调用renderContainer(),容器的渲染行为会最终委托给布局对象,而布局也是使用模板机制进行渲染,下表列出常见布局使用的模板:
布局 | renderTpl | ||
Container | 由Ext.layout.container.Container提供:
|
||
Auto | 由Ext.layout.container.Auto提供:
|
可以看到,renderContainer()方法是容器渲染(生成DOM结构)的核心,那么这个函数从何而来?
渲染树tpl模板成员函数的来源
tree.tpl是在渲染调用链:render() - getRenderTree() - getElConfig()中生成的:
- 如果容器使用模拟的圆角外框(注意,如果支持CSS3则绝不会通过图片去模拟圆角外框效果),调用initFramingTpl()
- 获取Renderable.frameTpl或者frameTplTable
- 调用Renderable.setupFramingTpl()对frameTpl进行预处理:
- 把模板成员函数applyRenderTpl关联到this.doApplyRenderTpl
- 把模板成员函数renderDockedItems关联到this.doRenderFramingDockedItems
- frameTpl中包含对applyRenderTpl()的调用代码,这段代码的前后则是圆角外框效果的HTML
- applyRenderTpl()就是doApplyRenderTpl(),后者则转调initRenderTpl()
- 至此,可以看到initRenderTpl()是核心所在
- 如果容器不使用模拟的圆角外框,直接调用initRenderTpl()
- 调用getTpl()把上表中AbstractContainer.renderTpl获取作为模板
- 调用setupRenderTpl()
- 获取容器布局对象
- 调用Renderable覆盖版本:设置tpl.renderBody=tpl.renderContent=this.doRenderContent
- 调用layout.setupRenderTpl
- tpl.renderBody = layout.doRenderBody
- tpl.renderContainer = layout.doRenderContainer
- tpl.renderItems = layout.doRenderItems
- tpl.renderPadder = layout.doRenderPadder
自此,渲染树的所有成员变量,成员函数的来源均已分析清楚。
渲染树插入DOM的具体过程
明确渲染树之后,render()方法会调用Ext.DomHelper()把渲染树插入到DOM结构中,细节如下:➁
- 获取c0的渲染树后,Renderable.render()调用Ext.DomHelper.append()进行DOM插入
- 调用 Ext.DomHelper.insert()
- 调用Ext.DomHelper.markup(),调用generateMarkup()生成渲染树的HTML字符串,具体如下:
- 输出c0的封装元素:<tree.tag,即 <div
- 遍历tree的属性,判断哪些需要作为封装元素的属性
- cls作为属性,输出: class="x-container x-container-default"
- style作为属性,输出: style="background-color:#f88;width:250px;height:250px;"
- id作为属性,输出: id="c0"
- 输出 > ,关闭封装元素的开始标签
- 调用tree.tpl.applyOut,填充模板并输出:
- 调用renderContainer(),这一步是容器渲染核心的起点
- 上一步即调用Auto布局父类layout.container.Container.doRenderContainer()
- 由于this指针的问题,从模板数据中取得$comp.layout,即当前布局对象
- 获取布局的渲染模板lt,调用layout.getRenderTpl(),结果如上表Auto
- 调用layout.owner.setupRenderTpl()对lt进行预处理,owner即c0
- 设置lt.renderBody=lt.renderContent=c0.doRenderContent
- 调用layout.setupRenderTpl(),设置:
- lt.renderBody = layout.doRenderBody
- lt.renderContainer = layout.doRenderContainer
- lt.renderItems = layout.doRenderItems
- lt.renderPadder = layout.doRenderPadder
- 以上3步完成布局渲染模板的初始化后,调用layout.getRenderData()初始化渲染上下文:
- $comp = c0
- $layout = c0.layout
- ownerId = c.id
- 执行布局渲染模板的applyOut()
- 调用lt.renderBody(),即layout.doRenderBody()
- 调用lt.renderItems(),即layout.doRenderItems()渲染子组件 ➀
- 调用lt.renderContent(),即c0.doRenderContent()渲染容器内容
- 输出: <div id="c0-clearEl" class="x-clear" role="presentation"></div>
- 调用lt.renderBody(),即layout.doRenderBody()
- 输出c0的封装元素的关闭标签: </div>
- Renderable.render()调用wrapPrimaryEl(),至此,c0.getEl()不再返回undefined
- 调用Renderable.finishRender(),完成渲染过程,细节内容请参考:ExtJS 4的组件机制
布局渲染子组件的过程
如上一节➀,布局对象调用doRenderItems来完整子组件的渲染,这是合理的,因为容器内子组件的排列、大小、位置就是由布局对象负责的。
子组件渲染具体过程如下:
- doRenderItems()调用layout.getRenderTree()获取布局的渲染树
- 调用Ext.layout.Layout.getItemsRenderTree(),遍历每个子组件(b0、b1):
- 对组件调用layout.configureItem(item),进行布局的预处理
- 对每个组件调用item.getRenderTree(),即获取子组件本身的渲染树
- 返回子组件渲染树的数组
- 调用Ext.layout.Layout.getItemsRenderTree(),遍历每个子组件(b0、b1):
- doRenderItems()调用DomHelper.generateMarkup()生成子组件的DOM结构,如果子组件本身是容器,这里会有个递归处理过程,类似 ➁
- 输出:
12<div class="x-component x-component-default" style="background-color:#8f8;width:100px;height:100px;" id="c0-b0"></div><div class="x-component x-component-default" style="background-color:#88f;width:100px;height:100px;" id="c0-b1"></div>
容器内容的渲染过程
Container类没有对Renderable.doRenderContent()进行覆盖,因此调用的就是原始版本:
- 把c0的html配置附加到输出 This is a container
- 如果c0配置了tpl,把该模板填充并输出
该私有类为Sencha的RIA框架产品线提供了公共的逻辑,包括以下成员:
配置项/属性/方法 | 说明 |
{activeItem} | String/Number,需要在容器渲染后处于“激活”状态的子组件ID或者索引。该配置只对那种每次显示一个子组件的布局有意义(例如Card) |
{autoDestroy} | Boolean = true,如果为true,容器会销毁所有从中移除的子组件 |
{bubbleEvents} | String[] = ["add", "remove"],支持冒泡的事件 |
{defaultType} | String = 'panel',子组件默认的xtype |
{defaults} |
Object/Function,传递给子组件的默认配置,通过items配置、add/insert方法加入的子组件,自动获得这些配置 如果传递参数,将把scope设置为当前容器,被添加组件作为第1参数调用,其结果作为config使用 |
{detachOnRemove} | Boolean = true,如果设置为true,把所有移除的组件放到detachedBody,仅在移除尚未销毁的子组件(注意autoDestroy配置)时可用 |
{items} |
Object/Object[],一个或者多个需要被添加到容器的子组件,每个Object必须是Component实例或者Component配置对象(需要指定xtype) 除非指定layout,否则子组件只是逐个的被渲染到它们的封装元素中,不做任何位置大小计算。 注意:ExtJS使用延迟渲染机制,子组件只在必要的时候才会渲染,子组件会在第一次显示时完成布局 |
{layout } |
String/Object,指定容器的布局方式。如果不指定,自动使用Ext.layout.container.Auto 可用的布局方式包括:card/fit/hbox/vbox/anchor/table等 |
{suspendLayout } | Boolean = false,是否暂停布局 |
items | Ext.util.AbstractMixedCollection,包含所有子组件的混合集合对象 |
add() |
Ext.Component[]/Ext.Component ( Ext.Component[]/Ext.Component... component ) 添加一个或者多个子组件到容器。在添加前会触发beforeadd事件,在添加后悔触发add事件。 注意:
|
insert() |
Ext.Component( Number index, Ext.Component component ),在指定位置插入子组件 |
move() |
Ext.Component( Number fromIdx, Number toIdx ) ,在容器内部移动组件 |
cascade() | Ext.Container ( Function fn, [Object scope], [Array args] ),向下递归的遍历所有子组件,执行指定的函数,任何一次执行返回false则停止遍历 |
doLayout() | Ext.container.Container ( ),强制重新计算布局,ExtJS内部通常使用该方法刷新布局 |
remove() | Ext.Component ( Ext.Component/String component, [Boolean autoDestroy] )。移除子组件,在移除前触发 beforeremove,移除后触发remove |
removeAll() | Ext.Component[] ( [Boolean autoDestroy] ) 。移除所有子组件 |
组件查询方法 | |
child() | Ext.Component ( [String selector] ),返回匹配选择器的第一个子组件 |
down() | Ext.Component ( [String selector] ),返回匹配选择器的第一个后代 |
getComponent() | Ext.Component ( String/Number comp ) ,根据id、itemId或者索引来获取直接子组件 |
isAncestor() | Boolean ( Ext.Component possibleDescendant ),判断当前容器是不是目标组件的祖先 |
query() | Ext.Component[] ( [String selector] ) ,根据选择器查询匹配的子代 |
queryBy() | Ext.Component[] ( Function fn, [Object scope] ),根据过滤器函数查询子代 |
queryById() | Ext.Component ( String id ) ,根据id、itemId查询子代,返回第一个匹配的条目 |
模板方法 | |
afterLayout() | void ( Ext.layout.container.Container layout ) 当容器已经完成其子组件的布局(包括必要的子组件渲染)时调用 |
beforeLayout( ) | Boolean (),在容器布局前调用,如果返回false阻止布局发生 |
onAdd() | void ( Ext.Component component, Number position ),在组件被添加后调用 |
onBeforeAdd() | Boolean ( Ext.Component item ),在添加组件前调用,返回false阻止添加 |
onRemove() | void ( Ext.Component component, Boolean autoDestroy ),在组件被移除后调用 |
事件 | |
⚡add | void ( Ext.container.Container this, Ext.Component component, Number index, Object eOpts ),子组件被添加或者插入进来时触发 |
⚡afterlayout | void ( Ext.container.Container this, Ext.layout.container.Container layout, Object eOpts ),当容器布局执行完毕后触发 |
⚡beforeadd | Boolean ( Ext.container.Container this, Ext.Component component, Number index, Object eOpts ),在添加或者插入子组件前触发,任一监听器返回false导致停止添加 |
⚡beforeremove | Boolean ( Ext.container.Container this, Ext.Component component, Object eOpts ),在移除子组件前触发,任一监听器返回false导致停止移除 |
⚡remove | void ( Ext.container.Container this, Ext.Component component, Object eOpts ) ,在子组件移除后触发 |
AbstractContainer类的源代码分析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
Ext.define('Ext.container.AbstractContainer', { renderTpl: '{%this.renderContainer(out,values)%}', suspendLayout : false, autoDestroy : true, defaultType: 'panel', detachOnRemove: true, isContainer : true, layoutCounter : 0, baseCls: Ext.baseCSSPrefix + 'container', bubbleEvents: ['add', 'remove'], initComponent : function(){ var me = this; //初始化事件 me.addEvents( 'afterlayout', 'beforeadd', 'beforeremove', 'add', 'remove' ); me.callParent();//调用覆盖版本 me.getLayout(); //初始化布局 me.initItems(); //初始化子组件,合并为数组并调用add() }, initItems : function() { var me = this, items = me.items; me.items = new Ext.util.AbstractMixedCollection(false, me.getComponentId); if (items) { if (!Ext.isArray(items)) { items = [items]; } me.add(items); } }, //渲染当前容器、及其子组件 //该方法直接使用了Renderable的版本 //子组件如何渲染,容器并不知道,所以委托其布局对象去渲染 //详细参考《容器的渲染过程》一节 render : function(){this.callParents(arguments);}, //聚焦元素 getFocusEl: function() { return this.getTargetEl(); }, //该方法会被finishRender调用,完成子组件/子元素的渲染 finishRenderChildren: function () { this.callParent(); //覆盖版本完成了子元素的渲染,属于组件布局系统 var layout = this.getLayout(); if (layout) { layout.finishRender(); //容器布局系统,完成子组件的渲染 } }, //生命周期模板方法 beforeRender: function () { var me = this, layout = me.getLayout(); me.callParent(); if (!layout.initialized) { //如果布局尚未初始化,初始化之 layout.initLayout(); } }, //初始化渲染模板,对当前类的renderTpl预处理 setupRenderTpl: function (renderTpl) { var layout = this.getLayout(); this.callParent(arguments); layout.setupRenderTpl(renderTpl); }, //设置容器的布局 setLayout : function(layout) { var currentLayout = this.layout; if (currentLayout && currentLayout.isLayout && currentLayout != layout) { currentLayout.setOwner(null); //解除与当前正在使用的布局的关联 } //设置新布局的双向关联 this.layout = layout; layout.setOwner(this); }, //初始化当前容器关联的布局对象 getLayout : function() { var me = this; if (!me.layout || !me.layout.isLayout) { me.setLayout(Ext.layout.Layout.create(me.layout, me.self.prototype.layout || 'autocontainer')); } return me.layout; }, //就是调用updateLayout(),不一定会flush doLayout : function() { this.updateLayout(); return this; }, afterLayout : function(layout) { var me = this; ++me.layoutCounter; if (me.hasListeners.afterlayout) { me.fireEvent('afterlayout', me, layout); } }, //准备子组件 prepareItems : function(items, applyDefaults) { if (Ext.isArray(items)) { items = items.slice(); //浅拷贝数组 } else { items = [items]; } var me = this, i = 0, len = items.length, item; //遍历处理子组件 for (; i < len; i++) { item = items[i]; if (item == null) { Ext.Array.erase(items, i, 1); --i; --len; } else { if (applyDefaults) { item = this.applyDefaults(item);//应用默认配置 } //告诉子组件,它在容器的初始化阶段就被包含在items中 item.isContained = me; items[i] = me.lookupComponent(item); //查找或者创建子组件 delete item.isContained; } } return items; }, //如果在组件管理器中存在,则查找,否则创建 lookupComponent : function(comp) { return (typeof comp == 'string') ? Ext.ComponentManager.get(comp) : Ext.ComponentManager.create(comp, this.defaultType); }, add : function() { var me = this, args = Ext.Array.slice(arguments), index = (typeof args[0] == 'number') ? args.shift() : -1, layout = me.getLayout(), addingArray, items, i, length, item, pos, ret; if (args.length == 1 && Ext.isArray(args[0])) { items = args[0]; addingArray = true; } else { items = args; } ret = items = me.prepareItems(items, true); //准备子组件,并应用缺省配置 length = items.length; if (me.rendered) { Ext.suspendLayouts(); //如果容器已经渲染,要在添加期间暂停布局 } if (!addingArray && length == 1) { ret = items[0]; } for (i = 0; i < length; i++) { pos = (index < 0) ? me.items.length : (index + i); if (item.floating) { //浮动组件的处理 me.floatingItems = me.floatingItems || new Ext.util.MixedCollection(); me.floatingItems.add(item); item.onAdded(me, pos); } else if ((!me.hasListeners.beforeadd || me.fireEvent('beforeadd', me, item, pos) !== false) && me.onBeforeAdd(item) !== false) { //普通组件的处理 me.items.insert(pos, item); item.onAdded(me, pos); me.onAdd(item, pos); layout.onAdd(item, pos); if (me.hasListeners.add) { me.fireEvent('add', me, item, pos); //发布事件 } } } //更新(计算)布局 me.updateLayout(); if (me.rendered) { //恢复布局系统 Ext.resumeLayouts(true); } return ret; }, //子组件相关模板方法 onAdd : Ext.emptyFn, onRemove : Ext.emptyFn, onBeforeAdd : function(item) { var me = this, border = item.border; if (item.ownerCt && item.ownerCt !== me) { //如果组件原来就在某个容器中,自动将其移除 //因为有这个逻辑,实际使用时不必先移除,再添加 item.ownerCt.remove(item, false); } if (me.border === false || me.border === 0) { item.border = Ext.isDefined(border) && border !== false && border !== 0; } }, //移动子组件的位置并重新布局 move : function(fromIdx, toIdx) { var items = this.items, item; item = items.removeAt(fromIdx); if (item === false) { return false; } items.insert(toIdx, item); this.doLayout(); return item; }, remove : function(comp, autoDestroy) { var me = this, c = me.getComponent(comp); //before模板方法调用、事件发布 if (c && (!me.hasListeners.beforeremove || me.fireEvent('beforeremove', me, c) !== false)) { me.doRemove(c, autoDestroy);//执行实际移除逻辑 if (me.hasListeners.remove) { me.fireEvent('remove', me, c); //移除事件 } //如果容器本身正在销毁过程中,则不需要更新布局 if (!me.destroying) { me.doLayout(); } } return c; }, doRemove : function(component, autoDestroy) { var me = this, layout = me.layout, hasLayout = layout && me.rendered, destroying = autoDestroy === true || (autoDestroy !== false && me.autoDestroy); autoDestroy = autoDestroy === true || (autoDestroy !== false && me.autoDestroy); me.items.remove(component); //从items中剔除 if (hasLayout) { if (layout.running) { //取消正在进行的布局 Ext.AbstractComponent.cancelLayout(component, destroying); } layout.onRemove(component, destroying); //模板方法 } component.onRemoved(destroying);//模板方法 me.onRemove(component, destroying);//模板方法 if (destroying) { component.destroy();//可选的,销毁子组件 } else { if (hasLayout) { layout.afterRemove(component); } if (me.detachOnRemove && component.rendered) { //把子组件转移到detachedBody Ext.getDetachedBody().appendChild(component.getEl()); } } }, //移除所有子组件 removeAll : function(autoDestroy) { var me = this, removeItems = me.items.items.slice(), items = [], i = 0, len = removeItems.length, item; me.suspendLayouts(); for (; i < len; i++) { item = removeItems[i]; me.remove(item, autoDestroy); if (item.ownerCt !== me) { items.push(item); } } me.resumeLayouts(!!len); return items; }, //启用容器 enable: function() { this.callParent(arguments); var itemsToDisable = this.getChildItemsToDisable(), length = itemsToDisable.length, item, i; for (i = 0; i < length; i++) { item = itemsToDisable[i]; if (item.resetDisable) { item.enable(); } } return this; }, //禁用容器 disable: function() { this.callParent(arguments); var itemsToDisable = this.getChildItemsToDisable(), length = itemsToDisable.length, item, i; for (i = 0; i < length; i++) { item = itemsToDisable[i]; if (item.resetDisable !== false && !item.disabled) { item.disable(); item.resetDisable = true; } } return this; }, //布局前模板方法,返回false禁止布局 beforeLayout: function() { return true; }, //容器销毁前模板方法 beforeDestroy : function() { var me = this, items = me.items, c; //销毁前首先移除、销毁子组件 if (items) { while ((c = items.first())) { me.doRemove(c, true); } } //销毁容器关联的布局对象 Ext.destroy( me.layout ); me.callParent(); } }); |
该类的包含的逻辑很少,提供以下成员:
配置项/属性/方法 | 说明 |
{anchorSize} | Number/Object,要么是数字,要么是{width:,height:}形式的对象,在容器使用AnchorLayout 时该配置有意义。默认情况下,AnchorLayout会基于容器本身大小进行子组件的anchor计算,如果指定anchorSize,则以anchorSize为基准计算 |
getChildByElement() | Ext.Component ( Ext.Element/HTMLElement/String el, Boolean deep ),返回包含指定元素的子组件 |
Container类的源代码分析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
Ext.define('Ext.container.Container', { extend: 'Ext.container.AbstractContainer', alias: 'widget.container', alternateClassName: 'Ext.Container', fireHierarchyEvent: function (ename) { //使hierarchyEventSource对象发布事件,并且把当前对象作为事件参数 this.hierarchyEventSource.fireEvent(ename, this); }, //生命周期模板方法:以下三个方法都是通过hierarchyEventSource对象发布相应的事件 afterHide: function() { this.callParent(arguments); this.fireHierarchyEvent('hide'); }, afterShow: function(){ this.callParent(arguments); this.fireHierarchyEvent('show'); }, onAdded: function() { this.callParent(arguments); if (this.hierarchyEventSource.hasListeners.added) { this.fireHierarchyEvent('added'); } }, //寻找“直接”包含某个元素的子组件 getChildByElement: function(el, deep) { var item, itemEl, i = 0, it = this.getRefItems(), ln = it.length; el = Ext.getDom(el); for (; i < ln; i++) { item = it[i]; itemEl = item.getEl(); if (itemEl && ((itemEl.dom === el) || itemEl.contains(el))) { return (deep && item.getChildByElement) ? item.getChildByElement(el, deep) : item; } } return null; } }, function () { this.hierarchyEventSource = this.prototype.hierarchyEventSource = new Ext.util.Observable({ events: { //支持的事件 hide: true, show: true, collapse: true, expand: true, added: true }}); }); |
Leave a Reply