Pages

搜尋此網誌

2013年12月25日 星期三

extjs mvc 架構:繼承與模組重覆使用

extjs mvc 架構:繼承與模組重覆使用

兩篇對 extjs 的基本介紹後,總算要進入主菜了,也就是在 extjs 中的物件導向 MVC 架構,物件導向的觀念,在網路上或書上都可以找到相關資料,用不用的好就是一個人的功力,此篇文章中,將透過幾個例子讓讀者了解 extjs 的物件導向操作。


ExtJS 4 MVC架構講解

引用上述文章的開頭:

大規模客戶端應用通常不好實現不好組織也不好維護,因為功能和人力的不斷增加,這些應用的規模很快就會超出掌控能力,ExtJS 4 帶來了一個新的應用架構,不但可以組織代碼,還可以減少實現的內容 新的應用架構遵照一個類 MVC 的模式,模型(Models)和控制器(Controllers)首次被引入。業界有很 ​​多種 MVC 架構,基本大同小異,ExtJS 4的定義如下:

  • Model: 資料的集合,例如 User 帶有 username 和 password 的資料,model 知道如何持久化自己的數據,並且可以和其他 model 關聯,model 跟 ExtJS 3中 的 Record 有點像(區別是,Record 只是單純的扁平結構,而 Model 可以 nest ),通常都用在 Store 中去展示 grid 和其他組件的資料
  • View: 用於界面展示– grid, tree, panel都是view
  • Controllers: 安放所有使你的 app 正確工作的代碼的位置,具體一點應該是所有動作,例如如何渲染 view,如何初始化 model,和 app 的其他邏輯

在我早先的令一篇文章中也有引用到,Extjs4: Mvc 使用簡介與範例介紹,在該篇文章中說明到較多的實作細節,本篇專注於 mvc 結構與特性,如果讀者有興趣也可以參考閱讀,在開始之前,先提一下兩個重要的物件導向觀念:

  • 單一責任原則(single responsibility principle:SRP):當一個類別需要修改時應該只有一個理由。
  • 關注點分離(separation of conserns:SoC):把應用程式功能分解成數個不重疊的模組。

遵循這兩個原則,最重要的好處就是要能夠重覆使類別,令單一類別不要過於複雜,如此一來程式在設計上就可以比較接近組合積木,一個個零件完成後,可以根據不同的狀況組合。

為了能夠設計出可以重覆使用的類別,將針對三種不同的課題,繼承,混和以及 model 關連建立,就讓我們從 mvc 中的 model 資料模型開始。

model - 關連建立

假設我們設計一個應用程式,其資料模型如下:

image

可以看到有三個 table:item(品項),itemImage(品項圖片),batch(批號),其中 item 可以有多張圖片,批號屬於 item,根據這樣的描述,可以整理出兩個關係:

  • item 可以有多張圖片:item hasMany itemImage
  • 批號屬於 item:batch belongsTo item

根據這樣的關係,我們可以利用 extjs 來建立對應的類別。

三個資料表的基本宣告程式碼如下:

//品項
Ext.define('finder_extjs.model.Item', {
    extend: 'Ext.data.Model',

    fields: [
        {
            name: 'id'
        },
        {
            name: 'name'
        },
        {
            name: 'title'
        }
    ]
});

//圖片
Ext.define('finder_extjs.model.ItemImage', {
    extend: 'Ext.data.Model',

    fields: [
        {
            name: 'id'
        },
        {
            mapping: 'item.id',
            name: 'item_id'
        }
    ]
});

//批號
Ext.define('finder_extjs.model.batch', {
    extend: 'Ext.data.Model',

    fields: [
        {
            name: 'id'
        },
        {
            mapping: 'item.id',
            name: 'item_id'
        },
        {
            name: 'name'
        }
    ]
});

如此一來,三個資料表算是建立完成,但是彼此之間都是獨立沒有關係的,為此,我們需要要在加上關連的定義。

//item hasMany itemImage
Ext.define('finder_extjs.model.Item', {
    extend: 'Ext.data.Model',
    //使用到的關連 model 務必引入  
    uses: [
        'finder_extjs.model.ItemImage'
    ],  
    ...
    hasMany: {
        associationKey: 'itemImages',
        model: 'finder_extjs.model.ItemImage',

        // 設定 foreignKey 將會作為查詢的 property name
        foreignKey: 'item_id',
        //跟據 name 將會產生對應的參照 function
        name: 'itemImages' 
    }

});

多定義了關係一定要有用處,不然何必要花時間,實際在使用上我們可以:

//item hasMany itemImage

var item = new finder_extjs.model.Item({id:1});

// 一旦定義 hasMany extjs 將自動產生對應 name 的 function 回傳關連資料的 store 實體
//取得關連資料
item.itemImage().load();

在上面的程式碼中,如果你有定義 model 的連結方式,實際上將會透過 http request 向後端查詢資料,並且帶有參數為:filter:[{"property":"item_id","value":1}],一旦結果回傳,會根據 model 的 fields mapping 相關的欄位資料,如此一來當你有存取相關的資料時,就可以很方便的使用,免去撰寫重覆 ajax request 的步驟,接著來看 belongsTo 的例子。

//batch belongsTo item
Ext.define('finder_extjs.model.batch', {
    extend: 'Ext.data.Model',
    uses: [
        'finder_extjs.model.Item'
    ],
    ...
    belongsTo: {
        model: 'finder_extjs.model.Item',
        // 根據定義的 getterName 產生 function 來取得所屬 model 的參照
        getterName: 'getItem'  
    }
});

var batch = new finder_extjs.model.Batch({id:1});

// 根據 getterName 的設定將自動產生對應 name 的 function 回傳關連資料的 record
// 取得關連資料
batch.getItem();

同樣的一旦呼叫了 batch.getItem(); 也會向後端請求取得資料,如此一來,只要資料結構與關係定義好,往後在使用時,就可以很方便的取得,甚至妳也可以這樣使用:batch.getItem().itemImages().load()

在資料串查上,可以從關關連快速完成資料的取得,再來我們就來看看,mvc 中的 view 如何在 extjs 物件化,並且能夠重覆使用。

view - 繼承與連結

單純的建立 view 的元件這邊不多做說明,主要還是針對 extjs 物件導向特性來做說明,假設我們需要建立如同下圖的畫面:

image

在圖片中可以看到主要是由 form 以及 grid 所組成,有個共同的地方,就是兩者皆有同樣的 toolbar,因此在設計的時候,我們可以將 toolbar 獨立成一個類別,在兩個地方引入,如下圖:

image

上圖的結構是利用 sencha Architect 所呈現的,可以看到元件 StdEditorToolbar 就是獨立的類別,藍色線代表作為連結使用在另外兩個自定元件裡面,如此一來相同的元件與 ID 定義,就不需要重覆多次,在索引時也較一致性,如果讀者有看我上一篇 ID 與 itemId 的不同,就會知道這樣是可行的,而因為 extjs 4 在這次多了 controller 的類別,所以我們的 view 層都是單純的介面元件,沒有特殊的邏輯,所以可以重覆使用,除此之外,我們也可以交由多人協助完成相關的元件,最後在將他們組合成最終要呈現的畫面,參考上一張圖片的樹狀結構,最終的呈現將由三個主要元件組成,ItemGrid,ItemViewer,ItemEdtor,步驟上就可以先將 ItemGrid,ItemViewer 完成以後,ItemEdtor 作為 tabpanel 連結 ItemGrid,ItemViewer 完成整個畫面,如下圖:

image

image

就是如此簡單愉快,上面所完成的自定元件都使用到了在物件導向中繼承的特性,基本上都是經由繼承 extjs 的元件加以客製化,當然自定元件同樣也可以作為別人的父類別,端看如何設計。mvc 中的前兩項完成了,接著我們就來看最後一個 C,controller 的部份。

Controller - 混和

作為 view 與 model 的溝通橋樑,與操作 view 中元件的互動,Controller 扮演的角色是很重要的,他必須要很方便存取以及定義欲操作的元件,extjs 在這邊也確實做到了,還記得上一篇最提到的關於 extjs 如何查詢 component,當你在撰寫 Controller 將會大量使用到,延續上一節的 view 中三個元件:ItemGrid,ItemViewer,ItemEdtor,來進行說明。

  • ItemGrid:如果是作為 grid 單獨存在,將只會有讀取資料的功能,因此在 controller 中,我們只需要定義 read 的功能實作。
  • ItemViewer:作為 Viewer 本身只是個空殼,需要由外部傳入資料後進行資料呈現,因此我們需要定義得是 display 的功能
  • ItemEditor:組合了 ItemGrid 與 ItemViewer,兩個介面,也因此實作的內容就為兩者之間的互動,一旦點選 ItemGrid 中的資料,就將其作為 ItemViewer 的資料來源進行呈現,因此我們要實作的是 readItem。

大致上解釋一下設計概念後,粗糙一點我們可以把上面三個功能都定義在 ItemEditor,但如果 ItemGrid 以及 ItemViewer 會作為獨立功能,或者組合到不同的介面,這時候我們就必須要寫兩個同樣的 function,這不是個好辦法,因此我們可以利用 extjs 提供的另一個特性:混和(mixsins),在系列文章的第一篇有提到,一旦我們個別定義好 ItemGrid,ItemViewer 的邏輯函式,我們可以將他混和入 ItemEditor 如此一來就可以各自專注各自的責任,除了可以組合在一起,也可以各自獨立運作,是不是很美好呢!接著我們就來看範例程式碼與結構:

image

參考上圖,可以看到我們為了三個介面分別做了三個 controller,就如同前面提到的三點,個別設定了三個函數,在這邊我就不詳述各個 controller 的實作,直接來看 ItemEditorController:

Ext.define('finder_extjs.controller.ItemEditorController', {
    extend: 'Ext.app.Controller',

    // 混和: ItemViewerController 以及 ItemGridController
    mixins: {
        itemViewerCtrl: 'finder_extjs.controller.ItemViewerController',
        ItemGridCtrl: 'finder_extjs.controller.ItemGridController'
    },

    // 目標 views
    views: [
        'ItemEditor'
    ],

    // 定義操作對象
    refs: [
        {
            ref: 'itemViewer',
            selector: 'itemviewer'
        },
        {
            ref: 'itemGrid',
            selector: 'itemgrid'
        },
        {
            ref: 'itemEditor',
            selector: 'itemeditor'
        }
    ],

    init: function(application) {
            // event binding
        this.control({
            'itemeditor button[itemId=readBtn]':{
                click:this.doRead
            },
            //一旦 grid 中的資料被雙擊,執行 readItem
            'itemeditor grid[itemId=itemGrid]':{
                itemdblclick:this.readItem
            }
        });
    },

    // 將傳入的目標資料,作為顯示的資料來源進行呈現
    readItem: function(obj, record, item, index, e, eOpts) {
        this.getItemEditor().setActiveTab(this.getItemViewer().up());

        //屬於 itemViewerController 所定義的函數
        this.doDisplay(record);
    }

});

這篇文章中,說明了 MVC 的運作,以及個別利用了物件導向開發中的資料關連,繼承以及混和;當然不只這些,還有很多奇巧淫技等著學習並熟練,程式的世界是學也學不完,希望藉由這樣的說明,能夠讓讀者有個概念,在使用 extjs 這樣的物件導向設計時可以有個參考,文章進行到這裡,對於 extjs 的概念與使用,相信應該有些了解,不過如果有實際經歷過 extjs 開發的讀者應該可以體會,因為他是這樣的複雜,在除錯上有時並不是那麼容易,特別是物件導向式的開發方式會有很多獨立的類別,如何能夠快速開發減少不必要的錯誤,就要善用工具啦!幸好官方提供了 sencha architect,可以幫助我們減少開發中的錯誤,並且快速完成介面的建立,最後要強調的是,ide 只是輔助,請在熟練基礎過後在服用,不然…嘿嘿~

張貼留言