Ext JS应用的UI是由一个或者多个widgets组称, 我们称之为Components. 所有的组件都是Ext.Component的子类,允许组件自动管理生命周期, 包括instantiation, rendering, sizing and positioning, 以及destruction. Ext JS提供了很多直接可以使用的组件, 能过简单继承,可以创建自定义组件。
在我们讲布局系统和窗口部件之前,我们需要先知道组件是如何工作的
在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被创建/渲染时,自动渲染
渲染阶段只在组件还没有被渲染时发生。在这个阶段,所有的节点将被插入到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方法。
这个阶段主要是清除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来自定义我们的组件。下面的例子显示了,在生命周期的某一个阶段,我们可以通过重写某些方法,实现额外的功能
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中,理解组件的生命周期对于添加自动义事件和监听器来说非常重要,这样我们才能在我们的应用程序中提供适当的功能和自定义的代码。
一个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
每一个组件都有一个像征性的名字,称为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.'); } } } ] });所有的组件都有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 componentFloating 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,居中对齐Ext.Base 是所有类的父类,它的原型和静态方法都会被其它类所继承。
虽然你可以扩展最底层的 Ext.Base, 但在很多情况下,开发者想要在更高级的类开始扩展
下面的代码创建了一个Ext.Component的子类
Ext.define('My.custom.Component', { extend: 'Ext.Component', newMethod : function() { //... } });上面的代码创建了一个新的类,My.custom.Component, 它继承了Ext.Component所有的功能(methods, properties etc).
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 在组件被布局前调用选择最合适的类去扩展是非常重要的, 这个基础类必须提供符合我们要求的功能。通常我们选择Ext.panel.Panel, 它可以被渲染,也可以管理其它组件
Panel class有以下的功能
BorderHeaderHeader toolsFooterFooter buttonsTop toolbarBottom toolbarContaining and managing child Components 如果你定义的组件不需要上面的功能,则使用Panel就浪费了资源如果一个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
如果创建的组件只是用来包含其它的组件,而不需要我们在上面提及的Panel的功能。则可以使用 Ext.container.Container. 在Container级别,需要记住的是使用Ext.layout.container.Container 管渲染和管理子组件
Container还包含以下的额外template 方法: * onBeforeAdd 这个方法在添加一个新的组件时调用,它传递了一个新的组件,我们可以修改这个组件,如果返回false, 则终此添加操作 * onAdd 在一个组件添加完成后调用。它传递已经添加好的组件。这个方法用来根据子组件items的状态,更新内部的结构。 * onRemove 在一个新的组件被删除后调用,传递这个被删除的组件。根据子组件的items的状态,更新内部的结构 * beforeLayout 在容器对它的子组件布局前调用 * afterLayout 在容器对它的子组件布局后调用
如果创建的组件,必须有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元素被删除时调用在当前我们知道了组件生命周期的所有阶段,其中有一个阶段就是组件的子组件也会被渲染。现在我们将学习什么是容器,并且如何为它添加子组件。
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" }] }); });查看Ext js中的所有xtype定义枚举
以下是上面代码的效果图,它由一个主组件包含两个子组件。
当我们使用容器时,我们可以在主容器中使用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使用多个组件作为容器使用,它们每一个有它自已的功能。以下是常用容器列表
ContainerDescriptionExt.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如我们上面说的,它表示的是整个应用程序的可视区域,并且我们在一个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可以包含其它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就是一个浮动的面板,并且包含更多的功能。它继承于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
每一个容器都有一个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 layoutThe Fit layoutThe Card layoutThe Accordion layoutThe Anchor 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将一个容器空间划分成五个区域,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 面板的大小.
这种布局适用于只有一个子组件的容器。它许我们将容器内部的组件,占据整个容器的大小。当容器的大小发生改变,子组件的大小也会发生可变,以适合(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, 则在改变容器大小时,会出现如下情况
卡片布局可以用来管理多个子组件,所以如果我们需要创建一个向导(下一步下一步)或者一次只显示一个组件,我们应该使用这种布局。 这个布局继承于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方法,来显示上一张和下一张卡片.
跟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(); })这个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.
更多的布局,比如HBox Layout, VBox Layout, Table Layout等等,你可以能过http://examples.sencha.com/extjs/6.2.0-ea/examples/kitchensink/#layouts.
跟容器的layout用来管理子组件元素的大小和位置,一个Component也可以有它的Layout 用来管理内部元素的大小和位置. Component的布局是通过componentLayout进行配置。
通常,你不需要使用这个配置,因为Ext JS所提供的组件都有它们自己的layout管理器,除非你自己写了一个自定义的组件。大部分组件使用的是Auto Layout. 但有的复杂组件需要自定义的组件布局,比如Panel组件(layout header, footer, toolbars)
你可以通过使用组合容器和布局进行嵌套布局(多种不同的布局类型). 即你可以能过嵌套,组合玩转布局系统, 对于一个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, 它的用法如下
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的内容.