Pages

搜尋此網誌

2013年12月25日 星期三

extjs4: mvc 使用簡介與範例介紹

extjs4: mvc 使用簡介與範例介紹

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 的其他邏輯

對於 extjs 4 的 mvc 有點概念後,我們可以實際來看例子進一步了解運作的方式。

Application

每個 ExtJS 4 的應用都從一個 Application 開始,這個實例包含應用的全域配置(例應用的名字),這個實例也負責維護對全部模型、視圖、控制器的引用的維護,還有一個 launch 函數,會在所有加載項加載完成之後呼叫。首先需要選擇一個全域命名空間,所有 ExtJS 4 應用都需要有一個全域命名空間,以讓所有應用中的 class 安放到其中


    Ext.Loader.setConfig({
        enabled: true,
        //disableCaching: true //強制關閉 cache
    });

    Ext.application({
        name: 'Frontend',   //app folder 別名為 Frontend
        appFolder: 'app',   //檔案 root 存放位置
        controllers: [

        'SYS.SYS001',   //使用者登入
        'SYS.SYS002',   //主畫面
        'MN.MNM001',     //群組權限維護
        'US.USM002'    //使用者群組維護
        ],
        launch: function() {
            Ext.create('Ext.container.Viewport', {
                id: 'mainVP',
                layout: 'fit',
                items: [{
                    xtype: 'sys001loginform',
                    url: 'http://localhost:8080/agricloud/user/login.json'
                }]
            });
        }
    });

Model

定義來源資料的 mapping 以及資料型態

    // app/model/MN/MNM001/MenuGroup.js
    Ext.define('Frontend.model.MN.MNM001.MenuGroup', {
        extend: 'Ext.data.Model',
        fields: [
            {name: 'menuId', type: 'string'},
            {name: 'menuDes',  type: 'string'},
            {name: 'userGroupId',  type: 'string'},
            {name: 'userGroupDes',  type: 'string'}
        ]
    });

Store

設定資料獲取的來源,以及取得資料的方式,載入上一節的 model Frontend.model.MN.MNM001.MenuGroup。一旦資料讀取成功,將會依據 model 進行資料 mapping 作為 view 層的資料呈現來源。

    // app.store.MN.MNM001.Store.js
    Ext.define('Frontend.store.MN.MNM001.Store' ,{
        model:'Frontend.model.MN.MNM001.MenuGroup', 
        extend: 'Ext.data.Store',
        alias : 'widget.mnm001store',
        autoLoad: true,
        proxy: {
            type: 'rest',   // 使用 rest 來與後端 server 溝通
            url: 'http://localhost:8080/agricloud/rest/menuGroup/',
            reader: {
                type: 'json',
                root: 'items'
            },
            writer: {
                type: 'json'
            },
            afterRequest:function(request,success){
                var operation = request.operation;
                var response = operation.response; 
                if(success){
                }else{}
            }
        },
        listeners: {
            // 讀取完資料後,進行 load 
            write: function(store, operation) {
                store.load();
            }
        }
    });

Controller

用於整合 model 的資料讓 view 可以使用,並且定義 view 各個事件要執行的內容,如此一來 view 將可以被獨立設計,一旦 view 被使用於別的功能也不會有衝突的事件定義。在使用上 Controller 也可以進行繼承,下面的 parent Controller 範例實作一些常用的事件,因為對於 controller 而言,所載入的各個 store 都視為 controller 的屬性,故只要繼承至 parent Controller 都有給定要求的變數內容,所定義的事件將會運作正常

parent Controller

    /**
     * std Controller
     */
    Ext.define('Frontend.controller.common.Standard', {
        extend: 'Ext.app.Controller',
        execute: function(params) {
            var tab = Ext.getCmp('mainTab');
            tab.add({
                id: params.id,
                title: params.title,
                closable: true,
                layout:'fit',
                items: {
                    xtype: params.tabXtype
                }
            });
        },
        doRead: function() {
            console.log("doRead")
            this.store.load();
        },
        doCreate: function() {
            this.store.insert(0, this.model);
        },
        doDelete:function(){
            var selection = this.grid.getSelectionModel().getSelection()[0];
            if (selection) {
                this.store.remove(selection);
            }
        },
        doUpdate: function() {
            this.store.sync({
                success : function(){
                    console.log("success");
                    Ext.Msg.alert('Status', '更新成功');
                },
                failure : function(response, options){
                    console.log("failure");
                    Ext.Msg.alert('Status', '更新失敗');
                }  
            });
        },
        onGridSelection:function(selModel, selections,eOpts){
            this.selections=selections;
            this.selModel=selModel;

            //必須利用refs 取得實體介面已 render的物件
            this.deleteButton.setDisabled(selections.length === 0);

        },
        onPanelRendered: function() {
            //將載入的 view 指定為此 controller 的屬性
            this.grid=this.getGridPanel();
            this.deleteButton=this.getDeleteButton();
        }
    });

main controller

繼承於上一節的 parent Controller,在主要使用的 controller 需透過 viewsstoresmodels 將對應的 mvc 載入,以及透過 refs 將需要控制的 Component 找出來並且給予別名,如此一來,我們在之後 init 事件定義元件的動作時可以方便的參照相關的元件。

    // app.controller.MN.MNM001.js
    /**
     * 功能群組維護
     */
    Ext.define('Frontend.controller.MN.MNM001', {
        extend: 'Frontend.controller.common.Standard',
        views: [
            'MN.MNM001.Panel'
        ], 

        refs: [{
            ref: 'deleteButton',
            selector: 'mnm001panel commonbuttondelete'
        },{
            ref: 'gridPanel',
            selector: 'mnm001panel'
        },{
            ref: 'combobox',
            selector: 'mnm001combobox'
        }],
        init: function() {

            定義各個元件所要執行的事件以及相關的函數
            this.control({
                'mnm001panel commonbuttoncreate': {
                    click: this.doCreate
                },
                'mnm001panel commonbuttondelete': {
                    click: this.doDelete
                },
                'mnm001panel commonbuttonupdate': {
                    click: this.doUpdate
                },
                'mnm001panel commonbuttonread': {
                    click: this.doRead
                },
                'mnm001combobox': {
                    select:this.comboboxSelect
                },
                'mnm001panel': {
                    selectionchange: this.onGridSelection,
                    render: this.onPanelRendered
                }
            })
            //ref 的使用對象為 Conpoment
            //store 的取得直接用 this 
            //在 view 所組成的 compoment 中有使用到的 store 皆可以在 controllr 中存取

            this.store=this.getStore("MN.MNM001.Store");

        },
        execute: function(params) {
            //定義此 contoller 所使用之屬性,controller 一旦被執行此事件將會最先被執行
            params.tabXtype='mnm001panel';
           //結果將透過下列程式呼叫繼承來的 controller 之 execute
            this.callParent(arguments);
        },
        comboboxSelect: function( combo, records, eOpts ) {
            this.selections[0].set('userGroupDes',records[0].get('description') );
        }
    });

View

view 的使用依賴於 controller 有載入的內容,在這邊只作為介面的呈現,不進行事件的定義,除非該事件為通用的事件。

Combobox

使用的第一個 store US.USM002.Store

    Ext.define('Frontend.view.MN.MNM001.Combobox', {
        extend: 'Ext.form.ComboBox',
        alias : 'widget.mnm001combobox',
        id:'mnm001combobox',
        itemId:'mnm001combobox',
        store: 'US.USM002.Store',
        queryMode: 'local',
        displayField: 'userGroupId',
        valueField: 'userGroupId'
    });

Panel

使用的第二個 store MN.MNM001.Store,其中 Frontend.view.common.gridpanel.standard 是自定義通用的 gridpanel,假設你有第二個類似的功能就可以繼承他,只要修改 columns 的定義即可,並且載入額外套件。

    Ext.define('Frontend.view.MN.MNM001.Panel' ,{
        extend: 'Frontend.view.common.gridpanel.standard',
        alias : 'widget.mnm001panel',
        requires:['Frontend.view.MN.MNM001.Combobox'],
        columns: [
            { 
                header: 'menuId',  
                dataIndex: 'menuId', 
                flex: 1,
                field: {
                    xtype: 'textfield'
                }
            },
            { 
                header: 'menuDes', 
                dataIndex: 'menuDes'
            },
            { 
                header: 'userGroupId', 
                dataIndex: 'userGroupId',
                field: {
                    xtype: 'mnm001combobox'
                }
            },
            { 
                header: 'userGroupDes', 
                dataIndex: 'userGroupDes' 
            }
        ],
        plugins: [{
                ptype:'rowediting'
        }],
        store:'MN.MNM001.Store'
    });

執行結果

功能一

image

功能二

image

可以看到兩個功能外框是類似的,差別在欄位的不同,其中共用的部分:

  • 功能二的 grid 與功能一的 userGroupId 的 combobox store 為同一個 US.USM002.Store
  • 兩個功能之 panal 皆繼承於 Frontend.view.common.gridpanel.standard
  • controller 也繼承於 Frontend.controller.common.Standard

透過 extjs 的模組化功能,我們可以很輕易的將重覆的程式碼進行利用,在 mvc 三個維度也可以很方便進行分工,只要各個元件的 id 索引定義清楚,幾乎可以獨立開發,剩下的就是最後的整合運作需要一起 debug,extjs 在前端 mvc 的架構卻實作的蠻漂亮的,有興趣可以參考一下。

張貼留言