agricloud / foodprint

1 stars 0 forks source link

extjs controller 重構 #55

Open smlsunxie opened 10 years ago

smlsunxie commented 10 years ago

    /*
    * extjs 自定義共用 singleton 類別
    */

Ext.define('foodprint.view.Utilities', {
    extend: 'Ext.Base',

    /*
    * 此為物件別名,也就是說一旦該類別被 require,要使用時不用用全名如 foodprint.view.Utilities
    * 類似 xtype 或是 alias,也可以定義多組比如 ['Utilities' , 'Util'] 如此兩個變數名稱將都會指向 foodprint.view.Utilities
    */
    alternateClassName: [
        'Utilities'
    ],

    /*
    * 類別實體化只會產生一次作為全域呼叫,且只要定義為 singleton 實際上為 object,而不是 class
    * 可以把定義為 singleton 的類別視為全域的物件
    */
    singleton: true,

    respondFailure: function(action, config) {

        Ext.MessageBox.alert('Failure',action.result.message);

        if(action.url.indexOf('create')!=-1){
            config.toolbar().down('button[itemId=commonSaveBtn]').setDisabled(false);   
        }else {
            config.toolbar().down('button[itemId=commonDeleteBtn]').setDisabled(false);
            config.toolbar().down('button[itemId=commonUpdateBtn]').setDisabled(false);

        }

    },

    respondSuccess: function(action, config) {
        Ext.MessageBox.alert('Success',action.result.message);

        console.log(config.grid());
        config.grid().getStore().reload();
        config.form().getForm().reset(true);
    },

    doRead: function(config) {

        config.form().doDisplay(config.grid().getSelectionModel().getSelection()[0]);
        config.toolbar().down('button[itemId=commonDeleteBtn]').setDisabled(false);
        config.toolbar().down('button[itemId=commonUpdateBtn]').setDisabled(false);
    },

    doUpdate: function(config) {

        console.log("util update");
        config.form().getForm().submit({
            url: '/'+config.domainName+'/update',
            submitEmptyText: false,
            waitMsg: 'Updating Data...',
            success: function(form,action) {
                Utilities.respondSuccess(action,config);
            },
            failure: function(form,action) {
                Utilities.respondFailure(action,config);
            }
        });
    },

    doDelete: function(config) {
        config.form().getForm().submit({
            url: '/'+config.domainName+'/delete',
            submitEmptyText: false,
            waitMsg: 'Deleting Data...',
            success: function(form,action) {
                Utilities.respondSuccess(action,config);
            },
            failure: function(form,action) {
                Utilities.respondFailure(action,config);
            }
        });

    },

    doCreate: function(config) {

        console.log("util create");
        config.form().getForm().submit({
            url: '/'+config.domainName+'/create',
            submitEmptyText: false,
            waitMsg: 'Saving Data...',
            success: function(form,action) {
                Utilities.respondSuccess(action,config);
            },
            failure: function(form,action) {
                Utilities.respondFailure(action,config);
            }
        });
    },

    validityChange: function(basic, valid,config) {
        config.form().getForm().updateRecord();

        if(basic.getRecord()!=null && basic.getRecord().getData().id!=null){
            if(valid){
                config.toolbar().down('button[itemId=commonUpdateBtn]').setDisabled(false);
            }
            else{
                config.toolbar().down('button[itemId=commonUpdateBtn]').setDisabled(true);
            }
        }
        else{
            if(valid){
                config.toolbar().down('button[itemId=commonSaveBtn]').setDisabled(false);
            }
            else{
                config.toolbar().down('button[itemId=commonSaveBtn]').setDisabled(true);
            }
        }
    }

});

上述類別使用於 controller 範例

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

    /*
    * 共用類別,作為統一訊息以及 form, grid, toolbar 裡 button 相關互動
    * 屬於 editor 的請引入
    */
    requires: [
        'foodprint.view.Utilities'
    ],

    models: [
        'Operation',
        'Item',
        'Workstation',
        'ItemRoute'
    ],
    stores: [
        'OperationStore',
        'WorkstationStore',
        'ItemStore',
        'ItemRouteDeepStore'
    ],
    views: [
        'ItemEditorCt'
    ],

    /*
    * refs 的起頭一律需要為主要 container,已下面例子皆為 itemeditorct
    * 避免不同作業之間共用元件互相影響,比如說
    * 現在 userEditor 的 readBtn 呼叫的是 batchEditor 的 read,是錯誤的
    */
    refs: [
        {
            ref: 'itemGrid',
            selector: 'itemeditorct itemgrid'
        },
        {
            ref: 'itemForm',
            selector: 'itemeditorct #itemForm'
        },
        {
            ref: 'commonImageUploader',
            selector: 'itemeditorct commonimageuploader'
        },
        {
            ref: 'itemToolbar',
            selector: 'itemeditorct #itemToolbar'
        },
        {
            ref: 'itemRouteToolbar',
            selector: 'itemeditorct itemviewer commoneditortoolbar'
        },
        {
            ref: 'itemRouteGrid',
            selector: 'itemeditorct #itemRouteGrid'
        },
        {
            ref: 'itemRouteForm',
            selector: 'itemeditorct #itemRouteForm'
        },
        {
            ref: 'itemEditor',
            selector: 'itemeditorct itemeditor'
        },
        {
            ref: 'itemEditorCt',
            selector: 'itemeditorct'
        },
        {
            ref: 'itemViewer',
            selector: 'itemeditorct itemviewer'
        }
    ],

    init: function(application) {

        /*
        * event binding 同樣需要以 container 為開頭命名
        */
        this.control({
            'itemeditorct #itemToolbar button[itemId=commonCreateBtn]':{
                click:this.doCreate
            },
            'itemeditorct #itemToolbar button[itemId=commonDeleteBtn]':{
                click:this.doDelete
            },
            'itemeditorct #itemToolbar button[itemId=commonSaveBtn]':{
                click:this.doSave
            },
            'itemeditorct #itemToolbar button[itemId=commonReadBtn]':{
                click:this.doRead
            },
            'itemeditorct #itemToolbar button[itemId=commonUpdateBtn]':{
                click:this.doUpdate
            },
            'itemeditorct grid[itemId=itemGrid]':{
                itemdblclick:this.readItem
            },
            'itemeditorct #itemRouteToolbar button[itemId=commonCreateBtn]':{
                click:this.itemRouteCreate
            },
            'itemeditorct #itemRouteToolbar button[itemId=commonDeleteBtn]':{
                click:this.itemRouteDelete
            },
            'itemeditorct #itemRouteToolbar button[itemId=commonSaveBtn]':{
                click:this.itemRouteSave
            },
            'itemeditorct #itemRouteToolbar button[itemId=commonUpdateBtn]':{
                click:this.itemRouteUpdate
            },
            //    'itemeditorct panel[itemId=itemEditor]':{
            //        tabchange:this.onTabChange
            //    },
            'itemeditorct itemviewer form[itemId=itemForm]':{
                validitychange:this.onItemFormValidityChange
            },   
            'itemeditorct itemviewer grid[itemId=itemRouteGrid]':{
                selectionchange:this.itemRouteGridChange
            }
        });

        /*
        * 作為統一共用元件呼叫用,一組將有三個元件
        * grid 作為資料來源
        * form 作為資料更新操作
        * toolbar 為互動的 button 的集合
        * domainName 作為呼叫後端依據
        */
        this.mainEditor = {
            grid:this.getItemGrid,
            form:this.getItemForm,
            toolbar:this.getItemToolbar,
            domainName:'item' 
        };

        /*
        * 若為一單多身,可在定義一組
        */
        this.subEditor = {
            grid:this.getItemRouteGrid,
            form:this.getItemRouteForm,
            toolbar:this.getItemRouteToolbar,
            domainName:'itemRoute'
        };

    },

    readItem: function(obj, record, item, index, e, eOpts) {
        this.getItemEditor().setActiveTab(this.getItemEditor().down('panel[itemId=itemMainten]'));

        /*
        * 標準 read grid 資料進行 form load,以及 button 互動
        */
        Utilities.doRead(this.mainEditor);

        this.getItemRouteGrid().loadItemRoute(record.data.id);

    },

    doUpdate: function() {
        /*
        * 標準 form 資料更新,訊息輸出,以及 button 互動
        */    
        Utilities.doUpdate(this.mainEditor);

    },

    doCreate: function() {
        this.getItemEditor().setActiveTab(this.getItemViewer().up());
        this.getItemViewer().down('form[itemId=itemForm]').getForm().reset(true);

    },

    doDelete: function() {
        /*
        * 標準刪除資料
        */ 
        Utilities.doUpdate(this.mainEditor);
    },

    doSave: function() {
        /*
        * 標準儲存資料
        */ 
        Utilities.doCreate(this.mainEditor);
    },

    doRead: function() {

        console.log("itemEditorCtrl.doRead");
        this.getItemGrid().loadStore();
    },

    onItemFormValidityChange: function(basic, valid, eOpts) {

        /*
        * 標準資料驗證完成處理
        */  
        Utilities.validityChange(basic, valid, this.mainEditor);
    },

    itemRouteCreate: function() {
        this.getItemViewer().down('form[itemId=itemRouteForm]').getForm().reset(true);

        var store =this.getItemViewer().down('gridpanel[itemId=itemRouteGrid]').getStore();
        var item_id = this.getItemViewer().down('form[itemId=itemForm]').down('numberfield[name=id]').getValue();

        this.getItemViewer().down('form[itemId=itemRouteForm]').getForm().setValues({
            'item.id': item_id,
            sequence:store.getCount()+1
        });
        this.getItemViewer().down('button[itemId=commonSaveBtn]').setDisabled(false);

    },

    itemRouteDelete: function() {
        /*
        * 標準刪除,單身只要丟入 subEditor 即可
        */  
        Utilities.doDelete(this.subEditor);
    },

    itemRouteSave: function() {
        /*
        * 標準儲存,單身只要丟入 subEditor 即可
        */  
        Utilities.doCreate(this.subEditor);
    },

    itemRouteGridChange: function(model, selected, eOpts) {
        if (selected) {
            if(selected!=""){
                this.getItemViewer().down('form[itemId=itemRouteForm]').loadRecord(selected[0]);

                var item_id = this.getItemViewer().down('form[itemId=itemForm]').down('numberfield[name=id]').getValue();

                this.getItemViewer().down('form[itemId=itemRouteForm]').getForm().setValues({
                    'item.id': item_id,
                });

                this.getItemViewer().down('button[itemId=commonDeleteBtn]').setDisabled(false);
                this.getItemViewer().down('button[itemId=commonUpdateBtn]').setDisabled(false);
            }
        }
    },

    itemRouteUpdate: function() {
        /*
        * 標準更新,單身只要丟入 subEditor 即可
        */  
        Utilities.doUpdate(this.subEditor);
    }

});

所有與後端的溝通還有 editor 主要的元件互動,皆定義於共用元件,如此一來只要使用共用元件將會有一致的防呆,訊息輸出,還有操作回饋,未來需要加強驗證,只要在共用元件調整即可同步致所有對應的 controller。

smlsunxie commented 10 years ago

各自完成相關的 controller,做一次以後新增防呆都不用各自動手囉!

pipibabe commented 10 years ago

controller init add container refs

smlsunxie commented 10 years ago

更進階重構

將原在 utilities 的函式移至 commonController 透過 mixins 的特性加入到標準維護的 controller,跟使用 utilities 的差別在於 mixins 特性將可以操作 this 為對象 controller 對於控制上將會更加方便,相關程式說明如下:

Ext.define('foodprint.controller.commonController', {
    extend: 'Ext.Base',

    respondFailure: function(action, config) { }, // 原 utilities

    respondSuccess: function(action, config) { }, // 原 utilities

    doMainEditorUpdate: function() { }, // 主要 editor Update

    doMainEditorCreate: function() { }, // 主要 editor Create

    doMainEditorDelete: function() { },// 主要 editor Delete

    doMainEditorSave: function() { }, // 主要 editor Save

    doSubEditorUpdate: function() { }, // 子單身 editor Update

    doSubEditorDelete: function() { },  // 子單身 editor Delete

    doSubEditorSave: function() { },  // 子單身 editor Save

    onMainFormValidityChange: function(basic, valid, eOpts) { },  // 主要 editor

    doSubEditorCreate: function() { },  // 子單身 editor Create

    readMainForm: function(config) {  },  // 主要 editor 讀取 grid 資料到 form

    submitUpdate: function(config) { }, //與後端溝通 Update

    submitDelete: function(config) {   }, //與後端溝通 Delete

    submitCreate: function(config) {  },  //與後端溝通 Create

    validityChange: function(basic, valid,config) { }, // 主要與子單身共用表單驗證

    activeListTab: function() {  }, //切換至清單頁籤

    activeViewerTab: function() {   }, //切換至編輯頁籤

    readSubForm: function(obj, record, item, index, e, eOpts) {  }, // 子單身 editor 讀取 grid 資料到 form

    cleanSubEditor: function() {  } // 清除子單身表單

});

上述說明每個函式的使用時機,實作細節請看原始碼。

利用上面的 commonController 在透過 mixins 的特性,我們可以簡化 controller 的撰寫,以 itemController 為例:

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

    mixins: {
        commonController: 'foodprint.controller.commonController'
    },

    models: [
        'Operation',
        'Item',
        'Workstation',
        'ItemRoute'
    ],
    stores: [
        'OperationStore',
        'WorkstationStore',
        'ItemStore',
        'ItemRouteDeepStore'
    ],
    views: [
        'ItemEditorCt'
    ],

    refs: [
        {
            ref: 'itemGrid',
            selector: 'itemeditorct #itemGrid'
        },
        {
            ref: 'itemForm',
            selector: 'itemeditorct #itemForm'
        },
        {
            ref: 'commonImageUploader',
            selector: 'itemeditorct commonimageuploader'
        },
        {
            ref: 'itemToolbar',
            selector: 'itemeditorct #itemToolbar'
        },
        {
            ref: 'itemRouteToolbar',
            selector: 'itemeditorct #itemViewer commoneditortoolbar'
        },
        {
            ref: 'itemRouteGrid',
            selector: 'itemeditorct #itemRouteGrid'
        },
        {
            ref: 'itemRouteForm',
            selector: 'itemeditorct #itemRouteForm'
        }
    ],

   //若無特殊的處理,直接 binding commonController 中的函式
    init: function(application) {
        this.control({
            'itemeditorct #itemToolbar button[itemId=commonCreateBtn]':{
                click:this.doMainEditorCreate
            },
            'itemeditorct #itemToolbar button[itemId=commonDeleteBtn]':{
                click:this.doMainEditorDelete
            },
            'itemeditorct #itemToolbar button[itemId=commonSaveBtn]':{
                click:this.doMainEditorSave
            },
            'itemeditorct #itemToolbar button[itemId=commonUpdateBtn]':{
                click:this.doMainEditorUpdate
            },
            'itemeditorct grid[itemId=itemGrid]':{
                itemdblclick:this.readItemForm
            },
            'itemeditorct #itemRouteToolbar button[itemId=commonCreateBtn]':{
                click:this.itemRouteCreate
            },
            'itemeditorct #itemRouteToolbar button[itemId=commonDeleteBtn]':{
                click:this.doSubEditorDelete
            },
            'itemeditorct #itemRouteToolbar button[itemId=commonSaveBtn]':{
                click:this.doSubEditorSave
            },
            'itemeditorct #itemRouteToolbar button[itemId=commonUpdateBtn]':{
                click:this.doSubEditorUpdate
            },
            'itemeditorct #itemViewer form[itemId=itemForm]':{
                validitychange:this.onMainFormValidityChange
            },   
            'itemeditorct #itemViewer grid[itemId=itemRouteGrid]':{
                itemdblclick:this.readItemRouteForm
            }
        });

        this.mainEditor = {
            grid:this.getItemGrid,
            form:this.getItemForm,
            toolbar:this.getItemToolbar,
            domainName:'item'
        };
        this.subEditor = {
            grid:this.getItemRouteGrid,
            form:this.getItemRouteForm,
            toolbar:this.getItemRouteToolbar,
            domainName:'itemRoute'
        };

    },

   // 需要特殊處理的部份可宣告個別的函式,在呼叫 commonController中的函式前後進行特別處理
    readItemForm: function(obj, record, item, index, e, eOpts) {

        this.readMainForm(this.mainEditor);
        this.getItemRouteGrid().loadItemRoute(record.data.id);

    },

    itemRouteCreate: function() {

        this.doSubEditorCreate();

        var item_id = this.mainEditor.form().down('numberfield[name=id]').getValue();
        var store = this.mainEditor.grid().getStore();

        this.subEditor.form().getForm().setValues({
            'item.id': item_id,
            sequence:store.getCount()+1
        });

    },

    readItemRouteForm: function(obj, record, item, index, e, eOpts) {
        var item_id = this.mainEditor.form().down('numberfield[name=id]').getValue();

        this.subEditor.form().getForm().setValues({
            'item.id': item_id
        });

        this.readSubForm(obj, record, item, index, e, eOpts);
    }

});

上述調整,可以將重覆的程式碼大量減少,如此一來可以有一致的防護與動作,需要加新特性只要是共用的只要寫一次,所有的 controller 都可以快速套用