組件
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的內容.