Ext JS 6組件,容器, 佈局

組件

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管理器中註冊我們新創建的組件,以及其它的一些工作。以下是這個階段的所有步聚

這裏寫圖片描述

以下是每一步詳細的解釋

  1. 第一步,我們將配置屬性應用到我們正在創建的實例上。在上面的代碼中,title, width, height, renderTo屬性,會被複制到panel實例中,以及任何我們其它定義的屬性
  2. 第二步定義常見的事件,比如enable, disable, show等。每個組件都擁有這些事件
  3. 爲這個實例分配一個唯一標識符。如果我在配置中有定義id(bad practice), 則使用配置中的ID
  4. 驗證我們是否有在配置中指定plugins, 如果指定,則創建這些插件的實例。插件既爲我們創建的組件的一個額外功能。在上面我們沒有定義任何插件,所有這一步跳過
  5. 執行initComponent函數,它是一個模板方法,會在constructor中調用, 如果我們想在實例創建時,執行自定義的代碼,應該在subclasses中重寫這個方法, 當然Component在不同的階段,都提供了template方法,能讓我們添加額外的功能
  6. 在這個步聚,我們將新創建好的實例添加到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實例數組

  7. Component包含兩個mixins類, 一個是事件管理器,另一個我們組件的狀態。

  8. 如果我們有定義plugins, 在上面的步聚中我們創建了這些插件的實例,現在,調用每個插件的init()方法,並用傳遞當前組件給它們,進行初始化。
  9. 如果在配置中有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中, 樣式和事件監聽器將被應用,所以我們能夠看到新的組件外觀,並且與它交互(事件).

這裏寫圖片描述

  1. 觸發beforeRender事件,如果它的監聽器返回false, 則停止渲染
  2. 確認組件是否floating組件,即在配置中指定floating爲true. 常見的組件有menu, window. 如果是,分配z-index屬性。
  3. 創建一個container屬性,並且將一個DOM元素賦值給它,表示組件將在哪裏被渲染, container屬性是一個Ext.dom.Element實例
  4. 組件的模板方法onRender被執行,創建一個el屬性,它表示組件的主節點元素。我們可以爲組件定義一個html模板,然後會被創建,並且添加到主節點中。我們可以重寫onRender方法,添加指定的節點到DOM中。
  5. 設置顯示模式,既根據配置中的hideMode, 它的值可以爲(display, visibility or offset)
  6. 如果有設置overClas屬性,則監聽鼠標移入和移出事件,用來添加和刪除這個css類。
  7. 在第七步,觸發render事件,組件實例將作爲參數傳遞給事件處理器
  8. 第八步用來初始化內容。有以下三個方法來設置組件的內容

    • 可以在組件的屬性中定義一個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>"]
    });
  9. 返回到render階段,下一步執行afterRender模板方法. 如果一個組件包含了子組件,子組件將在這一步渲染。我們在container之後討論.

  10. 在上一步,afterRender事件觸發。我們可以在subclass中監聽這個事件,在所有的節點都被渲染到DOM後,執行一此動作。
  11. 在上一步,註冊鼠標,鍵盤,大小等監聽器
  12. 最後一步,如果有設置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屬性可以用來銷燬。(默認情況下,已經設置過了),銷燬階段將在用戶關閉窗口後被調用

這裏寫圖片描述

  1. 銷燬階段在開始時,會先觸發beforeDestroy事件,如果事件處理器返回false, 則停止銷燬。如果繼續,並且組件是floating類型的,則從floating manager中取消註冊。
  2. 執行模板方法beforeDestroy,所有subclasses通過使用這個方法來刪除它們的子元素或者清理內存
  3. 在第三步,如果將被銷燬的組件是其它組件的子組件,那麼在父組件中,這個組件的引用,將被刪除
  4. onDestroy方法將被執行,這是一個模板方法,執行這個方法是爲了銷燬我們組件的屬性,並且確保它的子組件(已經被添加到當前組件)也被銷燬, 同時,也清理我們自己創建的自定義監聽器
  5. 第五步,銷燬所有的插件
  6. 如果組件已經被渲染了,則從DOM中刪除所有的組件節點,和節點所對應的監聽器
  7. 觸發destroy事件,我們可以監聽這個事件,執行相應的動作
  8. 在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"
        }]
    });
});

查看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使用多個組件作爲容器使用,它們每一個有它自已的功能。以下是常用容器列表

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的內容.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章