Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

ExtJS 4的容器机制

6
Apr
2013

ExtJS 4的容器机制

By Alex
/ in JavaScript
/ tags ExtJS
0 Comments
ExtJS容器简介

容器是一种特殊的组件,容器与一般组件的根本差别在于,它的内部可以包含其他组件(包括容器)作为其“子组件(items)”。容器提供添加、删除、插入子组件的方法和相关的事件。此外,容器还引入了“容器布局”,专门处理子组件的大小、位置。

ExtJS容器的初始化和渲染

在ExtJS 4的组件机制一文中,包含了对三层嵌套的面板的渲染阶段的整体分析,请参考。

本节主要分析以下几点内容:

  1. 容器(及其子组件)的初始化过程
  2. 容器渲染阶段,如何得到和处理渲染树
  3. 渲染树如何委托布局来渲染子组件

考虑下面的例子:

JavaScript
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结构:

c0b0b1

容器的初始化

在容器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,触发渲染阶段开始
容器的渲染

如ExtJS 4的组件机制一文所分析,渲染阶段会自上而下的onRender,并自下而上的完成afterRender,完成整个组件层次的渲染。渲染的核心是渲染树,渲染树包括tpl属性,是一个XTemplate模板,以及从Layout等对象获取过来控制渲染细节的若干函数。

渲染树tpl.html是模板内容,它看起来很简单,下表列出不同容器tree.tpl.html:

容器 tree.tpl.html
 Container 由AbstractContainer.renderTpl提供:
XHTML
1
{%this.renderContainer(out,values)%}
 Panel 由AbstractPanel.renderTpl提供,核心仍然是renderContainer,但是还包括dockedItems的渲染逻辑:
XHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{% this.renderDockedItems(out,values,0); %}
<div id="{id}-body"
     class="{baseCls}-body
            <tpl if="bodyCls"> {bodyCls}</tpl> {baseCls}-body-{ui}
            <tpl if="uiCls">
                <tpl for="uiCls">
                    {parent.baseCls}-body-{parent.ui}-{.}
                </tpl>
            </tpl>"
      <tpl if="bodyStyle">
      style="{bodyStyle}"
      </tpl>
>
    {%this.renderContainer(out,values);%}
</div>
{% this.renderDockedItems(out,values,1); %}

在填充渲染树模板过程中,通过调用renderContainer(),容器的渲染行为会最终委托给布局对象,而布局也是使用模板机制进行渲染,下表列出常见布局使用的模板:

 布局 renderTpl 
Container 由Ext.layout.container.Container提供:
XHTML
1
{%this.renderBody(out,values)%}
Auto 由Ext.layout.container.Auto提供:
XHTML
1
2
3
{%this.renderBody(out,values)%}
<div id="{ownerId}-clearEl" class="x-clear" role="presentation">
</div>

可以看到,renderContainer()方法是容器渲染(生成DOM结构)的核心,那么这个函数从何而来?

渲染树tpl模板成员函数的来源

tree.tpl是在渲染调用链:render() - getRenderTree() - getElConfig()中生成的:

  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的具体过程

明确渲染树之后,render()方法会调用Ext.DomHelper()把渲染树插入到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(),完成渲染过程,细节内容请参考:ExtJS 4的组件机制

布局渲染子组件的过程

如上一节➀,布局对象调用doRenderItems来完整子组件的渲染,这是合理的,因为容器内子组件的排列、大小、位置就是由布局对象负责的。

子组件渲染具体过程如下:

  1. doRenderItems()调用layout.getRenderTree()获取布局的渲染树
    1. 调用Ext.layout.Layout.getItemsRenderTree(),遍历每个子组件(b0、b1):
      1. 对组件调用layout.configureItem(item),进行布局的预处理
      2. 对每个组件调用item.getRenderTree(),即获取子组件本身的渲染树
      3. 返回子组件渲染树的数组
  2. doRenderItems()调用DomHelper.generateMarkup()生成子组件的DOM结构,如果子组件本身是容器,这里会有个递归处理过程,类似 ➁
  3. 输出:
    XHTML
    1
    2
    <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()进行覆盖,因此调用的就是原始版本:

  1. 把c0的html配置附加到输出 This is a container 
  2. 如果c0配置了tpl,把该模板填充并输出
ExtJS容器基础类
Ext.container.AbstractContainer

该私有类为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事件。

注意:

  1. 如果add()时,容器已经渲染完成,容器会把子组件渲染到它的内容区域,并且可能重新计算布局
  2. 如果有若干组件需要添加,应当传入数组一起添加,防止重复进行布局降低性能
  3. 由BorderLayout布局管理的直接子组件,不支持add或者remove
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类的源代码分析如下:

Ext.container.AbstractContainer.js
JavaScript
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();
    }
});
Ext.container.Container

该类的包含的逻辑很少,提供以下成员:

配置项/属性/方法  说明
{anchorSize} Number/Object,要么是数字,要么是{width:,height:}形式的对象,在容器使用AnchorLayout 时该配置有意义。默认情况下,AnchorLayout会基于容器本身大小进行子组件的anchor计算,如果指定anchorSize,则以anchorSize为基准计算
getChildByElement()  Ext.Component ( Ext.Element/HTMLElement/String el, Boolean deep ),返回包含指定元素的子组件

Container类的源代码分析如下:

Ext.container.Container.js
JavaScript
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
    }});
});
← ExtJS 4的组件机制
浅析ExtJS 4表单字段 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • ExtJS 4的MVC框架
  • ExtJS 4的事件系统
  • 浅析ExtJS新特性
  • ExtJS 4中的选取器控件
  • 定制ExtJS 4主题

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • 基于Calico的CNI 27 people like this
  • Ceph学习笔记 27 people like this
  • Three.js学习笔记 24 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2