JavaScript開發中ExtJS6組件,容器,佈局介紹

組件

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-p-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: "

Hello!

This is an example of content

" }); //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: ["

Content

Hello {name} {lastName}!

"] }); 返回到render階段,下一步執行afterRender模板方法. 如果一個組件包含瞭子組件,子組件將在這一步渲染。我們在container之後討論. 在上一步,afterRender事件觸發。我們可以在subclass中監聽這個事件,在所有的節點都被渲染到DOM後,執行一此動作。 在上一步,註冊鼠標,鍵盤,大小等監聽器 最後一步,如果有設置hidden屬性,則隱藏主組件。同樣,如果設置瞭disabled為true. 則組件執行disable方法,它會為組件的主節點添加css類,使得組件的外觀表現為disabled, 並且在DOM上面的html標簽為disable標志

以下的代碼顯示瞭渲染階段是如何工作的,我們整個的處理都是從調用render方法開始

var mycmp = Ext.create("Ext.Component",{
    width: 300,
    height: 150,
    data: {
    name:"Veronica",
    lastName:"Sanchez"
    },
    tpl:["

Content

Hello {name} {lastName}!

"] }); //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: 'p',
            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('p').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('p').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就浪費瞭資源。

發佈留言