组件
Ext JS应用的UI是由一个或者多个widgets组称, 我们称之为Components. 所有的组件都是Ext.Component的子类,允许组件自动管理生命周期, 包括instantiation, rendering, sizing and positioning, 以及destruction. Ext JS提供了很多直接可以使用的组件,
能过简单继承,可以创建自定义组件。
The component life cycle
在我们讲布局系统和窗口部件之前,我们需要先知道组件是如何工作的
在Ext JS框中,所有的组件都是继承于Ext.Conponent类。Ext.Conponent的的别名为Ext.AbstractComponent, 它为整个框架的组件提供了共享的方法。
当我们创建组件, 比如panels, windows, grids, trees, 或其它,它有一个生命周期。
在生命周期的每一个阶段要做什么,知道这些对我们来说非常重要。这对我们创建一个自定义组件,或者扩展一个组件非常有帮助。
在组件的生命周期中有三个主要阶段:初始化处理过程,渲染过程, 以及销毁过程。
在初始化阶段,创建一个新的实例,并且在组件管理器中注册;接着在渲染阶段,会在DOM树中,创建所需要的节点。而在销毁阶段,销毁组件,删除监听器,并且从DOM中删除node节点.
为了更好的理解上面的三个阶段,让我们创建一个panel组件, 然后看看到底发生了什么
var panel = Ext.create("Ext.panel.Panel",{
title: "My First panel",
width: 400,
height: 250,
renderTo: Ext.getBody()
});
初始化阶段
这个阶段的主要任务是,根据配置,创建组件的实例。它也会在component管理器中注册我们新创建的组件,以及其它的一些工作。以下是这个阶段的所有步聚
以下是每一步详细的解释
- 第一步,我们将配置属性应用到我们正在创建的实例上。在上面的代码中,title, width, height, renderTo属性,会被复制到panel实例中,以及任何我们其它定义的属性
- 第二步定义常见的事件,比如enable, disable, show等。每个组件都拥有这些事件
- 为这个实例分配一个唯一标识符。如果我在配置中有定义id(bad practice), 则使用配置中的ID
- 验证我们是否有在配置中指定plugins, 如果指定,则创建这些插件的实例。插件既为我们创建的组件的一个额外功能。在上面我们没有定义任何插件,所有这一步跳过
- 执行initComponent函数,它是一个模板方法,会在constructor中调用, 如果我们想在实例创建时,执行自定义的代码,应该在subclasses中重写这个方法, 当然Component在不同的阶段,都提供了template方法,能让我们添加额外的功能
-
在这个步聚,我们将新创建好的实例添加到Ext.ComponentManager对像。这意味着我们创建的组件都保存在组件管理器中,允许我们通过Ext.getCmp方法,并且传递ID,就能获取这个组件
//getting a component by its ID
var panel = Ext.getCmp("panel-1234");
console.log(panel);getCmp方法在调试应用时非常有用,我们可以在DOM元素中获得任何组件的ID. 通过这个ID, 我们可以获得这个实例, 并且检查我们对像的状态,但不推荐在我们的代码中使用这种方法,我们可以使用Ext.ComponentQuery.query方法。Ext.ComponentQuery.query(‘panel’),它返回一个使用了Ext.panel.Panel实例数组
Component包含两个mixins类, 一个是事件管理器,另一个我们组件的状态。
- 如果我们有定义plugins, 在上面的步聚中我们创建了这些插件的实例,现在,调用每个插件的init()方法,并用传递当前组件给它们,进行初始化。
- 如果在配置中有renderTo属性,那么在这一步开始渲染,那么表示我们组件的虚拟节点会被插入到DOM中。如果没有定义这个属性,则什么也不发生。我们可以在其它的任何地方,渲染我们的组件
var panel = Ext.create("Ext.panel.Panel",{
title: "My First panel",
width: 400,
height: 250
});
panel.render(Ext.getBody());
如果我们想之后渲染组件,可以调用这个组件的render方法,并且传递要渲染组件的位置作为参数。在上面的代码中,我们将它插入到document中。我们也可以设置为节点的ID panel.render("some-div-id");
*注意:如果组件是在另一个container中, 则不需要调用render方法,它们会在container被创建/渲染时,自动渲染
The rendering phase
渲染阶段只在组件还没有被渲染时发生。在这个阶段,所有的节点将被插入到DOM中, 样式和事件监听器将被应用,所以我们能够看到新的组件外观,并且与它交互(事件).
- 触发beforeRender事件,如果它的监听器返回false, 则停止渲染
- 确认组件是否floating组件,即在配置中指定floating为true. 常见的组件有menu, window. 如果是,分配z-index属性。
- 创建一个container属性,并且将一个DOM元素赋值给它,表示组件将在哪里被渲染, container属性是一个Ext.dom.Element实例
- 组件的模板方法onRender被执行,创建一个el属性,它表示组件的主节点元素。我们可以为组件定义一个html模板,然后会被创建,并且添加到主节点中。我们可以重写onRender方法,添加指定的节点到DOM中。
- 设置显示模式,既根据配置中的hideMode, 它的值可以为(display, visibility or offset)
- 如果有设置overClas属性,则监听鼠标移入和移出事件,用来添加和删除这个css类。
- 在第七步,触发render事件,组件实例将作为参数传递给事件处理器
-
第八步用来初始化内容。有以下三个方法来设置组件的内容
- 可以在组件的属性中定义一个html属性
- contentEl属性,它的值为已存在的DOM元素的ID.
- tpl属性,同时定义data属性对像,以替换为我们模板中点位符
以下代码显示了上面的三种方式,我们应该在组件中只使用其中一种方式
//Using the HTML property
Ext.create("Ext.Component",{
width: 300,
height: 150,
renderTo: Ext.getBody(),
html: "<h1>Hello!</h1><p>This is an <strong>example
</strong> of content</p>"
});
//Using an existing DOM element with an ID content
Ext.create("Ext.Component",{
width: 300,
height: 150,
renderTo: Ext.getBody(),
contentEl: "content"
});
//Using a template with data
Ext.create("Ext.Component",{
width: 300,
height: 150,
renderTo: Ext.getBody(),
data: {name:"Veronica", lastName:"Sanchez"},
tpl: ["<h1>Content</h1><p>Hello {name} {lastName}!</p>"]
}); 返回到render阶段,下一步执行afterRender模板方法. 如果一个组件包含了子组件,子组件将在这一步渲染。我们在container之后讨论.
- 在上一步,afterRender事件触发。我们可以在subclass中监听这个事件,在所有的节点都被渲染到DOM后,执行一此动作。
- 在上一步,注册鼠标,键盘,大小等监听器
- 最后一步,如果有设置hidden属性,则隐藏主组件。同样,如果设置了disabled为true. 则组件执行disable方法,它会为组件的主节点添加css类,使得组件的外观表现为disabled, 并且在DOM上面的html标签为disable标志
<input name="test" disable>
以下的代码显示了渲染阶段是如何工作的,我们整个的处理都是从调用render方法开始
var mycmp = Ext.create("Ext.Component",{
width: 300,
height: 150,
data: {
name:"Veronica",
lastName:"Sanchez"
},
tpl:["<h1>Content</h1><p>Hello {name} {lastName}!</p>"]
});
//The rendering phase starts for this component
mycmp.render(Ext.getBody());
通过以上的学习,我们知道,可以在定义我们自己的类时,重写onRender, afterRender方法。
The destruction phase
这个阶段主要是清除DOM, 删除监听器,并且通过删除对像和数组,清理被使用的内存。当我们不想在使用一个组件时,销毁组件非常重要。销毁阶段将在我们使用组件完成任务后进行。比如,我们创建了一个窗口,它有一个closeAction属性可以用来销毁。(默认情况下,已经设置过了),销毁阶段将在用户关闭窗口后被调用
- 销毁阶段在开始时,会先触发beforeDestroy事件,如果事件处理器返回false, 则停止销毁。如果继续,并且组件是floating类型的,则从floating manager中取消注册。
- 执行模板方法beforeDestroy,所有subclasses通过使用这个方法来删除它们的子元素或者清理内存
- 在第三步,如果将被销毁的组件是其它组件的子组件,那么在父组件中,这个组件的引用,将被删除
- onDestroy方法将被执行,这是一个模板方法,执行这个方法是为了销毁我们组件的属性,并且确保它的子组件(已经被添加到当前组件)也被销毁, 同时,也清理我们自己创建的自定义监听器
- 第五步,销毁所有的插件
- 如果组件已经被渲染了,则从DOM中删除所有的组件节点,和节点所对应的监听器
- 触发destroy事件,我们可以监听这个事件,执行相应的动作
- 在component manager中取消组件实例的注册,清理所有的事件。
有一件非常重要的事情需要记住,我们应当删除和清理在组件中使用的内存,以及我们在添加节点到DOM之前使用的内存。我们应该重写相应的方法,来正确的销毁我们的组件。
如果我们要清除一个组件,可以调用它的destroy方法,这个方法将会触发上面的销毁阶段,以上所有的步骤将会被执行
//The destroy phase starts for this component
cmp.destroy();
Lifecycle的作用
现在我们已经知道创建一个组件需要经理哪些步骤,我们可以利用lifecycle来自定义我们的组件。下面的例子显示了,在生命周期的某一个阶段,我们可以通过重写某些方法,实现额外的功能
Ext.define('Myapp.sample.CustomComponent',{
extend: 'Ext.Component',
initComponent: function(){
var me = this;
me.width = 200;
me.height = 100;
me.html = {
tag: 'div',
html: 'X',
style: { // this can be replaced by a CSS rule
'float': 'right',
'padding': '10px',
'background-color': '#e00',
'color': '#fff',
'font-weight': 'bold',
'cursor': 'pointer'
}
};
me.myOwnProperty = [1,2,3,4];
me.callParent();
console.log('Step 1. initComponent');
},
beforeRender: function(){
console.log('Step 2. beforeRender');
this.callParent(arguments);
},
onRender: function(){
console.log('Step 3. onRender');
this.callParent(arguments);
this.el.setStyle('background-color','#ccc');
},
afterRender : function(){
console.log('4. afterRender');
this.el.down('div').on('click',this.myCallback,this);
this.callParent(arguments);
},
beforeDestroy : function(){
console.log('5. beforeDestroy');
this.callParent(arguments);
},
onDestroy : function(){
console.log('6. onDestroy');
delete this.myOwnProperty;
this.el.down('div').un('click',this.myCallback);
this.callParent(arguments);
},
myCallback : function(){
var me = this;
Ext.Msg.confirm('Confirmation','Are you sure you want to close
this panel?',function(btn){
if(btn === 'yes'){
me.destroy();
}
});
}
});
Ext.onReady(function(){
Ext.create('Myapp.sample.CustomComponent',{
renderTo : Ext.getBody()
});
});
我们可以看到以上的方法都是基于组件的生命周期进行执行,如果我们想要销毁一个组件,我们需要点击右上角的按纽。它会调用destroy方法,将节点从DOM中删除,删除事件以及从内存中删除对像。
在Ext JS中,理解组件的生命周期对于添加自动义事件和监听器来说非常重要,这样我们才能在我们的应用程序中提供适当的功能和自定义的代码。
The Component Hierarchy
一个Container是一个特殊类型的组件,它可以包含其它的组件。一个标准的application是许多嵌套的组件,类似于树的结构组成,我们称之为组件层级。Containers负责管理组件的子组件的组件生命周期,这包括,创建,渲染,大小和位置,以及destruction. 一个标准应用
的组件层级是从Viewport开始。然后在Viewport嵌套其它Container or Component
子组件被添加到容器中,是通过在创建容器对像时,传入items属性。如下所示
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 ]
});
Containers 使用Layout Managers来确定子组件的大小和位置, 更多关于布局信息可以查看Layout and Container Guide
XTypes and Lazy Instantiation
每一个组件都有一个像征性的名字,称为xtype, 比如, Ext.panel.Panel的xtype为panel. 在上面的代码中,我们演示了如何初始化一个组件实例,并且将它们添加到容器中。在一个大型的应用中,这不是一种好的方法,因为不是所有的组件初始化之后在使用。有的组件可以不会被初始化,这取决于应用程序是如何使用的。
比如,一个应用中的Tab Panel, 每个面板的内容只在这个tab被点击后在渲染。这就是为什么要使用xtype的原因为,它允许子组件可以容器中预先配置,但它不会被初始化,除非容器决定需要它时,才会被初始化。
下面的例子,演示了Tab Panel中lazy instantiation以及渲染组件。每一个panel注册了一个事件render(只触发一次)监听器,当一个panel被渲染时,显示一个警告。
@example
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.');
}
}
},
{
// 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.');
}
}
}
]
});
Showing and Hiding
所有的组件都有show 和 hide方法。 默认是修改组件的css为”display:none”, 但也可以改变它的hideMode为 visibility.
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
Floating Components
Floating Component是能过css的绝对定位(absolute positioning) 将组件从文档流中独立出来。它不在受父容器的布局影响。有些组件,比如Windows,默认就是float. 但任何其它的组件都可以通过floating为true进行设置
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进行配置,或者作为一个子组件,添加到一个容器中。但对于浮动组件来说,他们都不适用。 Floating 组件会在第一次调用show方法时,自动的在document body中渲染。
panel.show(); // render and show the floating panel
以下的这些配置和方法,跟floating components有关:
- draggable - 允许floating组件在屏幕内可拖动
- shadow - 自动义 floating components的阴影
- alignTo() - 将floating components组件,与指定的组件对齐
- center() - 将floating component在它的container,居中对齐
创建自定义组件
Subclassing
Ext.Base 是所有类的父类,它的原型和静态方法都会被其它类所继承。
虽然你可以扩展最底层的 Ext.Base, 但在很多情况下,开发者想要在更高级的类开始扩展
下面的代码创建了一个Ext.Component的子类
Ext.define('My.custom.Component', {
extend: 'Ext.Component',
newMethod : function() {
//...
}
});
上面的代码创建了一个新的类,My.custom.Component, 它继承了Ext.Component所有的功能(methods, properties etc).
Tempplate method
Ext JS使用 Template method pattern(一种面向对像的设计模式,在模板类-父类中定义抽像方法,而在具体的子类中具体的实现这些方法),将行为委托给子类。
这意味着,在继承链中的每个类,在组件生命周期的某个阶段(初始化,读取,sizing, positioning),都可以“贡献”额外的逻辑。每一个类都实现了自子的特殊行为,同时允许继承链中的其它类可以继续贡献它们的逻辑.
以render功能为例,render方法是定义在Component中。它负责组件生命周期中的渲染阶段的初始化。render函数不能被重写, 但是在render中,会调用onRender方法,所以允许子类通过添加onRender方法,添加自己的逻辑。每一个类的onRender方法,在实现自己的逻辑前,必须调用它父类的onRender方法。
下图演示了onRender这个模板方法原理
render方法被调用(通过这个组件的Container的layout manager调用). 这个方法在Ext.Component中定义,并且不能被子类重写。它会调用this.onRender, 如果有定义子类,则会调用子类的onRender方法。因为每个onRender方法必须调用父类的onRender,所以它依次向上调用,执行完父类的逻辑,然后在依次返回到当前代码,最后控制权返回到render方法.
以下是具体的代码
Sample Code
Ext.define('My.custom.Component', {
extend: 'Ext.Component',
onRender: function() {
this.callParent(arguments); // call the superclass onRender method
// perform additional rendering tasks here.
}
});
非常重要的是,许多的模板方法,也都有对应的事件名称。比如render event会在组件被渲染后触发。在定义子类时,是通过模板方法,而不是事件来实现它要添加的逻辑。这是因为,事件可以在监听器内被暂停或者停止。
以下是可以在Component子类中实现的模板方法
- initComponent 这个方法在constructor中被调用。它可以用来初始化数据,调协配置,添加事件监听器
- beforeShow 这个方法会在显示前调用
- onShow 允许在show操作中添加额外的形为。在调用了 supperclass的onShow后,组件才会被显示
- afterShow 这个方法在组件显示后调用
- onShowComplete 这个方法在afterShow方法完成后调用
- onHide 对组件在隐藏操作时,添加额外形为
- afterHide 在组件隐藏后调用
- onRender 在渲染阶段调用
- afterRender 当渲染完成时,此阶段的组件已经依据配置,应用了样式,我们可以添加样式,配置组件的可见性。
- onEnable 在enable操作时,添加额外的操作,并且调用父类的onEnable, 然后组件成为可用状态
- onDisable, Allows addition of behavior to the disable operation. After calling the superclass’s onDisable, the Component will be disabled.
- onAdded 组件被添加到容器时调用, 在当前阶段,组件已经在父容器的子组件items中。调用了superclass的onAdded后,然后ownerCt引用这个元素,如果有设置ref 配置,refOwner将被设置
- onRemoved 从父容器移除时调用,当前阶段,组件从父容器的子组件items中移除,但还没有被destroyed(如果parent 容器的autoDestroy设置为true时,它将会被destroy, 或者在调用时传递的第二个参数为truthy.) . 在调用supperclass的onRemoved后, ownerCt和refOwner将不在有效
- onResize 在resize操作时,添加额外的形为
- onPosition 在position 操作时,添加额外的形为
- onDestroy 在destroy操作时,添加额外的形为。在调用到superclass后,这个组件被摧毁
- beforeDestroy 在组件被destroy之前调用
- afterSetPosition 在组件的位置已经设置完成之后调用
- afterComponentLayout 在组件被布局后调用
- beforeComponentLayout 在组件被布局前调用
Which Class to Extend
选择最合适的类去扩展是非常重要的, 这个基础类必须提供符合我们要求的功能。通常我们选择Ext.panel.Panel, 它可以被渲染,也可以管理其它组件
Panel class有以下的功能
- Border
- Header
- Header tools
- Footer
- Footer buttons
- Top toolbar
- Bottom toolbar
- Containing and managing child Components
如果你定义的组件不需要上面的功能,则使用Panel就浪费了资源
Component
如果一个UI组件不需要包含其它组件,换言之,如果只是简单的封装一些HTML的表单,则 extending Ext.Component非常合适,比如,下面的组件,wrap一个HTML的图片元素, 允许我们能过设置和获取src属性。并且在图片加载完成后,触发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.Img
Container
如果创建的组件只是用来包含其它的组件,而不需要我们在上面提及的Panel的功能。则可以使用 Ext.container.Container. 在Container级别,需要记住的是使用Ext.layout.container.Container 管渲染和管理子组件
Container还包含以下的额外template 方法:
* onBeforeAdd 这个方法在添加一个新的组件时调用,它传递了一个新的组件,我们可以修改这个组件,如果返回false, 则终此添加操作
* onAdd 在一个组件添加完成后调用。它传递已经添加好的组件。这个方法用来根据子组件items的状态,更新内部的结构。
* onRemove 在一个新的组件被删除后调用,传递这个被删除的组件。根据子组件的items的状态,更新内部的结构
* beforeLayout 在容器对它的子组件布局前调用
* afterLayout 在容器对它的子组件布局后调用
Panel
如果创建的组件,必须有header, footer, or toolbars, 则Ext.panel.Panel非常合适
一个Panel是一个容器,它可以使用layout来读取和管理它的子组件
继承Ext.panel.Panel类通常都是应用级的,并且用来聚合在layout配置中的其它的UI组件(Containers or form fields)。并且通过tbar和 bbar 提供对包含的组件的操作
Panel类有以下的template方法
- afterCollapse 在面板收起时调用
- afterExpand 在面板展开时调用
- onDockedAdd 一个docked元素被添加时调用
- onDockedRemove 一个docked元素被删除时调用
About containers
在当前我们知道了组件生命周期的所有阶段,其中有一个阶段就是组件的子组件也会被渲染。现在我们将学习什么是容器,并且如何为它添加子组件。
Ext.container.Container用于管理子组件,并且使用layouts来排列它的子组件。如果我们想我们的类包含其它类,那么创建的这个类应该继承Ext.container.Container. 值得注意的是,Ext.container.Container类也是扩展于Component, 所以它也拥有conponent lifecycle.
容器类能过items属性来添加子元素。或者使用add方法来添加一个新的组件作为它的子元素。
Ext.define("MyApp.sample.MyContainer",{
extend: "Ext.container.Container", //Step 1
border: true,
padding: 10,
initComponent: function(){
var me = this;
Ext.each(me.items,function(item){ //Step 2
item.style = {
backgroundColor:"#f4f4f4",
border:"1px solid #333"
};
item.padding = 10;
item.height = 100;
});
me.callParent();
},
onRender: function(){
var me = this;
me.callParent(arguments);
if( me.border ){ //Step 3
me.el.setStyle( "border" , "1px solid #333" );
}
}
});
当我们继承于Container类时,我们可以使用items属性来定义容器的子元素,我们遍历items属性(数组),并且给每一项添加基本的样式。因此我们使用initComponent方法,它们在创建这个类的实例时,自动执行。同时通过callParent方法,来调用父类的initComponent方法.
在最后一步,我们重写了onRender方法,在执行完callParent方法后,我们可以访问它的el属性,它引用了当前组件的在 DOM中的主节点。如果我们有在创建这个组件时,设置了 border属性,我们将为主节点添加一个边框。
一旦我们创建完类后,就可以使用它创建它的实例。
Ext.onReady(function(){
Ext.create("MyApp.sample.MyContainer",{
renderTo: Ext.getBody(),
items: [{
xtype: "component",
html: "Child Component one"
},{
xtype: "component",
html: "Child Component two"
}]
});
});
以下是上面代码的效果图,它由一个主组件包含两个子组件。
当我们使用容器时,我们可以在主容器中使用defaults属性,为所有的子组件,应用相同的属性(default values/configurations). 让我们为上面的例子添加默认值
Ext.onReady(function(){
Ext.create("MyApp.sample.MyContainer",{
renderTo: Ext.getBody(),
defaults: {
xtype : "component",
width : 100
},
items :[{
html:"Child Component one" //xtype:"component",
},{
html:"Child Component two" //xtype:"component",
}]
});
});
defaults属性接受一个对像,这个对像包含我们想要对items数组中子组件的相同配置。在这里,我们只是就用了width和xtype属性。这样,我们就不需要在items中,为每个子组件,重复的使用xtype和width.
容器的类型
Ext JS使用多个组件作为容器使用,它们每一个有它自已的功能。以下是常用容器列表
Container | Description |
---|---|
Ext.panel.Panel | 它继承于Ext.container.Container, 它是Ext JS最常用的容器之一 |
Ext.window.Window | 它继承于Ext.panel.Panel. 主要用于应用程序的窗口。类型为floating类型的组件,可以被重置大小,并且可以被拖动。所以windows可以被最大化为整个viewport. |
Ext.tab.Panel | 继承于Ext.panel.Panel, 它可以包含其它 Ext.pane.Panel组件,并且为每一个子panel创建一个tab标签。同时 tab panel使用card layout来管理子组件 |
Ext.form.Panel | 它继承于Ext.panel.Panel,为form提供了一个标准的容器。从本质上说,它是一个面板容器,用来创建基础的form来管理field组件 |
Ext.Viewport | 它表示整个应用区域(浏览器窗口). 它渲染自已到document body中。大小设置为浏览器窗口的尺寸 |
注间,每一个container都有一个layout属性,这个属性将让我们有能力来呈现容器的子组件,并以不同的方式来排列它们
Viewport
Viewport如我们上面说的,它表示的是整个应用程序的可视区域,并且我们在一个web page中,只创建一个Viewport.
Ext.onReady(function(){
Ext.create('Ext.container.Viewport',{
padding:'5px',
layout:'auto',
style : {
'background-color': '#fc9',
'color': '#000'
},
html:'This is application area'
});
});
建议,不管你创建的应用是纯代码或者MVC或者MVVM架构,都应使用Viewport组件
panel
panel组件是最常用的组件。一个panel可以包含其它panels,甚至其它组件
Ext.onReady(function(){
var MyPanel = Ext.create("Ext.panel.Panel",{
renderTo: Ext.getBody(),
title: 'My first panel...',
width: 300,
height: 220,
html:'<b>Here</b> goes some <i>content</i>..!'
});
});
Panels 对比 containers
如之前看到的,container创建了一个基础的HTML DOM元素,然后包含了子元素。而Panels的使用,则创建了其它的区域(header and tools), 并且比container有更多的功能.
Window component
一个Window就是一个浮动的面板,并且包含更多的功能。它继承于Panel类。这表示,我们可以使用Panel中的所有方法。同时,我们双可以拖动,关闭它等等。
var win = Ext.create("Ext.window.Window",{
title: 'My first window',
width: 300,
height: 200,
maximizable: true,
html: 'this is my first window'
});
win.show();
//或者
Ext.create("Ext.window.Window",{
title: 'My first window',
width: 300,
height: 200,
maximizable: true,
html: 'this is my first window'
}).show();
在上面我们没有使用renderTo以及 render方法,而是直接调用show方法显示,这是因为floating component会自动渲染到document body
Layouts
每一个容器都有一个layout来管理它子组件的大小和位置,我们可以使用相应的类来实现固定布局和流体布局。
Ext.create('Ext.panel.Panel', {
renderTo: Ext.getBody(),
width: 400,
height: 200,
title: 'Container Panel',
layout: 'column',
items: [
{
xtype: 'panel',
title: 'Child Panel 1',
height: 100,
columnWidth: 0.5
},
{
xtype: 'panel',
title: 'Child Panel 2',
height: 100,
columnWidth: 0.5
}
]
});
当前,你已经知道container是如何工作的了。我们可以设置一个layout来排列它的子元素。如果我们没有定义layout属性,默认的auto layout将会被使用。即一个子组件,在另一个子组件之后显示。
我们有许多不同的layout来排列我们的组件,比如accordions(折叠), cards, columns等等。
我们可以在 Ext.layout.container包中找到所有的layout. 在layouts 枚举页面http://docs.sencha.com/extjs/6.0.2-classic/Ext.enums.Layout.html可以查看所有的layout, 我们将看到许多的类,每一个表示一种layout. 常见的布局如下
- The Border layout
- The Fit layout
- The Card layout
- The Accordion layout
- The Anchor layout
Layout系统原理
一个容器的Layout用来初始化所有子组件的大小和位置。当调用Container的updateLayout方法,它会触发Layout计算容器内所有组件的尺寸和位置。 updateLayout方法完全是一个递归的方法,所以容器的子组件也也会调用它们的updateLayout方法,直到组件层级的最底层。你通常不会在应用代码中调用updateLayout方法,因为框架为自动为你处理。
当容器被重置大小是,或者子组件被添加或者移除时,触发re-layout. 通常我们可以依赖框架为我们处理布局的更新。但有的时候我们需要手动进行布局更新,这时我们可以使用suspendLayout 属性设置为 true。比如我们在添,删除元素时,正常的会触发布局,但我们想在整个添加和删除操作都完成后,更新整个布局,这时候可以将suspendLayout设置为false. 并且手动调用updateLayout方法
var containerPanel = Ext.create('Ext.panel.Panel', {
renderTo: Ext.getBody(),
width: 400,
height: 200,
title: 'Container Panel',
layout: 'column',
suspendLayout: true // Suspend automatic layouts while we do several different things that could trigger a layout on their own
});
// Add a couple of child items. We could add these both at the same time by passing an array to add(),
// but lets pretend we needed to add them separately for some reason.
containerPanel.add({
xtype: 'panel',
title: 'Child Panel 1',
height: 100,
columnWidth: 0.5
});
containerPanel.add({
xtype: 'panel',
title: 'Child Panel 2',
height: 100,
columnWidth: 0.5
});
// Turn the suspendLayout flag off.
containerPanel.suspendLayout = false;
// Trigger a layout.
containerPanel.updateLayout();
Border layout
border layout将一个容器空间划分成五个区域,north, south, west, eash and center. 我们可以将我们的子组件放置在任意的区域。但通常,我们使用center区域
Ext.onReady(function(){
Ext.create('Ext.panel.Panel', {
width: 500, height: 300,
title: 'Border Layout',
layout: 'border',
items: [{
xtype: 'panel',
title: 'South Region is resizable',
region: 'south', // region
height: 100,
split: true // enable resizing
},{
xtype: 'panel',
title: 'West Region',
region:'west', // region
width: 200,
collapsible: true, //make panel/region collapsible
layout: 'fit',
split: true // enable resizing
},{
title: 'Center Region',
region: 'center',
layout: 'fit',
margin: '5 5 0 0',
html:'<b>Main content</b> goes here'
}],
renderTo: Ext.getBody()
});
});
我们在West区域,创建了一个可collapsible(收缩) panel. 当我们点击收缩按纽时,我们将看到面板将会收缩到左边。同样,我们定义South为split, 这允许我们能过拖动分隔条来重置 South 面板的大小.
The Fit layout
这种布局适用于只有一个子组件的容器。它许我们将容器内部的组件,占据整个容器的大小。当容器的大小发生改变,子组件的大小也会发生可变,以适合(fit)新的大小。
Ext.onReady(function(){
var win = Ext.create('Ext.window.Window', {
title: "My first window",
width: 300,
height: 200,
maximizable: true,
layout: "fit",
defaults: {
xtype: "panel",
height: 60,
border: false
},
items: [
{title: "Menu", html: "The main menu"},
{title: "Content", html: "The main content!"}
]
});
win.show();
})
如果不使用fit, 则在改变容器大小时,会出现如下情况
The Card layout
卡片布局可以用来管理多个子组件,所以如果我们需要创建一个向导(下一步下一步)或者一次只显示一个组件,我们应该使用这种布局。 这个布局继承于fit layout. 意味着任何时候任何时候只能显示一个组件,并且填充整个容器的空间。
我们设置items数据中显示组件的索引。移到下一个组件,我们只需用next, or prev方法。
Ext.onReady(function(){
var win = Ext.create("Ext.window.Window",{
title: "My first window",
width: 300,
height: 200,
maximizable: true,
layout: "card",//Step 1
defaults:{ xtype: "panel", height: 60, border: false },
items: [{
title: "Menu",
html: "The main menu"
},{
title: "Content",
html: "The main content!"
}]
});
win.show();
setTimeout(function(){
win.getLayout().setActiveItem(1); //Step 2
},3000);
})
在上面的第二步,我们能过getLayout获得layout的实例,并通过setActiveItem改变最初始元素, 显示第二个组件。我们也可以从layout实例中调用prev或者next方法,来显示上一张和下一张卡片.
The Accordion layout
跟Card layout, 它也是一次只能显示一个子组件。我们可以看到每一上内部组件的header部分,点击每个子组件的标题栏时,会后向下打开或者向上收起这个组件。
Ext.onReady(function(){
var win = Ext.create("Ext.window.Window",{
title: "My first window",
width: 300,
height: 200,
maximizable: true,
layout: "accordion",
defaults: { xtype: "panel" },
items:[
{title: "Menu", html: "The main menu" },
{title: "Content", html: "The main content!" },
{title: "3rd Panel", html: "Content here...!" }
]
});
win.show();
})
The Anchor layout
这个layout允许容器内的子组件,相对于容器的尺寸进行固定(Anchor). 如果父容器被重置大小,所有的子元素会依赖的规则进行大小的改变
默认的, AnchorLayout会基于容器自身大小,计算锚的尺寸。但如果一个container使用了AnchorLayout属性, 它将会使用AnchorLayout配置对像中anchorSize来设置, 如果指定了anchorSize属性,layout将使用一个虚拟的container来计算anchor的大小,而不是这个容器本身的大小
Ext.onReady(function(){
var win = Ext.create("Ext.window.Window",{
title: "My first window",
width: 300,
height: 300,
maximizable : true,
layout: "anchor",
defaults: {xtype: "panel", height: 60, border: false},
items: [
{
title: "Menu",
html: "panel at 100% - 10 px",
anchor:'-10'
},
{
title: "Content",
html: "panel at 70% of anchor",
anchor:'70%'
},
{
title: "3rd Panel",
html: "panel at 50% width and 40% heightof anchor",
anchor:'50% 40%',
bodyStyle:'background-color:#fc3;'
}]
});
win.show();
});
当我们使用anchor的属性只有一个值时, 它表示子组件的宽度,比如 anchor: “70%” 表示子组件的宽度为父容器的70%. anchor: ‘-10’ 表示父容器100% 减去10 px的宽度。 当有两个值是,第一个表示的是width, 第二个为height.
More layouts
更多的布局,比如HBox Layout, VBox Layout, Table Layout等等,你可以能过http://examples.sencha.com/extjs/6.2.0-ea/examples/kitchensink/#layouts.
Component Layout
跟容器的layout用来管理子组件元素的大小和位置,一个Component也可以有它的Layout 用来管理内部元素的大小和位置. Component的布局是通过componentLayout进行配置。
通常,你不需要使用这个配置,因为Ext JS所提供的组件都有它们自己的layout管理器,除非你自己写了一个自定义的组件。大部分组件使用的是Auto Layout. 但有的复杂组件需要自定义的组件布局,比如Panel组件(layout header, footer, toolbars)
Comments about using layouts
你可以通过使用组合容器和布局进行嵌套布局(多种不同的布局类型). 即你可以能过嵌套,组合玩转布局系统, 对于一个Ext JS新手来说,有一个很易犯的错误就是overnesting. 这有时会影响性能, 你需要提前进行规划,使用合适的容器和布局。
Ext.onReady(function(){
Ext.create('Ext.panel.Panel', {
width: 500, height: 300,
title: 'Border Layout',
layout: 'border',
items: [
{// Incorrect Nesting
xtype: 'panel',
title: 'West Region',
region:'west',
width: 200,
collapsible: true,
layout: 'fit'
items:[{
xtype: 'form',
url: 'myForm.php'
items[
// Fields here
]
}]
},{
title: 'Center Region',
region: 'center',
layout: 'fit',
margin: '5 5 0 0',
html:'<b>Main content</b> goes here'
}],
renderTo: Ext.getBody()
});
});
跟你看到的一样,在West 区域,我们设置了一个panel, 它包含一个Ext.form.Panel. 在这里,我们就有多余的嵌套(overnesting), 因为Ext.form.Panel是Panel组件的一个子类。overnesting 只会让我们的浏览器产生过多的 DOM节点。同时创建了两个组件,而不是一个,占用过多的内存。以下是纠正后的代码
{
xtype: 'form',
title: 'West Region',
region:'west',
width: 200,
collapsible: true,
url: 'myForm.php'
items[
// Fields here
]
}
组件与XTemplate
当在一个组件或者容器中使用XTemplate, 它的用法如下
Ext.create('Ext.container.Container', {
renderTo: Ext.getBody(),
data: ["aaabbbddd"],
renderTpl: ['<div>renderTpl</div>'],
html: "hello",
tpl: Ext.create('Ext.XTemplate',
'<i>{0}</i>',
{
compiled: true
})
});
上面的结果是输出renderTpl, 我们需要了解以下重要的知识
- renderTpl用来描述整个组件的结构的模板,在Ext.container.Container的源文件中,默认为
{%this.renderContainer(out,values)%}
, 而模板方法renderContainer定义在Ext.layout.container.Container. 它会读取tpl属性中的内容 - tpl属性用来显示组件的主要内容,如panel组件的body部分
- 在有tpl和data的时,忽略html.
- 对于Ext.button.Button来说,设置tpl和data无效,因为它的源文件里的renderTpl没有调用tpl的内容.