ExtJS 4的组件机制
组件、容器是ExtJS组件机制的基础,后者是前者的子类型。
这是一个ExtJS框架的私有混入,定义了组件渲染的核心逻辑,对于下面这个包含三层嵌套的Panel:
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 |
Ext.onReady( function() { Ext.createByAlias( 'widget.panel', { id : 'ContainerA', layout : 'hbox', width : 500, height : 500, renderTo : Ext.getBody(), items : [ { id : 'ContainerB', xtype : 'container', width : 250, height : 250, layout : 'fit', items : [ { id : 'ComponentA', html : 'Hello World !' } ] } ] } ); } ) |
Renderable定义其渲染过程如下:
- ContainerA被初始化时转调父类构造器,在AbstractComponent.constructor()中,由于配置了renderTo,调用render()方法,进入渲染阶段
- render()开始渲染ContainerA,首先禁用布局系统,然后调用getRenderTree(),获取“渲染树”,这个所谓的渲染树就是一个DomHelper规格对象
- getRenderTree()触发事件beforerender、调用beforeRender()模板方法,然后调用getElConfig()获取渲染树对象
- 根据是否配置外框(Window、Tip必须有外框),getElConfig()会生成不同的结果:要么调用initFramingTpl(),要么调用initRenderTpl()生成一个模板,并置入渲染树:{tag : 'div', tpl: template}的tpl属性
- 从细节上看,initRenderTpl()获取当前类(Container)的renderTpl属性作为tpl,该模板具有以下成员:
属性 说明 html 容器的HTML结构,整体形式如下:
1{% this.renderDockedItems(out,values,0); %}{%this.renderContainer(out,values);%}1{% this.renderDockedItems(out,values,1); %}initialConfig 传入的初始配置对象 renderBody 可供在模板中调用的一系列方法
这些xxx方法都是在:- Renderable.setupFramingTpl()
- AbstractContainer.setupRenderTpl()
等方法中,与容器、布局类的doXxx进行关联得到的。之所以要和布局类的doXxx关联,是因为布局才知道子组件如何渲染
renderContainer renderContent renderDockedItems renderItems renderPadder - 如果配置了autoEl,替换渲染树的tag属性
- 调用initRenderData生成tpl的填充上下文(tplData),并提供一些默认值
- 最终返回的渲染树形式如下(符合DomHelper规格约定):
123456789101112131415161718192021222324252627282930{tag: "div",cls: "",style: {height: "500px"width: "500px"},id: "ContainerA"tpl: {html: "..."initialConfig: {}renderBody: function (out, renderData) {...}renderContainer: function (out, renderData) {...}renderContent: function (out, renderData) {...}renderDockedItems: function (out, renderData, after) {...}renderItems: function (out, renderData) {...}renderPadder: function (out, renderData) {...}},tplData : {$comp: '当前组件'baseCls: "x-panel"bodyCls: "x-panel-body-default"bodyStyle: ""componentCls: "x-panel"frame: undefinedid: "ContainerA"ui: "default"uiCls: []}}
- 从细节上看,initRenderTpl()获取当前类(Container)的renderTpl属性作为tpl,该模板具有以下成员:
- render()得到渲染树后,使用DomHelper将其插入到文档中,一旦插入,整个组件(包括所有后代组件)的DOM结构就都生成了,结果如下:
- 插入DOM完毕后,render()设置ContainerA的el为新生成的HTML元素ContainerA
- render()调用finishRender()完成渲染阶段,在此阶段,会自上而下的递归调用所有子代组件的onRender(),然后自下而上的递归完成所有子代组件的afterRender()调用,需要注意的是,子代的onRender()完全处于父代的afterRender()调用的内部
- 调用onRender()
- 设置renderSelectors为成员变量
- 清除正在渲染状态:rendering,设置rendered=true
- 记住lastBox={width:500,height:500}
- 设置targetEl的overflow样式;设置封装元素的visibilityMode
- 如果有存在监听器,则发布render事件
- 调用afterRender(),该方法最终会触发布局,主要逻辑位于Rendererable中
- 调用finishRenderChildren()完成子代的渲染
- 组件布局渲染:调用Renderable.finishRenderChildren()渲染内部结构
- 获取组件布局getComponentLayout(),这里是Ext.layout.component.Dock
- 调用Dock.finishRender(),该方法会获取dockedItems,并调用Ext.layout.Layout.finishRenderItems()逐个渲染
- 容器布局渲染:调用AbstractContainer.finishRenderChildren()渲染子组件
- 获取容器布局getLayout(),这里是Ext.layout.container.HBox
- 调用HBox.finishRender(),后者转调父类(Ext.layout.container.Container)版本
- Container.finishRender()获取渲染目标(targetEl)和渲染子组件(仅一个ContainerB)
- 调用Container.finishRenderItems(target, items)完成子组件渲染
- 转调Ext.layout.Layout.finishRenderItems(),逻辑类似7.4.1.1
- 调用ContainerB的Renderable.finishRender,这一小节是类似于7的递归调用
- 子组件ContainerB的el属性被设置,不再为空
- 调用ContainerB的onRender模板方法,逻辑类似7.1,lastBox为250
- 调用ContainerB.afterRender()
- finishRenderChildren()
- 完成组件布局渲染(Dock)
- 完成容器布局渲染(Fit),继续递归用
- ComponentA.finishRender()
- ComponentA.onRender()
- ComponentA.afterRender()
- ……一直递归到最底层组件……
- ComponentA.finishRender()
- 由于不是顶层容器,不进行updateLayout()
- 如果有存在监听器,则发布afterrender事件
- 初始化事件:initEvents()
- 如果配置为隐藏,那么调用getEl().hide()
- 调用ContainerB的Renderable.finishRender,这一小节是类似于7的递归调用
- 对于每一个item,调用afterRenderItem(item)
- 组件布局渲染:调用Renderable.finishRenderChildren()渲染内部结构
- 为targetEl添加CSS类styleHtmlCls、增减样式
- 由于当前容器是顶层的,调用AbstractComponent.updateLayout()触发布局
- 如果当前组件是隐藏的,调用cancelLayout()取消布局
- 否则判断是否布局根,如果不是,什么都不做
- 由于ContainerA的布局未被暂停,调用静态函数Ext.AbstractComponent.updateLayout执行实际布局逻辑。这里可以看到,AbstractComponent类对象是全局的布局管理对象,updateLayout会把参数指定的组件放入某个队列,并且进行复杂的布局计算
- 如果当前正在运行布局,则把ContainerA放入running的无效队列
- 如果当前没有运行布局,则把ContainerA放入pending的无效队列,这里匹配这一条
- 如果当前布局系统没有被暂停,则立即刷新布局flushLayouts()
- 调用finishRenderChildren()完成子代的渲染
- 如果有存在监听器,则发布afterrender事件
- 初始化事件,调用AbstractComponenet.initEvents()。initEvent()包含若干在组件渲染完毕后,需要初始化的事件监听器
- 如果具有afterRenderEvents,则把这些监听器注册为组件子元素的受管监听器,这些事件属于DOM事件
- 为当前组件添加焦点监听器(addFocusListener)
- 如果配置为隐藏,那么调用getEl().hide()
- 调用onRender()
- render()启用布局系统,转调AbstractComponent.resumeLayouts()静态函数
- 刷出布局,调用flushLayouts()
- 覆写pendingLayouts的runComplete方法防止死循环
- 调用pendingLayouts(类型Ext.layout.Context)的run()方法
- 调用Context.flushInvalidates()
- 转调Context.invalidate(),使相关的组件、容器布局失效
- 调用Context.flush()
- 调用Context.runComplete()
- 调用Context.flushInvalidates()
Renderable类包含以下成员:
配置项/属性/方法 | 说明 |
applyRenderSelectors( ) | 根据renderSelectors、childEls的配置,设置组件对其内部元素的引用 |
doApplyRenderTpl() |
void ( Object out, Object values ),由XTemplate调用,在framing结构中插入当前组件的内部结构。 如果使用了framing,一个自动生成的模板代替renderTpl作为getElConfig()的主模板 |
doAutoRender() | 执行自动渲染(autoRender)。浮动组件可能具有自己的ownerCt来管理ZIndex,虽然他们总是被渲染到document.body |
ensureAttachedToBody() | void ( [Boolean runLayout] )。确保当前组件已经被附加到document.body。如果当前组件被渲染到Ext.getDetachedBody(),该方法可以使之添加到document.body,所有配置的位置被还原 |
finishRender() | void ( Number containerIdx )。访问者模式。自上而下的访问组件树的每一个节点,调用其onRender()方法 |
getFrameInfo() | 根据组件性质、配置获取/计算圆角外框的样式信息 |
getFrameTpl() | void ( Object table ),得到圆角外框的模板 |
getInsertPosition() | HTMLElement ( String/Number/Ext.dom.Element/HTMLElement position ),返回一个可以insertBefore的Element |
initFrame() | 初始化圆角外框模板 |
initRenderData() | Object (),初始化renderData以供renderTpl使用 |
initRenderTpl( ) | Ext.XTemplate(),初始化renderTpl |
render() |
void ( [Ext.Element/HTMLElement/String container], [String/Number position] ),把组件渲染到给定的HTML元素 如果当前组件被容器管理,不需要调用该方法,因为容器的子组件是在容器第一次布局时,由容器的布局管理器负责渲染的。如果添加一个新子组件时,容器已经渲染,可能需要调用容器的doLayout()来强迫新的、未渲染的组件进行渲染。 当创建复杂的组件UI结构时,需要注意子组件的大小、位置是由父容器的布局管理器维护的。 如果父容器不提供layout配置,那么缺省的布局管理器被使用,这种情况下,只是依次渲染子组件,不会管理其大小和位置。 |
setupFramingTpl() | void ( Object frameTpl ),创建framing模板,该模板包裹renderTpl |
模板方法 | |
afterRender() |
在渲染完成后执行额外逻辑,在此时组件的元素已经生成,并且应用了配置的样式,包括CSS、可见性等 |
onRender() |
void( Ext.core.Element parentNode, Number containerIdx ),当组件的DOM结构被创建后,调用该方法。在此刻,组件(及其所有后代)的DOM结构已经存在,但是尚未被布局(尺寸/位置)。子类必须先调用父类的onRender版本,才能访问DOM结构。 parentNode:当前组件封装元素的元素;containerIdx:当前组件在父容器的索引 |
Ext.util.Renderable的代码分析如下:
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 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 |
Ext.define('Ext.util.Renderable', { requires: [ 'Ext.dom.Element' ], //外框:在组件外围制造圆角矩形效果 frameCls: Ext.baseCSSPrefix + 'frame', frameIdRegex: /[\-]frame\d+[TMB][LCR]$/, frameElementCls: { tl: [], tc: [], tr: [], ml: [], mc: [], mr: [], bl: [], bc: [], br: [] }, frameElNames: ['TL','TC','TR','ML','MC','MR','BL','BC','BR'], //基于DIV的外框模板 frameTpl: [ '{%this.renderDockedItems(out,values,0);%}',//渲染停靠的子组件 '<tpl if="top">', '<tpl if="left"><div id="{fgid}TL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl>" style="background-position: {tl}; padding-left: {frameWidth}px" role="presentation"></tpl>', '<tpl if="right"><div id="{fgid}TR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl>" style="background-position: {tr}; padding-right: {frameWidth}px" role="presentation"></tpl>', '<div id="{fgid}TC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl>" style="background-position: {tc}; height: {frameWidth}px" role="presentation"></div>', '<tpl if="right"></div></tpl>', '<tpl if="left"></div></tpl>', '</tpl>', '<tpl if="left"><div id="{fgid}ML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl>" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"></tpl>', '<tpl if="right"><div id="{fgid}MR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl>" style="background-position: {mr}; padding-right: {frameWidth}px" role="presentation"></tpl>', '<div id="{fgid}MC" class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl>" role="presentation">', '{%this.applyRenderTpl(out, values)%}', //渲染主体内容 '</div>', '<tpl if="right"></div></tpl>', '<tpl if="left"></div></tpl>', '<tpl if="bottom">', '<tpl if="left"><div id="{fgid}BL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl>" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"></tpl>', '<tpl if="right"><div id="{fgid}BR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl>" style="background-position: {br}; padding-right: {frameWidth}px" role="presentation"></tpl>', '<div id="{fgid}BC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl>" style="background-position: {bc}; height: {frameWidth}px" role="presentation"></div>', '<tpl if="right"></div></tpl>', '<tpl if="left"></div></tpl>', '</tpl>', '{%this.renderDockedItems(out,values,1);%}'//渲染停靠的子组件 ], //基于TABLE的外框模板 frameTableTpl: [ '{%this.renderDockedItems(out,values,0);%}', '<table><tbody>', '<tpl if="top">', '<tr>', '<tpl if="left"><td id="{fgid}TL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl>" style="background-position: {tl}; padding-left:{frameWidth}px" role="presentation"></td></tpl>', '<td id="{fgid}TC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl>" style="background-position: {tc}; height: {frameWidth}px" role="presentation"></td>', '<tpl if="right"><td id="{fgid}TR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl>" style="background-position: {tr}; padding-left: {frameWidth}px" role="presentation"></td></tpl>', '</tr>', '</tpl>', '<tr>', '<tpl if="left"><td id="{fgid}ML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl>" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"></td></tpl>', '<td id="{fgid}MC" class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl>" style="background-position: 0 0;" role="presentation">', '{%this.applyRenderTpl(out, values)%}', '</td>', '<tpl if="right"><td id="{fgid}MR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl>" style="background-position: {mr}; padding-left: {frameWidth}px" role="presentation"></td></tpl>', '</tr>', '<tpl if="bottom">', '<tr>', '<tpl if="left"><td id="{fgid}BL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl>" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"></td></tpl>', '<td id="{fgid}BC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl>" style="background-position: {bc}; height: {frameWidth}px" role="presentation"></td>', '<tpl if="right"><td id="{fgid}BR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl>" style="background-position: {br}; padding-left: {frameWidth}px" role="presentation"></td></tpl>', '</tr>', '</tpl>', '</tbody></table>', '{%this.renderDockedItems(out,values,1);%}' ], //执行渲染,生成组件的全部HTML并插入到DOM //container是渲染到的HTML父节点 render: function(container, position) { var me = this, el = me.el && (me.el = Ext.get(me.el)), vetoed, tree, nextSibling; //暂停所有布局 Ext.suspendLayouts(); //确保父节点container的引用已经获取 container = me.initContainer(container); //得到下一个兄弟HTML节点,在其前面插入 nextSibling = me.getInsertPosition(position); //对于顶层组件,渲染开始时el是不存在的 if (!el) { //如果当前组件的封装元素还不存在 //getRenderTree将会发布beforerender事件、调用beforeRender模板方法 //然后调用getElConfig()获得当前组件(包括全部子组件)完整的DomHelper规格对象 tree = me.getRenderTree(); if (me.ownerLayout && me.ownerLayout.transformItemRenderTree) { tree = me.ownerLayout.transformItemRenderTree(tree); } // 注意:如果beforeRender返回false,则tree不生成 if (tree) { //插入当前组件到文档中 if (nextSibling) { el = Ext.DomHelper.insertBefore(nextSibling, tree); } else { el = Ext.DomHelper.append(container, tree); } //设置this.el变量 me.wrapPrimaryEl(el); } } else { //如果当前组件的封装元素已经存在 if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) { me.initStyles(el); if (me.allowDomMove !== false) { if (nextSibling) { container.dom.insertBefore(el.dom, nextSibling); } else { container.dom.appendChild(el.dom); } } } else { vetoed = true;//非顶层组件为true } } //如果当前组件是顶层组件,则自上而下的递归整棵组件树(子代组件的finishRender被递归调用) //访问每个组件的onRender()方法 if (el && !vetoed) { me.finishRender(position); } //恢复布局系统,如果组件的parentNode在document.body下,立即刷新所有未决的布局 Ext.resumeLayouts(!container.isDetachedBody); }, //获取组件的parentNode initContainer: function(container) { var me = this; //如果没有指定container,则自动获取el的parentNode if (!container && me.el) { container = me.el.dom.parentNode; me.allowDomMove = false; } me.container = container.dom ? container : Ext.get(container); return me.container; }, //所谓渲染树就是一个DomHelper规格对象 getRenderTree: function() { var me = this; //发布beforerender事件 if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) { //如果beforerender事件句柄没有返回false,则调用beforeRender()模板方法 me.beforeRender(); //该标记可以让布局管理器的finishRenderItems、afterFinishRenderItems指导哪些items需要处理 me.rendering = true; //如果当前组件的封装元素以及存在,则返回一个代理元素 if (me.el) { //由于这里正在生成一棵“渲染树”,因此使用一个代理元素(proxy el)在生成的目标DOM的精确位置进行占位 //在finishRender阶段,代理元素会被真实元素替换 return { tag: 'div', id: (me.$pid = Ext.id()) }; } //否则,生成一个代表当前组件(包含子全部子组件)的完整的DomHelper规格对象 return me.getElConfig(); } return null; }, //生成当前组件完整的DomHelper规格 getElConfig : function() { var me = this, autoEl = me.autoEl, frameInfo = me.getFrameInfo(), //规格对象 config = { tag: 'div',//默认标签是DIV //如果具有组件外框效果,则使用frameTpl包裹renderTpl,否则直接使用initRenderTpl tpl: frameInfo ? me.initFramingTpl(frameInfo.table) : me.initRenderTpl() }, i, frameElNames, len, suffix, frameGenId; //初始化样式 me.initStyles(me.protoEl); me.protoEl.writeTo(config); me.protoEl.flush(); //根据autoEl的配置更新config if (Ext.isString(autoEl)) { config.tag = autoEl; } else { Ext.apply(config, autoEl); } config.id = me.id; //何时不为true? if (config.tpl) { if (frameInfo) { frameElNames = me.frameElNames; len = frameElNames.length; frameGenId = me.id + '-frame1'; me.frameGenId = 1; config.tplData = Ext.apply({}, { $comp: me, //把当前组件作为上下文 fgid: frameGenId, ui: me.ui, uiCls: me.uiCls, frameCls: me.frameCls, baseCls: me.baseCls, frameWidth: frameInfo.maxWidth, top: !!frameInfo.top, left: !!frameInfo.left, right: !!frameInfo.right, bottom: !!frameInfo.bottom, renderData: me.initRenderData() }, me.getFramePositions(frameInfo)); //设置外框子元素与组件的关联(作为成员变量) for (i = 0; i < len; i++) { suffix = frameElNames[i]; me.addChildEls({ name: 'frame' + suffix, id: frameGenId + suffix }); } //对于面板,必须具有frameBody子元素 me.addChildEls({ name: 'frameBody', id: frameGenId + 'MC' }); } else { config.tplData = me.initRenderData(); } } return config; }, //生命周期模板方法:渲染之后 afterRender : function() { var me = this, data = {}, protoEl = me.protoEl, target = me.getTargetEl(), item; //首先,递归的调用子组件的afterRender() me.finishRenderChildren(); //为目标元素(el或者frameBody)添加样式 if (me.styleHtmlContent) { target.addCls(me.styleHtmlCls); } //把protoEl的样式信息(包括style和class)写到对象data protoEl.writeTo(data); //添加、删除样式 item = data.removed; if (item) { target.removeCls(item); } item = data.cls; if (item.length) { target.addCls(item); } item = data.style; if (data.style) { target.setStyle(item); } //protoEl没有利用价值了 me.protoEl = null; //如果当前是最外层组件或容器,立即更新布局 if (!me.ownerCt) { me.updateLayout(); } }, //第一次布局后执行 afterFirstLayout : function(width, height) { var me = this, hasX = Ext.isDefined(me.x), hasY = Ext.isDefined(me.y), pos, xy; //设置组件的位置 if (me.floating && (!hasX || !hasY)) { if (me.floatParent) { pos = me.floatParent.getTargetEl().getViewRegion(); xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c'); pos.left = xy[0] - pos.left; pos.top = xy[1] - pos.top; } else { xy = me.el.getAlignToXY(me.container, 'c-c'); pos = me.container.translatePoints(xy[0], xy[1]); } me.x = hasX ? me.x : pos.left; me.y = hasY ? me.y : pos.top; hasX = hasY = true; } if (hasX || hasY) { me.setPosition(me.x, me.y); } //调用模板方法并发布事件 me.onBoxReady(width, height); if (me.hasListeners.boxready) { me.fireEvent('boxready', me, width, height); } }, //第一次布局后的模板方法,缺省适配 onBoxReady: Ext.emptyFn, //根据renderSelectors、childEls的配置,设置组件对其内部元素的引用 applyRenderSelectors: function() { var me = this, selectors = me.renderSelectors, el = me.el, dom = el.dom, selector; me.applyChildEls(el); if (selectors) { for (selector in selectors) { if (selectors.hasOwnProperty(selector) && selectors[selector]) { me[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], dom)); } } } }, //生命周期模板方法:渲染之前 beforeRender: function () { var me = this, target = me.getTargetEl(), layout = me.getComponentLayout(); //如果当前组件是诸如Window、Tip之类alwaysFramed的组件,那么设置frame为true me.frame = me.frame || me.alwaysFramed; //初始化组件布局管理器 if (!layout.initialized) { layout.initLayout(); } //设置targetEl的overflow样式,如果目前targetEl尚不存在,则在finishRender后会设置 if (target) { target.setStyle(me.getOverflowStyle()); me.overflowStyleSet = true; } //设置主题样式 me.setUI(me.ui); if (me.disabled) { //静默的禁用组件 me.disable(true); } }, doApplyRenderTpl: function(out, values) { //注意,该方法的this指向frameTpl var me = values.$comp, tpl; if (!me.rendered) { //初始化renderTpl tpl = me.initRenderTpl(); //把renderTpl的填充结果添加到输出中 tpl.applyOut(values.renderData, out); } }, //初始化renderTpl initRenderTpl: function() { var tpl = this.getTpl('renderTpl');//得到renderTpl作为模板 if (tpl && !tpl.renderContent) { this.setupRenderTpl(tpl); } return tpl; }, //把renderTpl的renderBody、renderContent设值为doRenderContent函数 setupRenderTpl: function (renderTpl) { renderTpl.renderBody = renderTpl.renderContent = this.doRenderContent; }, doRenderContent: function (out, renderData) { //注意,该方法的this指向frameTpl var me = renderData.$comp; if (me.html) { //如果指定了html配置,把它输出到out Ext.DomHelper.generateMarkup(me.html, out); delete me.html;//删除配置 } if (me.tpl) { //如果指定了tpl,将其填充结果输出到out if (!me.tpl.isTemplate) { me.tpl = new Ext.XTemplate(me.tpl); } if (me.data) { me.tpl.applyOut(me.data, out); delete me.data; } } }, //执行自动渲染 doAutoRender: function() { var me = this; if (!me.rendered) { if (me.floating) { //浮动组件渲染到body元素 me.render(document.body); } else { //如果配置了渲染目标元素,则渲染到目标;否则渲染到body me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender); } } }, doRenderFramingDockedItems: function (out, renderData, after) { //注意,该方法作为frameTpl的成员函数调用,this指向frameTpl var me = renderData.$comp; //大部分组件没有停靠子组件,所以检查其是否具有doRenderDockedItems()函数 if (!me.rendered && me.doRenderDockedItems) { renderData.renderData.$skipDockedItems = true; //调用渲染停靠子组件的函数 me.doRenderDockedItems.call(this, out, renderData, after); } }, //完成该组件的渲染,所有子组件的onRender、afterRender会被调用,所有组件布局、容器布局的计算都会完成 finishRender: function(containerIdx) { var me = this, tpl, data, contentEl, el, pre, hide; //如果尚未渲染,执行渲染(插入DOM) if (!me.el || me.$pid) { if (me.container) { el = me.container.getById(me.id, true); } else { el = Ext.getDom(me.id); } if (!me.el) { me.wrapPrimaryEl(el); } else { delete me.$pid; if (!me.el.dom) { me.wrapPrimaryEl(me.el); } el.parentNode.insertBefore(me.el.dom, el); Ext.removeNode(el); // } } else if (!me.rendering) { tpl = me.initRenderTpl(); if (tpl) { data = me.initRenderData(); tpl.insertFirst(me.getTargetEl(), data); } } //否则,目前正在渲染 if (!me.container) { me.container = Ext.get(me.el.dom.parentNode); } if (me.ctCls) { me.container.addCls(me.ctCls); } //调用onRender模板方法 me.onRender(me.container, containerIdx); if (!me.overflowStyleSet) { me.getTargetEl().setStyle(me.getOverflowStyle()); } me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]); if (me.overCls) { me.el.hover(me.addOverCls, me.removeOverCls, me); } //发布render事件 if (me.hasListeners.render) { me.fireEvent('render', me); } if (me.contentEl) { pre = Ext.baseCSSPrefix; hide = pre + 'hide-'; contentEl = Ext.get(me.contentEl); contentEl.removeCls([pre+'hidden', hide+'display', hide+'offsets', hide+'nosize']); me.getTargetEl().appendChild(contentEl.dom); } //调用afterRender模板方法,在这里会完成所有子组件的渲染 me.afterRender(); if (me.hasListeners.afterrender) { me.fireEvent('afterrender', me); } //初始化事件 me.initEvents(); //如果配置为隐藏,则隐藏组件 if (me.hidden) { me.el.hide(); } }, //渲染子代,这里的默认实现是处理组件布局 //对于容器类组件,还要处理容器布局 finishRenderChildren: function () { var layout = this.getComponentLayout(); //得到组件布局 layout.finishRender(); //由布局完成所有子代的渲染 }, //使用TABLE或者DIV模式获得外框的模板 initFramingTpl: function(table) { var tpl = table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl'); if (tpl && !tpl.applyRenderTpl) { this.setupFramingTpl(tpl); } return tpl; }, //为外框模板添加一些成员函数 setupFramingTpl: function(frameTpl) { frameTpl.applyRenderTpl = this.doApplyRenderTpl; frameTpl.renderDockedItems = this.doRenderFramingDockedItems; }, //在配置的renderData基础上加上额外的属性 initRenderData: function() { var me = this; return Ext.apply({ $comp: me, id: me.id, ui: me.ui, uiCls: me.uiCls, baseCls: me.baseCls, componentCls: me.componentCls, frame: me.frame }, me.renderData); }, //生命周期模板方法 onRender: function(parentNode, containerIdx) { var me = this, x = me.x, y = me.y, lastBox, width, height, el = me.el, body = Ext.getBody().dom; if (Ext.scopeResetCSS && !me.ownerCt) { if (el.dom === body) { el.parent().addCls(Ext.resetCls); } else { // Floaters rendered into the body can all be bumped into the common reset element if (me.floating && me.el.dom.parentNode === body) { Ext.resetElement.appendChild(me.el); } else { me.resetEl = el.wrap(Ext.resetElementSpec, false, Ext.supports.CSS3LinearGradient ? undefined : '*'); } } } me.applyRenderSelectors(); //处理子元素的引用,包括renderSelectors、childEls //设置组件状态为已渲染 delete me.rendering; me.rendered = true; //记住最初的尺寸 lastBox = null; if (x !== undefined) { lastBox = lastBox || {}; lastBox.x = x; } if (y !== undefined) { lastBox = lastBox || {}; lastBox.y = y; } if (!me.getFrameInfo() && Ext.isBorderBox) { width = me.width; height = me.height; if (typeof width == 'number') { lastBox = lastBox || {}; lastBox.width = width; } if (typeof height == 'number') { lastBox = lastBox || {}; lastBox.height = height; } } me.lastBox = me.el.lastBox = lastBox; }, //确保组件被附加到document.body内部,而不是detachedBody ensureAttachedToBody: function (runLayout) { var comp = this, body; while (comp.ownerCt) { comp = comp.ownerCt; } if (comp.container.isDetachedBody) { comp.container = body = Ext.resetElement; body.appendChild(comp.el.dom); if (runLayout) { comp.updateLayout(); } if (typeof comp.x == 'number' || typeof comp.y == 'number') { comp.setPosition(comp.x, comp.y); } } }, wrapPrimaryEl: function (dom) { this.el = Ext.get(dom, true); }, //初始化圆角外框 initFrame : function() { if (Ext.supports.CSS3BorderRadius || !this.frame) { return; } var me = this, frameInfo = me.getFrameInfo(), frameWidth, frameTpl, frameGenId, i, frameElNames = me.frameElNames, len = frameElNames.length, suffix; if (frameInfo) { frameWidth = frameInfo.maxWidth; frameTpl = me.getFrameTpl(frameInfo.table); me.frameGenId = frameGenId = (me.frameGenId || 0) + 1; frameGenId = me.id + '-frame' + frameGenId; frameTpl.insertFirst(me.el, Ext.apply({ $comp: me, fgid: frameGenId, ui: me.ui, uiCls: me.uiCls, frameCls: me.frameCls, baseCls: me.baseCls, frameWidth: frameWidth, top: !!frameInfo.top, left: !!frameInfo.left, right: !!frameInfo.right, bottom: !!frameInfo.bottom }, me.getFramePositions(frameInfo))); me.frameBody = me.el.down('.' + me.frameCls + '-mc'); me.removeChildEls(function (c) { return c.id && me.frameIdRegex.test(c.id); }); for (i = 0; i < len; i++) { suffix = frameElNames[i]; me['frame' + suffix] = me.el.getById(frameGenId + suffix); } } }, //更新圆角外框 updateFrame: function() { if (Ext.supports.CSS3BorderRadius || !this.frame) { return; } var me = this, wasTable = this.frameSize && this.frameSize.table, oldFrameTL = this.frameTL, oldFrameBL = this.frameBL, oldFrameML = this.frameML, oldFrameMC = this.frameMC, newMCClassName; this.initFrame(); if (oldFrameMC) { if (me.frame) { newMCClassName = this.frameMC.dom.className; oldFrameMC.insertAfter(this.frameMC); this.frameMC.remove(); this.frameBody = this.frameMC = oldFrameMC; oldFrameMC.dom.className = newMCClassName; if (wasTable) { me.el.query('> table')[1].remove(); } else { if (oldFrameTL) { oldFrameTL.remove(); } if (oldFrameBL) { oldFrameBL.remove(); } if (oldFrameML) { oldFrameML.remove(); } } } } else if (me.frame) { this.applyRenderSelectors(); } }, //得到组件圆角外框的信息 getFrameInfo: function() { //如果浏览器支持CSS3圆角边框,或者组件不使用外框,则返回,不去读取CSS样式 if (Ext.supports.CSS3BorderRadius || !this.frame) { return false; } var me = this, frameInfoCache = me.frameInfoCache, el = me.el || me.protoEl, cls = el.dom ? el.dom.className : el.classList.join(' '), frameInfo = frameInfoCache[cls],//缓存的外框信息 styleEl, left, top, info; //如果找不到缓存的外框信息 if (frameInfo == null) { //创建一个在屏幕可见范围外的承载样式的元素 styleEl = Ext.fly(me.getStyleProxy(cls), 'frame-style-el'); //设置背景图片偏移 left = styleEl.getStyle('background-position-x'); top = styleEl.getStyle('background-position-y'); if (!left && !top) { info = styleEl.getStyle('background-position').split(' '); left = info[0]; top = info[1]; } //计算样式 frameInfo = me.calculateFrame(left, top); if (frameInfo) { //确保不使用背景图片 el.setStyle('background-image', 'none'); } //加入缓存 frameInfoCache[cls] = frameInfo; } me.frame = !!frameInfo; me.frameSize = frameInfo; return frameInfo; }, //返回一个在屏幕可视范围外的基于cls样式的DIV getStyleProxy: function(cls) { var result = this.styleProxyEl || (Ext.AbstractComponent.prototype.styleProxyEl = Ext.resetElement.createChild({ style: { position: 'absolute', top: '-10000px' } }, null, true)); result.className = cls; return result; }, //计算组件外框样式 calculateFrame: function(left, top){ if (!(parseInt(left, 10) >= 1000000 && parseInt(top, 10) >= 1000000)) { return false; } var max = Math.max, tl = parseInt(left.substr(3, 2), 10), tr = parseInt(left.substr(5, 2), 10), br = parseInt(top.substr(3, 2), 10), bl = parseInt(top.substr(5, 2), 10), frameInfo = { table: left.substr(0, 3) == '110', vertical: top.substr(0, 3) == '110', top: max(tl, tr), right: max(tr, br), bottom: max(bl, br), left: max(tl, bl) }; frameInfo.maxWidth = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left); frameInfo.width = frameInfo.left + frameInfo.right; frameInfo.height = frameInfo.top + frameInfo.bottom; return frameInfo; }, //获取圆角外框的位置信息 getFramePositions: function(frameInfo) { var me = this, frameWidth = frameInfo.maxWidth, dock = me.dock, positions, tc, bc, ml, mr; if (frameInfo.vertical) { tc = '0 -' + (frameWidth * 0) + 'px'; bc = '0 -' + (frameWidth * 1) + 'px'; if (dock && dock == "right") { tc = 'right -' + (frameWidth * 0) + 'px'; bc = 'right -' + (frameWidth * 1) + 'px'; } positions = { tl: '0 -' + (frameWidth * 0) + 'px', tr: '0 -' + (frameWidth * 1) + 'px', bl: '0 -' + (frameWidth * 2) + 'px', br: '0 -' + (frameWidth * 3) + 'px', ml: '-' + (frameWidth * 1) + 'px 0', mr: 'right 0', tc: tc, bc: bc }; } else { ml = '-' + (frameWidth * 0) + 'px 0'; mr = 'right 0'; if (dock && dock == "bottom") { ml = 'left bottom'; mr = 'right bottom'; } positions = { tl: '0 -' + (frameWidth * 2) + 'px', tr: 'right -' + (frameWidth * 3) + 'px', bl: '0 -' + (frameWidth * 4) + 'px', br: 'right -' + (frameWidth * 5) + 'px', ml: ml, mr: mr, tc: '0 -' + (frameWidth * 0) + 'px', bc: '0 -' + (frameWidth * 1) + 'px' }; } return positions; }, //获取圆角外框的XTemplate getFrameTpl : function(table) { return this.getTpl(table ? 'frameTableTpl' : 'frameTpl'); }, frameInfoCache: {} }); |
私有类Ext.AbstractComponent为Sencha的RIA应用框架(例如Sencha Touch、ExtJS)提供了共享的功能,是ExtJS组件机制的基础。它提供了以下方面的功能:
- 规定了ExtJS组件的生命周期模型
- 提供了若干供扩展的模板方法,可以扩展ExtJS组件生命周期的各阶段,或者阻止默认生命周期行为的发生
- 规定了组件HTML内容的、基于模板的渲染机制
- 提供了各种设置组件UI样式的方法,包括CSS样式类、补白、边距、边框、可见性等
- 提供了组件布局机制
- 混入了:
- Ext.util.Renderable,保护组件渲染阶段的核心逻辑
- Ext.util.Observable,实现了观察者模式,是ExtJS事件机制的基础
AbstractComponent类包含以下成员:
配置项/属性/方法 | 说明 | ||||
{autoEl} |
String/Object,标签名或者DomHelper元素规格对象,用来创建表示该组件的封装元素(encapsulating element,即顶级元素)。该元素后续可以通过getEl()获得 示例:
|
||||
{contentEl} |
String,指定一个既有的HTML元素或者其id,作为当前组件的内容元素。用于把已有的DOM节点移动到组件的布局元素内部 可以用x-hidden、x-hide-display样式防止渲染前的闪烁 |
||||
{html} |
String/Object,HTML片段或者DomHelper元素规格对象,用作组件布局元素的内容。该HTML内容在组件被渲染后(render事件发布后)可用。在contentEl被插入前该HTML的内容被插入组件体 示例:
|
||||
{autoRender} | Boolean/String/HTMLElement/Ext.Element,可以控制非浮动组件的自动渲染(在第一次show被调用时进行渲染),替代renderTo配置。对于浮动组件该配置无效,总是true | ||||
{renderTo} | String/HTMLElement/Ext.Element,指定组件需要渲染到的HTML元素 | ||||
{autoShow} | Boolean,如果为真,组件创建后立即显示。仅用于使用autoRender的组件、浮动组件。对于浮动组件,如果该选项为false,即使作为某个容器的子组件,也不会自动渲染。 | ||||
{baseCls} | String,应用到组件封装元素的样式类,默认“x-component” | ||||
{cls} | String,添加到组件封装元素的额外样式类 | ||||
{componentCls} | String,添加到组件根元素的样式类 | ||||
{disabledCls} | String,组件被禁用时的样式 | ||||
{overCls} | String,鼠标悬停组件时的额外样式类 | ||||
{styleHtmlCls} | String,styleHtmlContent配置为true时,用于指定内容目标(content target)的样式 | ||||
{border} | Number/String/Boolean,指定组件边框大小,支持类似10 5 3 10的CSS格式 | ||||
{margin} | Number/String/Boolean,指定组件外边距的大小,支持类似10 5 3 10的CSS格式 | ||||
{padding} | Number/String/Boolean,指定组件内补白的大小,支持类似10 5 3 10的CSS格式 | ||||
{childEls} | Object[],描述组件子元素(不是子组件)的数组,数组的每一个元素是包含3个属性的对象:
如果数组元素是字符串而不是对象,等价于:{ name: m, itemId: m } 举例:
|
||||
{componentLayout} |
String/Object,组件布局。组件内部元素的大小、位置是由组件的布局管理器来负责的,当组件本身的大小发生改变时,组件布局管理器更新这些元素的大小和位置。 通常不需要初始化组件实例时指定该配置,具体组件类通常内置了组件布局方式 Ext.Component提供的默认组件布局,仅仅是将组件封装元素的宽高通过setSize()进行复制 |
||||
{data} |
Object,用于更新组件内容区域(content area)的tpl模板的初始上下文。要使用创建组件时的配置对象作为上下文,可以:
|
||||
{tpl} |
Ext.XTemplate/Ext.Template/String/String[],与data、tplWriteMode联用,更新组件的内容区域 如果不同时指定data,导致tpl中的内容不渲染,示例:
使用tpl时,可利用的强大特性是模板中的占位符可以动态的被替换:
上面的语句导致HTML内容变为 <span>Hello Alex Wong</span> |
||||
{tplWriteMode} |
String,更新组件内容区域(content area)使用Ext.(X)Template的哪个函数?默认overwrite |
||||
{renderData} |
Object,用于renderTpl模板的上下文,包含以下内置属性:id、ui、uiCls、baseCls、componentCls、frame |
||||
{renderTpl} |
Ext.XTemplate/String/String[],一个XTemplate,用于创建组件封装元素的内部结构。 对于Ext.Component、Ext.container.Container,通常不需要指定该配置,如果该配置为null表示不渲染内部结构(getEl的得到的是空的没有子元素的元素) |
||||
{renderSelectors} |
Object,包含DomQuery选择器的、用于识别组件子元素的对象。当组件内部结构通过renderTpl渲染后,该对象被遍历,找到的元素被作为当前组件的属性,该配置的功能与childEls类似,但是更加灵活,举例:
|
||||
{disabled} |
Boolean,用于禁用组件 |
||||
{draggable} |
Boolean,用于支持组件的拖拽 |
||||
{floating} |
Boolean,用于指示组件为浮动,即独立出文档布局并使用绝对定位。该属性为true的组件称为浮动组件。 浮动组件的z-index由某个ZIndexManager的实例管理:
|
||||
{frame} |
Boolean,如果指定为true,在渲染阶段自动诸如一个外框元素到组件外围,提供一个圆角矩形框的视觉效果。仅仅用于诸如IE9-的遗留浏览器 |
||||
{hidden} | Boolean,组件是否是隐藏的 | ||||
{height} | Number,组件的高度,像素 | ||||
{maxHeight} | Number,组件允许的最大高度 | ||||
{minHeight} | Number,组件允许的最小高度 | ||||
{width} | Number,组件的宽度,像素 | ||||
{maxWidth} | Number,组件允许的最大宽度 | ||||
{minWidth} | Number,组件允许的最小宽度 | ||||
{hideMode} |
String,隐藏的方式:
|
||||
{id} |
String,组件的唯一标识 |
||||
{itemId} |
String,条目标识,可以在不使用id的情况下用于获取组件(通过Container.getComponent),该属性在组件兄弟组件之间保持唯一性 |
||||
{loader} |
Ext.ComponentLoader/Object,用于远程加载组件内容的加载器对象 |
||||
{plugins} |
Object/Object[],使用的组件插件,插件用于提供定制的功能。对于插件只有一个要求:存在一个init方法,接受当前组件作为其参数 |
||||
{shrinkWrap} |
Boolean/Number,各值的意义:
|
||||
{style} | String/Object,设置组件元素的样式,参数必须满足Ext.Element.applyStyles()的参数要求 | ||||
{styleHtmlContent} | Boolean,如果设置为true,自动为组件内容目标(content target,对于panel来说是body)添加样式, | ||||
{ui} | String,用于定制ExtJS组件的主题 | ||||
{xtype} | Sring,组件的别名,用于延迟创建 | ||||
_isLayoutRoot | protected Boolean,设置该值为true,可以导致isLayoutRoot()返回true | ||||
autoGenId | private Boolean,指示组件标识是自动生成而不是手工指定的 | ||||
componentLayoutCounter | private Number,该组件的组件布局被调用的次数 | ||||
draggable | readonly Boolean,是否可拖拽 | ||||
frameSize | readonly Object,圆角外框的尺寸,参frame配置 | ||||
isComponent | Boolean,用于Duck类型识别,判断一个对象是否组件 | ||||
maskOnDisable | Boolean,当禁用时,是否显示一个半透明遮罩。FieldContainer, FieldSet, Field, Button, Tab等组件覆盖此标记,以提供定制的禁用逻辑 | ||||
ownerCt | readonly Ext.Container,组件所在容器 | ||||
rendered | readonly Boolean,指示组件是否已经渲染 | ||||
addCls() | Ext.Component ( String/String[] cls ),添加样式到组件的顶级元素 | ||||
removeCls | Ext.Component ( String/String[] cls ) ,从顶级元素移除样式 | ||||
addClsWithUI() | void ( String/String[] classes, Object skip ),添加样式到uiCls数组,该方法会同时调用addUIClsToElement把样式应用到组件所有元素 | ||||
removeClsWithUI | void ( String/String[] cls ) | ||||
addFocusListener() | private,如果元素具有focusEl,则添加focus监听器 | ||||
addPropertyToState() | Boolean ( Object state, String propName, [String value] ),如果属性值与默认/配置值不一样,将其存放到状态对象state中 | ||||
constructPlugins( ) | private,返回完整创建的插件实例数组 | ||||
convertPosition() | privaet void( Object pos, Object withUnits ),把数字形式的位置添加px后缀 | ||||
destroy() | 销毁组件 | ||||
disable() | void ( [Boolean silent] ),禁用组件,参数用于避免disable事件的触发 | ||||
doComponentLayout() | Ext.container.Container(),执行组件布局,当组件内容改变,需要调整子元素大小位置时,应当调用该方法,返回this | ||||
enable() | void ( [Boolean silent] ),启用组件,参数用于避免enable事件的触发 | ||||
getBubbleTarget( ) | Ext.container.Container(),返回事件冒泡的目标 | ||||
getEl() | Ext.dom.Element(),返回组件的顶层元素 | ||||
getFocusEl() | private,获取该组件用于持有焦点的元素,默认返回null | ||||
getHeight( ) | Number(),获取元素的高度,类似的方法包括getWidth()、getSize( ) | ||||
getId( ) | String(),获取组件ID | ||||
getItemId( ) | String(),获取分配给组件的itemId, 如果没有返回id | ||||
getPlugin() | Ext.AbstractPlugin (String pluginId),根据id获取插件对象 | ||||
getSizeModel() | Object ( Object ownerCtSizeModel ) 返回一个用于描述组件宽高如何被管理的SizeModel对象 | ||||
getState( ) | Object(),获取组件状态对象,默认实现返回flex, anchor, width, height, collapsed等尺寸相关的属性 | ||||
getTargetEl() | private,用于确定在何处插入html、contentEl、items | ||||
hasCls() | Boolean (String cls),判断指定的CSS样式类是否存在于组件的元素 | ||||
hasUICls() | Boolean (String cls),判断当前是否应用了指定的uiCls | ||||
isDisabled( ) | 组件是否被禁用 | ||||
isDraggable( ) | 组件是否可以作为拖拽的源 | ||||
isDroppable( ) | 组件是否可以作为拖拽的目标 | ||||
isFloating( ) | 是否浮动组件 | ||||
isHidden( ) | 组件是否被隐藏 | ||||
isLayoutRoot( ) | protected Boolean(),组件是否是布局的根。如果组件可以在没有父容器的配合/不对父容器产生影响的情况下执行布局,则可以返回true | ||||
isLayoutSuspended( ) | 当前组件的布局是否被暂停 | ||||
isVisible() | 组件是否可见 | ||||
isXType | Boolean ( String xtype, [Boolean shallow] ),判断当前组件是否是指定xtype的实例,如果shallow=true,则必须精确匹配,否则当前组件可以是xtype的子类型 | ||||
on() | addListener()的别名 | ||||
registerFloatingItem | void ( Object cmp ),由Component.doAutoRender方法,使用当前组件的ZIndexManager管理cmp | ||||
setBorder() | void ( String/Number border ),设置边框 | ||||
setDisabled() | void ( Boolean disabled ),设置禁用状态 | ||||
setDocked() | Ext.Component( Object dock, [Boolean layoutParent] ) ,设置当前组件在其父容器的停靠位置,仅当该组件是父容器dockedItems的一部分时有效 | ||||
setHeight() | Ext.Component( Number height ),设置高度,类似还有setWidth()、setSize() | ||||
setPosition | Ext.Component( Number left, Number top, [Boolean/Object animate] ),设置组件的位置 | ||||
setUI() | 设置使用的主题样式 | ||||
setVisible() | 设置组件可见性 | ||||
update() |
void ( String/Object htmlOrData, [Boolean loadScripts], [Function callback] ),更新组件的内容区域 可以导致组件内容模板中的占位符自动更新 |
||||
updateLayout() | void ( Object options ),更新组件的布局,如果该操作影响ownerCt,则转调ownerCt的updateLayout(),否则仅仅调用该组件的updateLayout()本身 | ||||
静态方法 | |||||
flushLayouts( ) | 执行所有因为 suspendLayouts而暂停的布局 | ||||
resumeLayouts() | void ( [Boolean flush] ),恢复整个框架的布局活动 | ||||
suspendLayouts( ) | 暂停整个框架的布局活动 | ||||
updateLayout() | void ( Ext.Component comp, [Boolean defer] ),更新某个组件的布局 | ||||
组件查询方法 | |||||
is() | Boolean ( String selector ),检测当前组件是否与选择器匹配 | ||||
isDescendantOf() | Boolean ( Ext.Container container ) ,检测当前组件是否为指定容器的后代 | ||||
nextNode() | Ext.Component ( [String selector] ) ,获取组件树结构中的下一个(匹配选择器的)组件(按树遍历顺序) | ||||
nextSibling() | Ext.Component ( [String selector] ),获取组件的下一个(匹配选择器的)兄弟组件。别名:next | ||||
previousNode() | Ext.Component ( [String selector] ),获取组件树结构中的上一个(匹配选择器的)组件(按树遍历顺序) | ||||
previousSibling() | Ext.Component ( [String selector] ),获取组件的上一个(匹配选择器的)兄弟组件。别名:prev | ||||
up() | Ext.container.Container( [String selector] ),根据选择器向上查询容器 | ||||
模板方法 | |||||
afterComponentLayout() | void ( Number width, Number height, Number oldWidth, Number oldHeight ),当组件布局被执行后,由布局系统调用 | ||||
afterSetPosition() | void ( Number x, Number y ),当组件的位置被设置了,该方法被调用 | ||||
beforeComponentLayout() | Boolean ( Number adjWidth, Number adjHeight ),当组件布局被执行前调用,返回false导致不进行组件布局 | ||||
beforeDestroy() | 在组件被销毁前调用 | ||||
beforeSetPosition() | private void ( Object x, Object y, Object animate ),在设置组件位置前调用 | ||||
onAdded() | void ( Ext.container.Container container, Number pos ),该方法用于感知组件被加入的容器,会触发一个add事件。对父容器的引用建立在添加阶段,而不是渲染阶段。在此方法被调用时,组件被置于父容器的items中 | ||||
onDisable( ) | 添加额外的禁用逻辑,在调用被覆盖版本后,组件禁用 | ||||
onEnable( ) | 添加额外的启用逻辑,在调用被覆盖版本后,组件启用 | ||||
onPosition() | void ( Number x, Number y ),组件被移动后调用,默认实现时空的 | ||||
onRemoved() | void ( Boolean destroying ),用于感知组件被从父容器中移除,会发布一个removed事件。在此方法被调用时,组件已经移出父容器的items,但是尚未destroy,调用覆盖版本后,ownerCt、refOwner 置空 | ||||
onResize( ) | 添加额外的改变大小逻辑 | ||||
⚡activate | void ( Ext.Component this, Object eOpts ),组件被视觉上激活后触发 | ||||
⚡added | void ( Ext.Component this, Ext.container.Container container, Number pos, Object eOpts ),组件被添加到容器后触发 | ||||
⚡afterrender | void ( Ext.Component this, Object eOpts ),在组件被渲染之后触发,此时afterRender()已经调用 | ||||
⚡beforeactivate | Boolean ( Ext.Component this, Object eOpts ),组件激活前触发,返回false阻止激活 | ||||
⚡beforedeactivate | Boolean ( Ext.Component this, Object eOpts ),组件去激活前触发,返回false阻止去激活 | ||||
⚡beforedestroy | Boolean ( Ext.Component this, Object eOpts ),组件销毁前触发,返回false阻止销毁 | ||||
⚡beforehide | Boolean ( Ext.Component this, Object eOpts ),组件渲染前触发,返回false阻止渲染 | ||||
⚡beforeshow | Boolean ( Ext.Component this, Object eOpts ),组件显示前触发,返回false阻止显示 | ||||
⚡blur | void( Ext.Component this, Ext.EventObject The, Object eOpts ),组件失去焦点时触发 | ||||
⚡boxready | void( Ext.Component this, Number width, Number height, Object eOpts ),组件在第一次以初始尺寸布局后发布一次 | ||||
⚡deactivate | void ( Ext.Component this, Object eOpts ),组件在视觉上去激活时触发 | ||||
⚡destroy | void ( Ext.Component this, Object eOpts ),组件被销毁后触发 | ||||
⚡disable | void ( Ext.Component this, Object eOpts ),组件禁用后触发 | ||||
⚡enable | void ( Ext.Component this, Object eOpts ),组件启用后触发 | ||||
⚡focus | void ( Ext.Component this, Ext.EventObject The, Object eOpts ) 组件接收到焦点后触发 | ||||
⚡hide | void ( Ext.Component this, Object eOpts ),组件被隐藏后触发 | ||||
⚡move | void ( Ext.Component this, Number x, Number y, Object eOpts ),组件的位置改变后触发 | ||||
⚡removed | void ( Ext.Component this, Ext.container.Container ownerCt, Object eOpts ),组件从容器中移除后触发 | ||||
⚡render | void ( Ext.Component this, Object eOpts ),当组件的HTML标记被渲染到DOM后触发,注意组件不一定展示(show) | ||||
⚡resize | void ( Ext.Component this, Number width, Number height, Number oldWidth, Number oldHeight, Object eOpts ),当组件的大小被改变时触发。注意:组件第一次使用其初始大小布局后,不会触发该事件,此情况可以使用boxready代替该事件 | ||||
⚡show | void ( Ext.Component this, Object eOpts ),当调用show()方法后,当前组件展示,该事件被触发 |
Ext.AbstractComponent的源代码分析如下:
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 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 |
Ext.define('Ext.AbstractComponent', { statics: { AUTO_ID: 1000, pendingLayouts: null, layoutSuspendCount: 0, cancelLayout: function(comp, isDestroying) { //context为布局上下文,Ext.layout.Context的实例 var context = this.runningLayoutContext || this.pendingLayouts; if (context) { context.cancelComponent(comp, false, isDestroying); } }, flushLayouts: function () { var me = this, context = me.pendingLayouts; //invalidQueue是布局失效的顶层布局组件的队列,队列里的组件不会存在具有父子/前后代关系的组件 if (context && context.invalidQueue.length) { me.pendingLayouts = null; me.runningLayoutContext = context; //正在执行的布局上下文 //覆盖上下文的刷新布局方法 Ext.override(context, { runComplete: function () { //在通过runComplete调用finishedLayout()前必须清空布局队列 //因为finishedLayout()会调用afterComponentLayout,而可能导致重新进入doLayout或doComponentLayout me.runningLayoutContext = null; return this.callParent(); } }); //执行布局计算,此方法只能在布局上下文上调用一次 context.run(); } }, //递减暂停布局计数器,如果为0,则刷新布局 resumeLayouts: function (flush) { if (this.layoutSuspendCount && ! --this.layoutSuspendCount) { if (flush) { this.flushLayouts(); } } }, //递增暂停布局计数器 suspendLayouts: function () { ++this.layoutSuspendCount; }, //更新组件的布局 updateLayout: function (comp, defer) { var me = this, running = me.runningLayoutContext, pending; //在running或者pending布局的下一次周期中排队 if (running) { running.queueInvalidate(comp); } else { pending = me.pendingLayouts || (me.pendingLayouts = new Ext.layout.Context()); pending.queueInvalidate(comp); if (!defer && !me.layoutSuspendCount && !comp.isLayoutSuspended()) { me.flushLayouts(); } } } }, isComponent: true, getAutoId: function() { this.autoGenId = true; return ++Ext.AbstractComponent.AUTO_ID; }, deferLayouts: false, autoGenId: false, //默认的渲染模板 renderTpl: '{%this.renderContent(out,values)%}', frameSize: { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 }, tplWriteMode: 'overwrite', baseCls: Ext.baseCSSPrefix + 'component', disabledCls: Ext.baseCSSPrefix + 'item-disabled', ui: 'default', uiCls: [], hidden: false, disabled: false, draggable: false, floating: false, hideMode: 'display', styleHtmlContent: false, styleHtmlCls: Ext.baseCSSPrefix + 'html', autoShow: false, autoRender: false, // @private allowDomMove: true, rendered: false, componentLayoutCounter: 0, shrinkWrap: 2, weight: 0, maskOnDisable: true, _isLayoutRoot: false, //组件构造器,规定了组件的生命周期 constructor : function(config) { var me = this, i, len, xhooks; //配置项作为属性,应用到当前对象 if (config) { Ext.apply(me, config); //xhooks用于动态的覆盖一个实例的成员,而不是类的 xhooks = me.xhooks; if (xhooks) { delete me.xhooks; Ext.override(me, xhooks); } } else { config = {}; } //初始配置 me.initialConfig = config; //构造混入 me.mixins.elementCt.constructor.call(me); me.addEvents( 'beforeactivate', 'activate', 'beforedeactivate', 'deactivate', 'added', 'disable', 'enable', 'beforeshow', 'show', 'beforehide', 'hide', 'removed', 'beforerender', 'render', 'afterrender', 'boxready', 'beforedestroy', 'destroy', 'resize', 'move', 'focus', 'blur' ); //触发以生成ID me.getId(); //生成protoEl属性,Ext.util.ProtoElement用于在渲染前管理Element的属性,例如class和style me.setupProtoEl(); if (me.cls) { me.initialCls = me.cls; me.protoEl.addCls(me.cls); } if (me.style) { me.initialStyle = me.style; me.protoEl.setStyle(me.style); } //受管的监听器列表 me.mons = []; me.renderData = me.renderData || {}; me.renderSelectors = me.renderSelectors || {}; //如果配置了插件,则在此执行所有插件的构造 if (me.plugins) { me.plugins = me.constructPlugins(); } if (!me.hasListeners) { me.hasListeners = new me.HasListeners(); } //生命周期模板方法:初始化组件 me.initComponent(); //在组件管理器中注册自己 Ext.ComponentManager.register(me); //构造混入类 me.mixins.observable.constructor.call(me); me.mixins.state.constructor.call(me, config); //resize作为状态事件,resize将导致组件状态被保存 this.addStateEvents('resize'); //如果配置了插件,则在此依次初始化插件 if (me.plugins) { for (i = 0, len = me.plugins.length; i < len; i++) { me.plugins[i] = me.initPlugin(me.plugins[i]); } } //初始化用于远程加载组件内容的加载器 me.loader = me.getLoader(); //生命周期的第二阶段:渲染 //容器中的子组件,其渲染阶段被父容器控制 //如果配置了renderTo,那么立即执行渲染 if (me.renderTo) { me.render(me.renderTo); } //如果配置了autoShow,并且不处于容器内,那么立即执行展示 if (me.autoShow && !me.isContained) { me.show(); } if (Ext.isDefined(me.disabledClass)) { if (Ext.isDefined(Ext.global.console)) { Ext.global.console.warn('Ext.Component: disabledClass has been deprecated. Please use disabledCls.'); } me.disabledCls = me.disabledClass; delete me.disabledClass; } }, initComponent: function () { //再一次调用了constructPlugins(),可以让子类在自己的initComponent()里面添加新的插件配置 this.plugins = this.constructPlugins(); //设置组件的初始大小 this.setSize(this.width, this.height); }, //获取组件的状态,例如大小、位置等信息,子类可以扩展以添加任何定制的、需要持久化的信息 getState: function() { var me = this, state = null, sizeModel = me.getSizeModel(); if (sizeModel.width.configured) { state = me.addPropertyToState(state, 'width'); } if (sizeModel.height.configured) { state = me.addPropertyToState(state, 'height'); } return state; }, //把某个属性添加到状态中 addPropertyToState: function (state, propName, value) { var me = this, len = arguments.length; if (len == 3 || me.hasOwnProperty(propName)) { if (len < 3) { value = me[propName]; } if (value !== me.initialConfig[propName]) { (state || (state = {}))[propName] = value; } } return state; }, //如何展示组件,缺省适配 show: Ext.emptyFn, //生命周期模板方法:隐藏组件时 //缺省实现:更新布局 onHide: function() { this.updateLayout({ isRoot: false }); }, //生命周期模板方法:显示组件时 //缺省实现:更新布局 onShow : function() { this.updateLayout({ isRoot: false }); }, //构造一个插件,如果已经构造,简单的返回查询对象 constructPlugin: function(plugin) { if (plugin.ptype && typeof plugin.init != 'function') { plugin.cmp = this; plugin = Ext.PluginManager.create(plugin); } else if (typeof plugin == 'string') { plugin = Ext.PluginManager.create({ ptype: plugin, cmp: this }); } return plugin; }, //构造所有配置的插件 constructPlugins: function() { var me = this, plugins, result = [], i, len; if (me.plugins) { plugins = Ext.isArray(me.plugins) ? me.plugins : [ me.plugins ]; for (i = 0, len = plugins.length; i < len; i++) { result[i] = me.constructPlugin(plugins[i]); } return result; } }, // @private //初始化插件,就是调用插件的init方法并入参当前组件 initPlugin : function(plugin) { plugin.init(this); return plugin; }, updateAria: Ext.emptyFn, //使用当前组件作为参数组件的浮动管理器 registerFloatingItem: function(cmp) { var me = this; if (!me.floatingDescendants) { me.floatingDescendants = new Ext.ZIndexManager(me); } me.floatingDescendants.register(cmp); }, unregisterFloatingItem: function(cmp) { var me = this; if (me.floatingDescendants) { me.floatingDescendants.unregister(cmp); } }, //布局暂停计数器 layoutSuspendCount: 0, //暂停当前组件的布局 suspendLayouts: function () { var me = this; if (!me.rendered) { //如果当前组件尚未渲染,不做任何处理 return; } if (++me.layoutSuspendCount == 1) { me.suspendLayout = true; } }, //恢复当前组件的布局 resumeLayouts: function (flushOptions) { var me = this; if (!me.rendered) { //如果当前组件尚未渲染,不做任何处理 return; } if (! --me.layoutSuspendCount) { //如果计数器为0,那么需要更新当前组件的布局 me.suspendLayout = false; if (flushOptions && !me.isLayoutSuspended()) { me.updateLayout(flushOptions); } } }, //初始化事件的私有方法 initEvents : function() { var me = this, afterRenderEvents = me.afterRenderEvents, el, //注意这个闭包变量 ➀ property, //为元素添加受管的监听器 fn = function(listeners){ //回调函数立即使用闭包变量的实时值 ➃ me.mon(el, listeners); }; //渲染后事件映射:{元素属性:[监听器1,监听器2]} if (afterRenderEvents) { for (property in afterRenderEvents) { if (afterRenderEvents.hasOwnProperty(property)) { el = me[property]; //闭包变量的值改变 ➁ if (el && el.on) { //执行回调函数 ➂ Ext.each(afterRenderEvents[property], fn); } } } } me.addFocusListener(); }, //添加焦点监听器 addFocusListener: function() { var me = this, focusEl = me.getFocusEl(),//得到当前组件的焦点元素 needsTabIndex; //不仅仅是表单,所有容器均可能是支持获取焦点,例如面板、工具栏、窗口 //通常,作为焦点元素的DIV元素无法接收焦点,但如果调用了FocusManager,则其非缺省的导航处理器(navigation handlers ) //将明确的使面板、容器或者FieldSet获取焦点 //如果当前组件具有焦点元素,可能需要为其添加focus、blur事件: if (focusEl) { // getFocusEl()方法可能返回一个组件,如果作为容器的this希望把焦点管理代理给它的子代组件 // Window组件可以通过配置defaultFocus为它的一个按钮来实现这种代理 if (focusEl.isComponent) { return focusEl.addFocusListener(); //代理 } //如果焦点元素本身就是可聚焦的(focusable),那么总是需要添加focus监听器处理组件的聚焦 //如果焦点元素是不可聚集的,除非启用了FocusManager,否则不需要添加focus监听器 needsTabIndex = focusEl.needsTabIndex(); if (!me.focusListenerAdded && (!needsTabIndex || Ext.FocusManager.enabled)) { if (needsTabIndex) { focusEl.dom.tabIndex = -1; } focusEl.on({ focus: me.onFocus, blur: me.onBlur, scope: me }); me.focusListenerAdded = true; } } }, //获取当前组件关联的焦点元素,缺省适配 getFocusEl: Ext.emptyFn, //是否可聚焦 isFocusable: function(c) { var me = this, focusEl; if ((me.focusable !== false) && (focusEl = me.getFocusEl()) && me.rendered && !me.destroying && !me.isDestroyed && !me.disabled && me.isVisible(true)) { if (focusEl.isComponent) { return focusEl.isFocusable(); } return focusEl && focusEl.dom && focusEl.isVisible(); } }, //聚焦前模板方法 preFocus: Ext.emptyFn, //聚焦时模板方法 onFocus: function(e) { var me = this, focusCls = me.focusCls, focusEl = me.getFocusEl(); if (!me.disabled) { me.preFocus(e); if (focusCls && focusEl) { focusEl.addCls(me.addClsWithUI(focusCls, true));//添加聚焦CSS样式 } if (!me.hasFocus) { me.hasFocus = true; me.fireEvent('focus', me, e); //发布聚焦事件 } } }, //失焦前模板方法 beforeBlur : Ext.emptyFn, //失焦时模板方法 onBlur : function(e) { var me = this, focusCls = me.focusCls, focusEl = me.getFocusEl(); if (me.destroying) { return; } me.beforeBlur(e); if (focusCls && focusEl) { focusEl.removeCls(me.removeClsWithUI(focusCls, true));//移除聚焦CSS样式 } if (me.validateOnBlur) { me.validate(); //执行验证 } me.hasFocus = false; me.fireEvent('blur', me, e); //发布失焦事件 me.postBlur(e); }, //失焦后模板方法 postBlur : Ext.emptyFn, getId : function() { return this.id || (this.id = 'ext-comp-' + (this.getAutoId())); }, //得到当前组件的itemId,如果没有,则返回id getItemId : function() { return this.itemId || this.id; }, //得到当前组件的封装(顶级)元素 getEl : function() { return this.el; }, //用于确定html、contentEl、items等HTML插入到哪边 getTargetEl: function() { return this.frameBody || this.el;//如果具有frameBody,则frameBody作为targetEl,否则使用顶级元素 }, /** * 更新当前组件的内容区域(即targetEl) * @param {String/Object} htmlOrData 如果组件配置了tpl模板,则使用该参数作为上下文填充模板,否则作为HTML更新内如区域 * @param {Boolean} [loadScripts=false] 仅当使用html配置时合法 * @param {Function} [callback] 仅当使用html配置时合法,加载脚本完毕后的回调 */ update : function(htmlOrData, loadScripts, cb) { var me = this; if (me.tpl && !Ext.isString(htmlOrData)) { me.data = htmlOrData; if (me.rendered) { //填充模板,并覆盖targetEl的innerHTML me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {}); } } else { me.html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData; if (me.rendered) { //更新元素的innerHTML me.getTargetEl().update(me.html, loadScripts, cb); } } if (me.rendered) { //更新组件的布局 me.updateLayout(); } }, //模板方法:第一次使用初始大小渲染后 onBoxReady: function(){ var me = this; if (me.disableOnBoxReady) { me.onDisable(); } else if (me.enableOnBoxReady) { me.onEnable(); } //初始化:可改变大小 if (me.resizable) { me.initResizable(me.resizable); } //初始化:可拖拽。必须在可改变大小之后 if (me.draggable) { me.initDraggable(); } }, //启用组件 enable: function(silent) { var me = this; delete me.disableOnBoxReady; me.removeCls(me.disabledCls); if (me.rendered) { me.onEnable(); } else { me.enableOnBoxReady = true; } me.disabled = false; delete me.resetDisable; //silent用于阻止事件触发 if (silent !== true) { me.fireEvent('enable', me); } return me; }, //禁用组件 disable: function(silent) { var me = this; delete me.enableOnBoxReady; me.addCls(me.disabledCls); if (me.rendered) { me.onDisable(); } else { me.disableOnBoxReady = true; } me.disabled = true; //silent用于阻止事件触发 if (silent !== true) { delete me.resetDisable; me.fireEvent('disable', me); } return me; }, //模板方法:在启用后执行 //缺省实现:去除遮罩 onEnable: function() { if (this.maskOnDisable) { this.el.dom.disabled = false; this.unmask(); } }, //模板方法:在禁用后执行 onDisable : function() { var me = this, focusCls = me.focusCls, focusEl = me.getFocusEl(); //清除聚焦 if (focusCls && focusEl) { focusEl.removeCls(me.removeClsWithUI(focusCls, true)); } //添加遮罩 if (me.maskOnDisable) { me.el.dom.disabled = true; me.mask(); } }, //添加遮罩 mask: function() { var box = this.lastBox, target = this.getMaskTarget(), args = []; if (box) { args[2] = box.height; } target.mask.apply(target, args); }, //清除遮罩 unmask: function() { this.getMaskTarget().unmask(); }, //遮罩的目标元素 getMaskTarget: function(){ return this.el; }, //添加监听器 addListener : function(element, listeners, scope, options) { var me = this, fn, option; if (Ext.isString(element) && (Ext.isObject(listeners) || options && options.element)) { if (options.element) { fn = listeners; listeners = {}; listeners[element] = fn; element = options.element; if (scope) { listeners.scope = scope; } for (option in options) { if (options.hasOwnProperty(option)) { if (me.eventOptionsRe.test(option)) { listeners[option] = options[option]; } } } } if (me[element] && me[element].on) { me.mon(me[element], listeners); } else { me.afterRenderEvents = me.afterRenderEvents || {}; if (!me.afterRenderEvents[element]) { me.afterRenderEvents[element] = []; } me.afterRenderEvents[element].push(listeners); } } return me.mixins.observable.addListener.apply(me, arguments); }, //移除受管的事件监听器 removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){ var me = this, element = managedListener.options ? managedListener.options.element : null; if (element) { element = me[element]; if (element && element.un) { if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) { element.un(managedListener.ename, managedListener.fn, managedListener.scope); if (!isClear) { Ext.Array.remove(me.managedListeners, managedListener); } } } } else { return me.mixins.observable.removeManagedListenerItem.apply(me, arguments); } }, //模板方法:被添加到容器后 //缺省实现:发布added事件 onAdded : function(container, pos) { var me = this; me.ownerCt = container; if (me.hasListeners.added) { me.fireEvent('added', me, container, pos); } }, //模板方法:被从容器中移除后 //缺省实现:发布removed事件 onRemoved : function(destroying) { var me = this; if (me.hasListeners.removed) { me.fireEvent('removed', me, me.ownerCt); } delete me.ownerCt; //删除父容器属性 delete me.ownerLayout; //删除父布局属性 }, //生命周期模板方法:在销毁前的动作 //返回false可以取消销毁 beforeDestroy : Ext.emptyFn, //模板方法,当修改了组件大小后 onResize : Ext.emptyFn, //设置组件大小 setSize : function(width, height) { var me = this; if (width && typeof width == 'object') { height = width.height; width = width.width; } if (typeof width == 'number') { me.width = Ext.Number.constrain(width, me.minWidth, me.maxWidth); } else if (width === null) { delete me.width; } if (typeof height == 'number') { me.height = Ext.Number.constrain(height, me.minHeight, me.maxHeight); } else if (height === null) { delete me.height; } //如果尚未渲染,仅仅设置属性即可,否则: if (me.rendered && me.isVisible()) { //如果改变了当前组件的大小,那么就不能作为布局的根,因为对父容器会产生影响 //更新布局 me.updateLayout({ isRoot: false }); } return me; }, //判断当前组件是否布局的根 isLayoutRoot: function() { var me = this, ownerLayout = me.ownerLayout; //对于浮动组件,或者明确设置了_isLayoutRoot的组件,返回true if (!ownerLayout || me._isLayoutRoot || me.floating) { return true; } //让父布局判断当前组件是否布局根 return ownerLayout.isItemLayoutRoot(me); }, //判断布局是否被暂停 //只要当前组件或者其任意布局祖代的布局被暂停,则返回true isLayoutSuspended: function () { var comp = this, ownerLayout; while (comp) { if (comp.layoutSuspendCount || comp.suspendLayout) { return true; } ownerLayout = comp.ownerLayout; if (!ownerLayout) { break; } comp = ownerLayout.owner; } return false; }, //更新当前组件的布局 updateLayout: function (options) { var me = this, defer, isRoot = options && options.isRoot; if (!me.rendered || me.layoutSuspendCount || me.suspendLayout) { return; } //对于隐藏组件,取消布局 if (me.hidden) { Ext.AbstractComponent.cancelLayout(me); } else if (typeof isRoot != 'boolean') { isRoot = me.isLayoutRoot(); } // 如果当前组件不是布局根,且父布局关心当前组件的尺寸变化,那么父布局会统一处理 if (isRoot || !me.ownerLayout || !me.ownerLayout.onContentChange(me)) { //否则,自行处理 if (!me.isLayoutSuspended()) { defer = (options && options.hasOwnProperty('defer')) ? options.defer : me.deferLayouts; Ext.AbstractComponent.updateLayout(me, defer); //调用静态函数执行布局 } } }, //执行组件布局,该方法开放调用,任何时候,改变组件内容均需要考虑调用 doComponentLayout : function() { this.updateLayout(); return this; }, //重设布局对象 setComponentLayout : function(layout) { var currentLayout = this.componentLayout;//当前布局是componentLayout属性 if (currentLayout && currentLayout.isLayout && currentLayout != layout) { currentLayout.setOwner(null); //取消当前布局对象的关联 } this.componentLayout = layout; layout.setOwner(this); //注意布局对象是被组件独占的 }, //获取当前的组件布局,如果不存在,则创建之 getComponentLayout : function() { var me = this; if (!me.componentLayout || !me.componentLayout.isLayout) { me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent')); } return me.componentLayout; }, //组件布局后调用的模板方法 //缺省实现 afterComponentLayout: function(width, height, oldWidth, oldHeight) { var me = this, floaters, len, i, floater; if (++me.componentLayoutCounter === 1) { me.afterFirstLayout(width, height);//调用第一次组件布局的模板方法 } //如果浮动子组件没有显示,立即显示 if (me.floatingItems) { floaters = me.floatingItems.items; len = floaters.length; for (i = 0; i < len; i++) { floater = floaters[i]; if (!floater.rendered && floater.autoShow) { floater.show(); } } } //如果大小发生改变,发布resize事件 if (me.hasListeners.resize && (width !== oldWidth || height !== oldHeight)) { me.fireEvent('resize', me, width, height, oldWidth, oldHeight); } }, //布局前执行的模板方法,返回false禁止布局 beforeComponentLayout: function(width, height) { return true; }, //设置组件的位置 setPosition : function(x, y, animate) { var me = this, pos = me.beforeSetPosition.apply(me, arguments);//设置位置前模板方法 if (pos && me.rendered) { pos = me.convertPosition(pos); if (pos.left !== me.el.getLeft() || pos.top !== me.el.getTop()) { if (animate) { //可选的动画 me.stopAnimation(); me.animate(Ext.apply({ duration: 1000, listeners: { afteranimate: Ext.Function.bind(me.afterSetPosition, me, [pos.left, pos.top]) }, to: pos }, animate)); } else { if (pos.left !== undefined && pos.top !== undefined) { me.el.setLeftTop(pos.left, pos.top); } else if (pos.left !== undefined) { me.el.setLeft(pos.left); } else if (pos.top !==undefined) { me.el.setTop(pos.top); } me.afterSetPosition(pos.left, pos.top); } } } return me; }, //设置位置前模板 beforeSetPosition: function (x, y, animate) { var pos, x0; if (!x || Ext.isNumber(x)) { pos = { x: x, y : y, anim: animate }; } else if (Ext.isNumber(x0 = x[0])) { pos = { x : x0, y : x[1], anim: y }; } else { pos = { x: x.x, y: x.y, anim: y }; } pos.hasX = Ext.isNumber(pos.x); pos.hasY = Ext.isNumber(pos.y); this.x = pos.x; this.y = pos.y; return (pos.hasX || pos.hasY) ? pos : null; }, //设置位置后模板 //缺省实现:如果支持,触发move事件 afterSetPosition: function(x, y) { var me = this; me.onPosition(x, y); if (me.hasListeners.move) { me.fireEvent('move', me, x, y); } }, //组件位置变化后执行的模板方法 onPosition: Ext.emptyFn, //设置宽高,就是在el元素上进行操作 setWidth : function(width) { return this.setSize(width); }, setHeight : function(height) { return this.setSize(undefined, height); }, getSize : function() { return this.el.getSize(); }, getWidth : function() { return this.el.getWidth(); }, getHeight : function() { return this.el.getHeight(); }, //设置当前组件在父容器的停靠位置 setDocked : function(dock, layoutParent) { var me = this; me.dock = dock; if (layoutParent && me.ownerCt && me.rendered) { me.ownerCt.updateLayout();//导致更新父容器的布局 } return me; }, //生命周期模板方法:销毁后执行 //缺省实现 onDestroy : function() { var me = this; if (me.monitorResize && Ext.EventManager.resizeEvent) { Ext.EventManager.resizeEvent.removeListener(me.setSize, me); } //销毁组件布局、遮罩、浮动后代 Ext.destroy( me.componentLayout, me.loadMask, me.floatingDescendants ); }, //销毁组件 destroy : function() { var me = this, selectors = me.renderSelectors, selector, el; if (!me.isDestroyed) { //如果beforedestroy事件处理返回false,则不销毁 if (!me.hasListeners.beforedestroy || me.fireEvent('beforedestroy', me) !== false) { me.destroying = true; me.beforeDestroy();//销毁前模板 //对于浮动组件 if (me.floating) { delete me.floatParent;//清除浮动父组件引用 //从ZIndexManager移除自己 if (me.zIndexManager) { me.zIndexManager.unregister(me); } } else if (me.ownerCt && me.ownerCt.remove) { me.ownerCt.remove(me, false); } me.onDestroy();//销毁后模板 Ext.destroy(me.plugins); //尝试销毁所有插件 //发布销毁事件 if (me.hasListeners.destroy) { me.fireEvent('destroy', me); } //接触组件注册 Ext.ComponentManager.unregister(me); //销毁组件状态 me.mixins.state.destroy.call(me); //清除组件监听器 me.clearListeners(); //确保在移除所有事件监听器后,清除所有对DOM元素的引用,避免内存泄漏 if (me.rendered) {//如果尚未渲染,任何DOM结构都未创建,无需处理 if (!me.preserveElOnDestroy) { me.el.remove(); //删除组件的顶级元素 } me.mixins.elementCt.destroy.call(me); //删除所有childEls if (selectors) {//如果配置了renderSelectors for (selector in selectors) { if (selectors.hasOwnProperty(selector)) { el = me[selector]; //对于所有作为成员变量的元素,需要解除关系 if (el) { delete me[selector]; el.remove(); } } } } delete me.el; //删除顶级元素 delete me.frameBody; //删除组件体 delete me.rendered; } me.destroying = false; me.isDestroyed = true; } } } }, /*当组件类被创建后,执行的回调,this指向新创建的类*/function() { var AbstractComponent = this; //方法别名 AbstractComponent.createAlias({ on: 'addListener', prev: 'previousSibling', next: 'nextSibling' }); //静态方法快捷方式 Ext.resumeLayouts = function (flush) { AbstractComponent.resumeLayouts(flush); }; Ext.suspendLayouts = function () { AbstractComponent.suspendLayouts(); }; Ext.batchLayouts = function(fn, scope) { AbstractComponent.suspendLayouts(); fn.call(scope); AbstractComponent.resumeLayouts(true); }; }); |
该类是ExtJS的所有组件的基类,保护:
- 基本的隐藏/显示逻辑
- 基本的启用/禁用逻辑
- 组件尺寸控制行为
在ExtJS中,每个组件类都具有一个称为xtype的别名,可以用于组件的延迟创建。Component类提供了一下成员:
配置项/属性/方法 | 说明 | ||
{autoScroll} | Boolean = false。如果设置为true,那么在组件布局元素上使用overflow : 'auto' 样式,这导致必要时出现滚动条;如果设置为false,会自动剪除溢出的内容,不显示滚动条。该配置不应该与overflowX、 overflowY一起使用 | ||
{columnWidth} | Number/String,在Column布局中,定义当前组件的列宽度,可以设置为数字或者百分比 | ||
{region} | String,在Border布局中用于指定该组件所在的位置:center,north,south,east,west被支持 | ||
{flex} | Number,在HBox、VBox等布局中用于指定组件所占据的尺寸比例 | ||
{draggable} | Boolean/Object = false,覆盖AbstractComponent。设置为true,可以让浮动组件可拖拽(其封装元素作为drag handle)。亦可提供一个传递给 ComponentDragger的配置以启动拖拽:
|
||
{resizable} | Boolean/Object,如果设置为true,组件渲染后为其添加一个Resizer对象。亦可指定Resizer构造器需要的配置对象 | ||
{floating} |
Boolean/Object = false,覆盖AbstractComponent。 如果指定为true,组件将脱离正常文档流,使用CSS绝对定位。Window、Menu等组件默认即是浮动的。通过编程方式手工render()的组件会在全局ZIndexManager(即Ext.WindowManager单例)中注册。 作为容器子组件的浮动组件 浮动组件会通过ownerCt链条来寻找ZIndexManager——依次上溯,直到找到一个本身是floating的父容器,浮动组件总是在它的浮动父容器上面显示,如果找不到floating的父容器,则使用全局ZIndexManager 如果配置为浮动组件,在渲染阶段会它寻找一个ZIndexManager来管理自己的z-index,以正确位置与其它浮动组件的层叠(Stack)关系。当浮动组件的toFront()方法被调用时,其ZIndexManager会把它放在最前面 浮动组件不参与容器的布局,因此它不会随着容器的渲染而渲染,需要手工调用show()才会进行渲染,在渲染后ownerCt属性被删除, floatParent属性被设置,执行其祖先floating容器或者undefined |
||
{formBind} | Boolean = false,如果组件位于FormPanel内,设置该配置为true导致组件的启用/禁用状态取决于表单的验证有效性 | ||
{overflowX} | String = 'hidden',支持auto、scroll、hidden | ||
{overflowY} | String = 'hidden',支持auto、scroll、hidden | ||
{resizeHandles} | String = 'all',Resizer的handles配置项,仅 resizable = true时有意义 | ||
{toFrontOnShow} | Boolean = true,如果设置为真,在浮动组件show()时,自动带到最前面 | ||
floatParent | readonly Ext.Container,仅对浮动组件有意义,手工render()的浮动组件没有floatParent | ||
zIndexManager | readonly Ext.ZIndexManager,仅对浮动组件有意义,渲染后被设置 | ||
zIndexParent | readonly Ext.Container, | ||
bubble() | Ext.Component ( Function fn, [Object scope], [Array args] )。向上(getBubbleTarget)传播执行一个方法,如果任何一次执行返回false,停止传播 | ||
cancelFocus() | 取消任何在当前组件上的延迟聚集 | ||
cloneConfig() | Ext.Component cloneConfig( Object overrides ) ,克隆当前组件,使用overrides 覆盖原来的配置项 | ||
focus() | Ext.Component ( [Boolean selectText], [Boolean/Number delay] ) ,尝试聚焦当前组件,返回聚焦的组件。可选的延迟聚焦 | ||
getBox() | Object ( [Boolean local] ),得到组件的度量对象,形式:{x, y, width, height} | ||
getPosition() | Number[] ( [Boolean local] ) ,返回组件的位置,如果local=true,返回的位置相对于元素的offsetParent | ||
hide() |
Ext.Component( [String/Ext.Element/Ext.Component animateTarget], [Function callback], [Object scope] ) 。隐藏组件,根据hideMode设置隐藏模式 |
||
scrollBy() |
void ( Number/Number[]/Object deltaX, Number/Boolean/Object deltaY, Boolean/Object animate ) 滚动组件的targetEl,可选动画 |
||
setLoading() | Ext.LoadMask( Boolean/Object/String load, [Boolean targetEl] )。显示一个LoadMask。如果targetEl=true,则遮罩targetEl而不是el,对于面板,targetEl对应面板体 | ||
setOverflowXY() | Ext.Component ( String overflowX, String overflowY )。在组件内容元素上设置overflow样式 | ||
setPagePosition() | Ext.Component ( Number x, Number y, [Boolean/Object animate] )。设置组件的page XY 位置 | ||
show() |
Ext.Component ( [String/Ext.Element animateTarget], [Function callback], [Object scope] ) 显示当前组件,如果floating或者autoRender为true,则先render()。 在show()之后,浮动组件z-index被带到最前面 |
||
showAt() |
void ( Number x, Number y, [Boolean/Object animate] ) 在指定位置显示组件,浮动组件的x,y相对于其ownerCt(例如Menu),该方法经常用来显示上下文菜单 |
||
updateBox() | Ext.Component ( Object box ),设置组件el的大小位置 | ||
组件查询方法 | |||
findParentBy() | Ext.container.Container ( Function fn )。根据过滤器函数查询第一个匹配的祖先容器 | ||
findParentByType() | Ext.container.Container( String/Ext.Class xtype ) 。根据类型查询第一个匹配的祖先容器 | ||
模板方法 | |||
afterHide() | void ( [Function callback], [Object scope] )。当组件隐藏后调用 | ||
afterShow() | void ( [String/Ext.Element animateTarget], [Function callback], [Object scope] )。在组件显示后调用 | ||
beforeShow() | 在组件显示前调用 | ||
initComponent() | 覆盖AbstractComponent版本。该方法是组件初始化的重要阶段,所有子类应当通过覆盖此方法来提供构造逻辑(一般不通过constructor方法),任何子类的覆盖版本必须调用父类的版本。下面是生成一个动态文字按钮的例子:
|
||
onDestroy() | 添加销毁逻辑,在调用覆盖版本的onDestroy后,当前组件被销毁 | ||
onHide() |
void ( [String/Ext.Element/Ext.Component animateTarget], [Function callback], [Object scope] ) 添加隐藏逻辑,在调用覆盖版本的onHide后,当前组件被隐藏 |
||
onShow() |
void ( [String/Ext.Element animateTarget], [Function callback], [Object scope] ) 添加显示逻辑,在调用覆盖版本的onShow后,当前组件显示 |
||
onShowComplete() |
void ( [Function callback], [Object scope] ) 在afterShow()之后调用 |
Ext.Component的代码分析如下:
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 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 |
Ext.define('Ext.Component', { alias: ['widget.component', 'widget.box'], extend: 'Ext.AbstractComponent', statics: { DIRECTION_TOP: 'top', DIRECTION_RIGHT: 'right', DIRECTION_BOTTOM: 'bottom', DIRECTION_LEFT: 'left', VERTICAL_DIRECTION_Re: /^(?:top|bottom)$/, INVALID_ID_CHARS_Re: /[\.,\s]/g }, resizeHandles: 'all', floating: false, toFrontOnShow: true, hideMode: 'display', bubbleEvents: [], monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/, constructor: function(config) { var me = this; config = config || {}; if (config.initialConfig) { if (config.isAction) { //从Ext.Action初始化 me.baseAction = config; } config = config.initialConfig; } else if (config.tagName || config.dom || Ext.isString(config)) { config = { applyTo: config, id: config.id || config }; } me.callParent([config]);//调用覆盖版本 if (me.baseAction){ me.baseAction.addComponent(me); } }, initComponent: function() { var me = this; me.callParent(); if (me.listeners) { me.on(me.listeners);//添加监听器 me.listeners = null;//移除配置项 } me.enableBubble(me.bubbleEvents); //启用事件冒泡 me.mons = []; }, afterRender: function() { var me = this; me.callParent(); //设置页面位置 if (!(me.x && me.y) && (me.pageX || me.pageY)) { me.setPagePosition(me.pageX, me.pageY); } }, beforeRender: function () { var me = this, floating = me.floating, cls; if (floating) { //处理浮动组件 me.addCls(Ext.baseCSSPrefix + 'layer'); cls = floating.cls; if (cls) { me.addCls(cls); } } return me.callParent(); //调用覆盖版本 }, //布局后模板 afterComponentLayout: function(){ this.callParent(arguments); if (this.floating) { //处理浮动组件 this.onAfterFloatLayout(); } }, //构造浮动混入 makeFloating : function (dom) { this.mixins.floating.constructor.call(this, dom); }, wrapPrimaryEl: function (dom) { if (this.floating) { this.makeFloating(dom); } else { this.callParent(arguments); } }, //初始化尺寸调整器 initResizable: function(resizable) { var me = this; resizable = Ext.apply({ target: me, dynamic: false, constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : null), handles: me.resizeHandles }, resizable); resizable.target = me; me.resizer = new Ext.resizer.Resizer(resizable); }, getDragEl: function() { return this.el; //被拖拽的元素 }, //初始化拖拽 initDraggable: function() { var me = this, //如果拖拽目标不是封装元素本身,则从resizer.el创建一个简单组件作为拖拽目标 dragTarget = (me.resizer && me.resizer.el !== me.el) ? me.resizerComponent = new Ext.Component({ el: me.resizer.el, rendered: true, container: me.container }) : me, ddConfig = Ext.applyIf({ el: dragTarget.getDragEl(), //拖拽限制范围 constrainTo: me.constrain ? (me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent())) : undefined }, me.draggable); if (me.constrain || me.constrainDelegate) { ddConfig.constrain = me.constrain; ddConfig.constrainDelegate = me.constrainDelegate; } //设置dd,即组件拖拽器(处理任何组件的drag行为的对象) me.dd = new Ext.util.ComponentDragger(dragTarget, ddConfig); }, //滚动组件的targetEl scrollBy: function(deltaX, deltaY, animate) { var el; if ((el = this.getTargetEl()) && el.dom) { el.scrollBy.apply(el, arguments); } }, //设置组件位置前的模板 beforeSetPosition: function () { var me = this, pos = me.callParent(arguments), adj; if (pos) { adj = me.adjustPosition(pos.x, pos.y); pos.x = adj.x; pos.y = adj.y; } return pos || null; }, //设置组件位置后的模板 afterSetPosition: function(ax, ay) { this.onPosition(ax, ay); this.fireEvent('move', this, ax, ay); }, //在指定位置显示组件 showAt: function(x, y, animate) { var me = this; if (!me.rendered && (me.autoRender || me.floating)) { me.doAutoRender(); //如果尚未渲染,执行渲染 me.hidden = true; //设置hidden状态,以便后续发布beforeshow、show事件 } if (me.floating) { //对于浮动组件,设置位置 me.setPosition(x, y, animate); } else { //否则,设置Page位置 me.setPagePosition(x, y, animate); } me.show(); }, setPagePosition: function(x, y, animate) { var me = this, p, floatParentBox; if (Ext.isArray(x)) { y = x[1]; x = x[0]; } me.pageX = x; me.pageY = y; if (me.floating) { if (me.isContainedFloater()) { //在容器中注册的浮动组件,必须把x,y设置为相对于容器的值 floatParentBox = me.floatParent.getTargetEl().getViewRegion(); if (Ext.isNumber(x) && Ext.isNumber(floatParentBox.left)) { x -= floatParentBox.left; } if (Ext.isNumber(y) && Ext.isNumber(floatParentBox.top)) { y -= floatParentBox.top; } } else { p = me.el.translatePoints(x, y); //将文档坐标转换为页面坐标 x = p.left; y = p.top; } me.setPosition(x, y, animate); } else { p = me.el.translatePoints(x, y); //将文档坐标转换为页面坐标 me.setPosition(p.left, p.top, animate); } return me; }, //得到组件的位置 getPosition: function(local) { var me = this, el = me.el, xy, isContainedFloater = me.isContainedFloater(), floatParentBox; //对于非浮动组件,本地位置就是相对于el的offsetParent的位置偏移量 if ((local === true) && !isContainedFloater) { return [el.getLocalX(), el.getLocalY()]; } xy = me.el.getXY(); //对于浮动组件,本地位置就是相对于容器的targetEl的位置偏移量 if ((local === true) && isContainedFloater) { floatParentBox = me.floatParent.getTargetEl().getViewRegion(); xy[0] -= floatParentBox.left; xy[1] -= floatParentBox.top; } return xy; }, //显示组件 show: function(animateTarget, cb, scope) { var me = this, rendered = me.rendered; if (rendered && me.isVisible()) { if (me.toFrontOnShow && me.floating) { //如果已渲染并且是可见的,带到最前面 me.toFront(); } } else { //触发beforeshow,如果返回false,什么都不做 if (me.fireEvent('beforeshow', me) !== false) { me.hidden = false; if (!rendered && (me.autoRender || me.floating)) { //如果尚未渲染,执行渲染 me.doAutoRender(); rendered = me.rendered; } if (rendered) { //如果渲染成功 me.beforeShow(); //beforeShow模板 me.onShow.apply(me, arguments);//onShow模板 me.afterShow.apply(me, arguments);//afterShow模板 } } else { me.onShowVeto(); } } return me; }, onShowVeto: Ext.emptyFn, //生命周期模板方法:显示前,渲染后 beforeShow: Ext.emptyFn, //生命周期模板方法:显示时 onShow: function() { var me = this; me.el.show(); //底层HTML元素的显示 me.callParent(arguments); if (me.floating) { //处理浮动组件的尺寸 if (me.maximized) { me.fitContainer(); } else if (me.constrain) { me.doConstrain(); } } }, //生命周期模板方法:显示后 afterShow: function(animateTarget, cb, scope) { var me = this, fromBox, toBox, ghostPanel; animateTarget = animateTarget || me.animateTarget; if (!me.ghost) { animateTarget = null; } if (animateTarget) { //处理动画 animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget); toBox = me.el.getBox(); fromBox = animateTarget.getBox(); me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets'); ghostPanel = me.ghost(); ghostPanel.el.stopAnimation(); ghostPanel.el.setX(-10000); ghostPanel.el.animate({ from: fromBox, to: toBox, listeners: { afteranimate: function() { delete ghostPanel.componentLayout.lastComponentSize; me.unghost(); me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets'); //动画完毕后,继续调用下一模板 me.onShowComplete(cb, scope); } } }); } else { //直接调用下一模板 me.onShowComplete(cb, scope); } }, //生命周期模板方法:显示(包括动画效果)完成 onShowComplete: function(cb, scope) { var me = this; if (me.floating) { me.toFront(); me.onFloatShow(); } Ext.callback(cb, scope || me); me.fireEvent('show', me); //发布show事件 delete me.hiddenByLayout; }, //隐藏组件 hide: function() { var me = this; me.showOnParentShow = false; //触发beforehide事件,如果false什么都不做 if (!(me.rendered && !me.isVisible()) && me.fireEvent('beforehide', me) !== false) { me.hidden = true; if (me.rendered) { //如果已经渲染过,则调用onHide模板 me.onHide.apply(me, arguments); } } return me; }, //生命周期模板方法:隐藏 onHide: function(animateTarget, cb, scope) { var me = this, ghostPanel, toBox; animateTarget = animateTarget || me.animateTarget; if (!me.ghost) { animateTarget = null; } if (animateTarget) { //处理动画 animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget); ghostPanel = me.ghost(); ghostPanel.el.stopAnimation(); toBox = animateTarget.getBox(); toBox.width += 'px'; toBox.height += 'px'; ghostPanel.el.animate({ to: toBox, listeners: { afteranimate: function() { delete ghostPanel.componentLayout.lastComponentSize; ghostPanel.el.hide(); me.afterHide(cb, scope);//动画完毕后,继续调用下一模板 } } }); } me.el.hide(); //隐藏组件的元素 if (!animateTarget) { me.afterHide(cb, scope);//直接调用下一模板 } }, //声明周期模板方法:隐藏后 afterHide: function(cb, scope) { var me = this; delete me.hiddenByLayout; Ext.AbstractComponent.prototype.onHide.call(this); Ext.callback(cb, scope || me); me.fireEvent('hide', me); //发布hide事件 }, //声明周期模板方法:销毁时 onDestroy: function() { var me = this; if (me.rendered) { //如果已经渲染,则销毁组件附加的对象 Ext.destroy( me.proxy, me.proxyWrap, me.resizer, me.resizerComponent ); } delete me.focusTask; //删除聚焦任务 me.callParent(); }, //处理组件聚焦 focus: function(selectText, delay) { var me = this, focusEl, focusElDom, containerScrollTop; if (delay) { //延迟聚焦 if (!me.focusTask) { me.focusTask = new Ext.util.DelayedTask(me.focus); } me.focusTask.delay(Ext.isNumber(delay) ? delay : 10, null, me, [selectText, false]); return me; } if (me.rendered && !me.isDestroyed && me.isVisible(true) && (focusEl = me.getFocusEl())) { if (focusEl.isComponent) { //聚焦行为代理给子代组件 return focusEl.focus(selectText, delay); } if ((focusElDom = focusEl.dom)) { if (focusEl.needsTabIndex()) { focusElDom.tabIndex = -1; //设置tabIndex } if (me.floating) { containerScrollTop = me.container.dom.scrollTop; } //直接使用原生HTML聚焦 focusEl.focus(); if (selectText === true) { focusElDom.select(); } } if (me.floating) { //对于浮动组件,聚集意味着位于最前 me.toFront(true); if (containerScrollTop !== undefined) { me.container.dom.scrollTop = containerScrollTop; } } } return me; }, //取消延迟的聚焦 cancelFocus: function() { var task = this.focusTask; if (task) { task.cancel(); } }, //失焦处理 blur: function() { var focusEl; if (this.rendered && (focusEl = this.getFocusEl())) { focusEl.blur(); } return this; }, //当大小改变时执行的模板方法 onResize: Ext.emptyFn, //获取事件传播的目标 getBubbleTarget: function() { return this.ownerCt || this.floatParent; }, //得到“内容目标” getContentTarget: function() { return this.el; }, //克隆组件 cloneConfig: function(overrides) { overrides = overrides || {}; var id = overrides.id || Ext.id(), cfg = Ext.applyIf(overrides, this.initialConfig),//获取当前组件的初始配置 self; cfg.id = id; self = Ext.getClass(this); //获取当前组件的类型 return new self(cfg); //调用构造器 }, //向上传播的执行一函数 bubble: function(fn, scope, args) { var p = this; while (p) { if (fn.apply(scope || p, args || [p]) === false) { break; } p = p.getBubbleTarget(); } return this; } }); |
Leave a Reply