Email:zhoubangtao@gmail.com
转载请注明出处: http://blog.csdn.net/zhoubangtao/article/details/27366477
1. 简介
一个Ext JS 应用的UI是由一个或多个叫做组件(Component)的小部件组成的。所有的组件都是Ext.Component的子类,Ext.Component可以使其子类参与自动化的声明周期管理,包括初始化、渲染、调整大小及位置和销毁。Ext JS提供了大量的直接可用的组件,并且任何组件都能够被轻松的继承从而实现定制化的组件。
2. 组件层次结构
容器是一个可以容纳其他组件的一种特殊组件。一个典型的应用程序是由多个嵌套的树状层次结构的组件组成的。容器负责管理它的子组件的生命周期,包括创建、渲染、调整大小及位置、销毁。一个典型的应用程序组件层次结构以一个Viewport在顶部开始,这个Viewport内部又嵌套了其他的容器或者组件:
使用容器的items配置属性将组件添加成自己的子组件。这个例子中使用了Ext.create去实例化了两个Panel,然后将这两个Panel添加成了Viewport的子组件:
var childPanel1 = Ext.create('Ext.panel.Panel', { title: 'Child Panel 1', html: 'A Panel' }); var childPanel2 = Ext.create('Ext.panel.Panel', { title: 'Child Panel 2', html: 'Another Panel' }); Ext.create('Ext.container.Viewport', { items: [ childPanel1, childPanel2 ] });
容器使用容器管理器去调整它的子组件的大小和位置。更多关于布局和容器的信息请参考我的另一篇文章【ExtJS 4.x学习教程】(3)布局和容器(Layouts and Containers)。
3. XType和延迟初始化
每个组件都有一个符号名叫做xtype。例如Ext.panel.Panel有一个xtype叫做‘panel’。所有组件的xtype在Ext.Component的API文档中都能找到。上边的例子显示了如何将已经初始化的组件添加到容器中,但是在一个大的应用程序中这样并不理想,由于并不是所有组件多需要立即被初始化,并且一些组件根据应用程序的使用情况可能永远不会被初始化。例如一个使用了Tab Panel的应用程序只需要用户点击了的tab的内容。这就是xtype存在的理由,它可以让容器的子元素被预先配置进去,但是知道容器决定它需要时才会被初始化。
下边的例子代码示范了延迟初始化以及使用Tab Panel渲染容器的子组件。每一个标签页有一个监听器,负责当标签页被点击时显示一个提示。
Ext.create('Ext.tab.Panel', { renderTo: Ext.getBody(), height: 100, width: 200, items: [ { // Explicitly define the xtype of this Component configuration. // This tells the Container (the tab panel in this case) // to instantiate a Ext.panel.Panel when it deems necessary xtype: 'panel', title: 'Tab One', html: 'The first tab', listeners: { render: function() { Ext.MessageBox.alert('Rendered One', 'Tab One was rendered.'); } } }, { // this component configuration does not have an xtype since 'panel' is the default // xtype for all Component configurations in a Container title: 'Tab Two', html: 'The second tab', listeners: { render: function() { Ext.MessageBox.alert('Rendered One', 'Tab Two was rendered.'); } } } ] });
运行以上代码就会导致第一个标签页立即弹出一个提示。因为它是默认的活动标签页,所以它的容器Tab Panel会立即初始化它并且渲染它。
直到第二个标签页被点击它的提示框才会显示出来。这就说明这个标签页并没有被渲染直到需要它的时候,因为render方法并没有被触发直到这个标签页被激活。
例子代码可以在Ext JS 4 SDK提供的例子中找到
4. 显示和隐藏
所有的组件都有内建的show和hide方法。隐藏一个组件的CSS方法是“display : none”,但是它也可以通过hideMode配置来改变效果。
var panel = Ext.create('Ext.panel.Panel', { renderTo: Ext.getBody(), title: 'Test', html: 'Test Panel', hideMode: 'visibility' // use the CSS visibility property to show and hide this component }); panel.hide(); // hide the component panel.show(); // show the component
5. 浮动组件
浮动组件使用CSS的绝对定被位置于文档流之外,并且不参与它容器的布局。一些组件(例如Window)就默认是浮动的,但是任何组件都可以通过使用floating配置参数实现浮动。
var panel = Ext.create('Ext.panel.Panel', { width: 200, height: 100, floating: true, // make this panel an absolutely-positioned floating component title: 'Test', html: 'Test Panel' });
上边的代码实例化了一个Panel但是并没有渲染它。通常一个组件要么指定一个renderTo配置,要么被添加到一个容器中作为子元素,但是在浮动组件的情况下,以上两种情况都不必。浮动组件在它们的show方法被第一次调用的时候会自动渲染到document body上:
panel.show(); // render and show the floating panel
这里还有一些浮动组件相关的其他的配置和方法:
- draggable —— 使浮动组件能够在屏幕上拖拽
- shadow —— 定制化浮动组件阴影的外观
- alignTo() —— 使一个浮动组件与一个特殊元素靠齐
- center() —— 把一个浮动组件置于它的容器中间
你可以在Ext JS 4 SDK的例子中找到浮动Panel的例子
6. 创建定制化组件
6.1 组合还是扩展
当创建一个新的UI类时,你必须确定你的类时Component的一个实例还是继承自一个组件。
这里强烈推荐继承一个和你需要功能相近的基类。这是因为Ext JS 提供的自动化生命周期管理将会被合适的布局管理器管理并且在从容器中移除时会自动销毁。
很简单就能写一个新的组件类,并且它能够在组件层次结构中占有一席之地,而不是一个包含Ext JS 组件的新类,还必须在外部对它进行渲染和管理。
6.2 子类化
类系统(见【ExtJS 4.x学习教程】(1)类系统(Class System))能够让你很简单就能继承一个已存在的组件。下边的例子创建了一个Ext.Component的子类,而没有添加任何额外的功能:
Ext.define('My.custom.Component', { extend: 'Ext.Component' });
6.3 模板方法
Ext JS使用模板方法模型(具体可以参考http://en.wikipedia.org/wiki/Template_method_design_pattern)委托给子类,为对应的子类指定特定的表现。
也就是在这个层次结构链中的每一个类都可以为一个组件的生命周期的各个阶段贡献一部额外的业务逻辑。每一个类实现了他自己的特殊表现,同时还允许在这个层级结构链中的其他类继续贡献他们自己的逻辑。
render函数就是一个例子。render是一个在组件父类中定义的私有方法,AbstractComponent负责初始化组建生命周期中的渲染阶段。render不可以被重写,但是在处理过程中它调用了onRender方法使子类的实现能够添加一个onRender方法去做一些特殊的操作。每一个onRender方法添加自己的逻辑之前必须先调用它父类的onRender方法。
下边的图表描述了onRender模板方法的功能。
render方法被调用(被一个容器的布局管理器)。这个方法不可以被重写并且是由Ext的基类实现的。它调用this.onRender,而onRender则是由当前正在使用的子类实现的(如果真的确实实现了这个方法)。这个方法就会调用它父类版本的方法,以此向上调用。最终每一个类都贡献了自己的逻辑功能,并且最终返回到render方法。
这里有一个组件子类实现了onRender方法的例子:
Ext.define('My.custom.Component', { extend: 'Ext.Component', onRender: function() { this.callParent(arguments); // call the superclass onRender method // perform additional rendering tasks here. } });
还要注意到许多模板方法都有对应的事件。例如render事件在组件被渲染后触发。但是当子类化的时候使用模板函数在生命周期的重要阶段执行自己的逻辑(而不是使用事件)还是很必要的。事件可能被程序挂起,也可能被一个handler阻止。
下边是可以被组件子类实现的模板方法:
- initComponent: 这个方法被构造器调用。它用来初始化数据、设置配置和绑定事件
- beforeShow: 这个方法在组件显示之前被调用
- onShow: 允许对显示操作添加额外的表现。调用父类的onShow方法后,组件就会显示出来
- afterShow: 这个方法在组件显示之后被调用
- onShowComplete: 这个方法在afterShow方法完成之后被调用
- onHide: 允许对隐藏操作添加额外的行为。在调用父类的onHide方法后,组件就会被隐藏
- afterHide: 这个方法在组件被隐藏后调用
- onRender:允许在渲染阶段添加额外的行为
- afterRender: 允许在渲染完成后添加额外的行为。在这个阶段,组件的元素就已经根据配置被设计好了,已经添加了任何配置的CSS类名,并且已经处在配置的可视化和有效状态。
- onEnable: 允许对使有效(enable)操作添加额外的行为。在调用父类的onEnable方法后,组件就会变成有效状态
- onDisable: 允许对使无效(disable)操作添加额外的行为。在调用父类的onDisable方法后,组件就会变为无效状态。
- onAdded: 允许当一个组件被添加到容器中时添加额外行为。在这个阶段,组件已经被添加到父容器的items集合中。在调用父类的onAdded方法后,ownerCt引用就会被呈现,并且如果配置了ref,refOwner就会被设置。
- onRemoved: 允许当一个组件被从容器中移除时添加额外行为。在这个阶段,组件已经从父容器的items集合中被移除了,但是还没有被销毁(如果父容器的autoDestroy设置为true,或者remove调用时第二个参数传入了true,这个组件就会在移除时被销毁)。在父类的onRemoved被调用之后,ownerCt和refOwner就不会呈现了。
- onResize: 允许对大小调整(resize)操作添加额外的行为。
- onPosition: 允许对位置调整(position)操作添加额外的行为。
- onDestroy: 允许对销毁操作添加额外的行为,在父类的onDestroy被调用后,组件就会销毁。
- beforeDestroy: 这个方法在组件被销毁前调用。
- afterSetPosition: 这个方法在组件的位置被调用后调用。
- afterComponentLayout: 这个方法在组件被布局好后调用。
- beforeComponentLayout: 这个方法在组件被布局好前调用。
6.4 继承哪个类
选择最好的类来扩展主要是效率问题,以及考虑基类必须提供哪些能力。以前的版本总是趋向于总是继承自Ext.Panel,无论需要被渲染和管理的组件到底是什么
Panel类有很多能力:
- Border
- Header
- Header tools
- Footer
- Footer buttons
- Top toolbar
- Bottom toolbar
- 容纳和管理子组件
如果这些能力不是必须的,那使用Panel就是对资源的一种浪费。
6.4.1 组件(Component)
如果需要的UI组件不需要的容纳任何的其他组件,也就是如果它仅封装了一些必要的HTML表单等,那么继承Ext.Component比较合适。例如,下边的I类是一个封装了HTML image元素的组件,它允许设置和获取image的src属性。当image被加载完后还触发了load事件:
Ext.define('Ext.ux.Image', { extend: 'Ext.Component', // subclass Ext.Component alias: 'widget.managedimage', // this component will have an xtype of 'managedimage' autoEl: { tag: 'img', src: Ext.BLANK_IMAGE_URL, cls: 'my-managed-image' }, // Add custom processing to the onRender phase. // Add a ‘load’ listener to the element. onRender: function() { this.autoEl = Ext.apply({}, this.initialConfig, this.autoEl); this.callParent(arguments); this.el.on('load', this.onLoad, this); }, onLoad: function() { this.fireEvent('load', this); }, setSrc: function(src) { if (this.rendered) { this.el.dom.src = src; } else { this.src = src; } }, getSrc: function(src) { return this.el.dom.src || this.src; } });用法:
var image = Ext.create('Ext.ux.Image'); Ext.create('Ext.panel.Panel', { title: 'Image Panel', height: 200, renderTo: Ext.getBody(), items: [ image ] }) image.on('load', function() { console.log('image loaded: ', image.getSrc()); }); image.setSrc('http://www.sencha.com/img/sencha-large.png');
例子代码可以在Ext JS 4 SDK提供的例子中找到。这个例子只是为了展示,不要和Ext.Img类搞混了,Ext.Img类就是用来在真正的应用程序中管理图片的。
6.4.2 容器(Container)
如果你需要的UI组件需要包含其他的组件,但是不需要前边提到的Panel的功能,那么继承Ext.container.Container比较合适。在容器层,记住用那个Layout来渲染和管理子组件很重要。
容器有下边额外的模板方法:
- onBeforeAdd: 这个方法在添加一个新的子组件之前被调用。它传入这个新子组件,它可以用来改变这个组件,或者准备容器等。如果这个方法返回false,那么添加操作就被终止。
- onAdd: 这个在一个新的组件被添加后调用。它传入刚刚被添加的组件。这个方法可以用来更新任何依赖于items状态的内部结构。
- onRemove: 这个方法在一个组件被移除后调用。它被传入刚刚被移除的组件。这个方法可以用来更新任何依赖于items状态的内部结构。
- beforeLayout: 这个方法在容器已经布局好它的子组件之前被调用。
- afterLayout: 这个方法在容器已经布局好它的子组件之后被调用。
6.4.3 面板(Panel)
如果你的UI组件必须有header,footer,或者toolbar,继承Ext.Panel会比较合适。
注意:一个Panel是一个容器。记住使用哪个Layout去渲染和管理它的子组件很重要。
继承自Ext.Panel的类通常是高度特定于应用的,并且通常使用Layout和其他的UI组件(通常是容器和form表单)一起使用,通过tbar和bbar的控制方式提供它所容纳的组件的操作方式。
Panel有如下额外的模板方法:
- afterCollapse: 这个方法在Panel被收起后调用。
- afterExpand: 这个方法在Panel被展开后条用。
- onDockedAdd: 这个方法在一个docked item被添加到Panel后调用。
- onDockedRemove: 这个方法在一个docked item被从Panel移除后调用。
7. 总结
本文主要讲述了Ext JS 4的组件模型,一些常用组件,以及怎么定制一个组件。通过学习本文,你能够很好的了解Ext JS 4 组件模型的全貌,并能够新建一个组件类时,采用正确的方式。
8. 参考资料
Email:zhoubangtao@gmail.com
转载请注明出处: http://blog.csdn.net/zhoubangtao/article/details/27366477