extjs-mvc結構實踐(四):導航菜單與控制器模塊聯動

前面幾篇文檔,咱們基本實現了一個靜態的extjs頁面,本篇開始,實現左側導航樹與右側內容的聯動,也就是點擊導航菜單,加載對應模塊頁面和業務邏輯,實現js文件的按需加載。

業務需求是這樣的:

左側的treelist,當點擊某個節點的時候,系統根據tree數據裏配置的模塊信息,加載這個模塊,而且把模塊對應的主頁面顯示在中間區域的tabpanel裏。javascript

改造主控制器:app/luter/controller/MainController.js

監聽導航樹的node點擊事件,進行後續處理:html

'navlist': {
    'itemclick':function(el, record, opt){
        //能夠經過以下方式獲取點擊節點的數據。
        var nodeData = record.node.data;
    
    }
}

完整的代碼以下:前端

Ext.define('luter.controller.MainController', {
    extend: 'Ext.app.Controller',
    views: ['main.ViewPort'],
    stores: ['NavTreeStore'],
    init: function (application) {
        var me = this;
        this.control({
            'viewport': {//監聽viewport的初始化事件,能夠作點其餘事情在這裏,若有必要,記得viewport定義裏的alias麼?
                'beforerender': function () {
                    console.log('viewport begin render at:' + new Date());
                },
                'afterrender': function () {
                    console.log('viewport  render finished at:' + new Date());
                },
            },
            'syscontentpanel': {
                'afterrender': function (view) {
                    console.log('syscontentpanel rendered at:' + new Date());
                }
            },
            'navlist': {
                'itemclick': function (el, record, opt) {
                    var nodeData = record.node.data;//當前點擊節點的數據

                    var tabpanel = Ext.getCmp('systabpanel');//中間tabpanel
                    var tabcount = tabpanel.items.getCount();//當前tabpanel已經打開幾個tab了。
                    var maxTabCount = 5;//最大打開的tab個數
                    if (tabcount && tabcount > 5) {
                        showFailMesg({
                            title: '爲了更好的使用,最多容許打開5個頁面',
                            msg: '您打開的頁面過多,請關掉一些!'
                        });
                        return false;
                    }
                    if (nodeData.leaf) {//是打開新模塊,不然是展開樹節點
                        var moduleID = nodeData.module_id;//找到控制器ID,定義在tree的數據裏modole_id
                        if (!moduleID || moduleID == '') {
                            showFailMesg({
                                title: '建立模塊失敗.',
                                msg: '模塊加載錯誤,模塊id爲空,建立模塊失敗'
                            });
                            return false;
                        }
                        console.log('to add module with id:' + moduleID);
                        //開始加載控制器
                        try {
                           //嘗試加載這個控制器,這個過程就是按需ajax加載js文件的過程。
                          //若是這個模塊被加載過,則不會重複加載。
                            var module = luterapp.getController(moduleID);
                        } catch (error) {
                            showFailMesg({
                                msg: '根據模塊ID:' + moduleID + '建立模塊失敗。' +
                                '<br> 可能的緣由 :<br>一、該模塊當前沒有實現.' +
                                '<br> 二、模塊文件名稱與模塊名稱不一致,請檢查' +
                                '</br><span style="color: red">Error: ' + error + '</span>'
                            });
                            return false;
                        } finally {

                        }
                        //判斷模塊是否加載下來,由於是ajax加載,因此仍是判斷一下比較好
                        if (!module) {
                            showFailMesg({
                                msg: 'B:load module fail,the module object is null.' +
                                '<br> maybe :the module is Not available now.'
                            });
                            return false;

                        }
                        //加載到以後,默認去獲取控制器裏views:[]數組裏的第一個做爲主視圖
                        var viewName = module.views[0];
                        console.log('will create a tab with view ,id:' + viewName);
                        var view = module.getView(viewName);
                        console.log('get the view el:' + view);
                        if (!view) {
                            showFailMesg({
                                msg: 'Sorry ,to get the module view fail...'
                            });
                            return false;
                        }

                        //判斷一下這個視圖是否是已經加載到tabpanel裏去了
                        var tabid = me.getTabId(moduleID);
                        console.log('will create a tab with id:' + tabid);
                        var notab = tabpanel.getComponent(tabid);
                        var viewEL = view.create();
                        if (!viewEL) {
                            showFailMesg({
                                msg: 'Sorry ,to get the module viewEL fail...'
                            });
                            return false;
                        }

                        if (!notab && null == notab) {//不存在新建
                            //無論是啥,都放到一個panel裏面。
                            notab = tabpanel.add(Ext.create('Ext.panel.Panel', {
                                tooltip: nodeData.text + ':' + nodeData.qtip,
                                id: tabid, // tab的惟一id
                                title: nodeData.text, // tab的標題
                                layout: 'fit', // 填充佈局,它不會讓load進來的東西改變大小
                                border: false, // 無邊框
                                closable: true, // 有關閉選項卡按鈕
                                iconCls: nodeData.iconCls,
                                listeners: {
                                    // 偵聽tab頁被激活裏觸發的動做
                                    scope: this,
                                    destroy: function () {
                                        console.log("tab :" + tabid + ",has been destroyed")
                                    }
                                },
                                items: [view.create()]
                            }));
                            //新建以後focus
                            tabpanel.setActiveTab(notab);
                        }
                        else {//若是這個tab已經存在了,則focus到這個tab
                            tabpanel.setActiveTab(notab);
                        }
                    } else {
                    //若是leaf  =false,則說明這不是一個最底層節點,是目錄,展開。
                        console.log('tree node expand')
                    }
                }
            }
        });
    },
    //這個方法從tab id裏分離出控制器名稱
    getTabId: function (mid) {
        var winid = mid;
        var c = winid.split('.');
        winid = c.pop();
        return winid + '-tab';
    }

});

左側菜單樹對應的測試數據:app/testdata/menu.json

通常狀況下,這個菜單數據是保存在後端的,經過權限判斷加載用戶可訪問的資源造成樹結構返回給前端使用。leaf標明瞭這是一個目錄仍是一個模塊,module_id對應的是控制器的路徑。 好比下面這個測試數據中。 "leaf": true, "module_id": "sys.UserController", 在app.js中,咱們配置了appFolder:‘app/luter’, leaf標明瞭這是一個控制器模塊,點擊後會去觸發控制器加載動做。 因此這個模塊的實際路徑(也就是js文件)就是:${appFolder}/controller/${module_id}.js 即:app/luter/controller/sys.UserController.jsjava

[
  {
    "id": "111",
    "text": "系統管理",
    "href": null,
    "leaf": false,
    "iconCls": "fa fa-home",
    "module_id": "no sign",
    "qtip": "這個地方顯示鼠標懸停提示",
    "children": [
      {
        "id": "11111",
        "text": "用戶管理",
        "href": null,
        "leaf": true,
        "iconCls": "fa fa-user",
        "module_id": "sys.UserController",
        "qtip": "系統用戶管理",
        "children": []
      }
    ]
  }

]

導航菜單與tabpanel 聯動完成,下面弄個控制器實驗一下效果,以新建一個系統管理分類下的用戶管理模塊功能爲例:
系統管理部分模塊放在sys目錄下,so:node

  • 控制器:app/luter/controller/sys/UserController.js
  • 模型:app/luter/model/UserModel.js
  • Store:app/luter/store/UserStore.js
  • 視圖主入口:app/luter/model/view/sys/user/User.js
  • 列表視圖:app/luter/model/view/sys/user/UserList.js
  • ......

用戶管理控制器 :app/luter/controller/sys/UserController.js

Ext.define('luter.controller.sys.UserController', {
    extend: 'Ext.app.Controller',
    stores: ['UserStore'], //用戶store
    views: ['sys.user.User'], //主view ,tab裏會加載第一個視圖。
    init: function () {
        this.control({
            'userlistview': {
                'beforerender': function (view) {
                    console.log("beforerender   list......   ");
                },
                'afterrender': function (view) {
                    console.log("afterrender   list......   ");
                     // this.getUserStoreStore().load();//若是UserStore裏沒設置autoLoad: true,就能夠在這裏加載用戶數據
                }
            }
        });

    }
});

用戶模型Model:app/luter/model/UserModel.js

Ext.define('luter.model.UserModel', {
    extend: 'Ext.data.Model',
    fields: [
        {name: 'id', type: 'string'},
        {name: 'username', type: 'string'},
        {name: 'gender', type: 'string'},
        {name: 'real_name', type: 'string'}
    ]
});

用戶Store:app/luter/store/UserStore.js

Ext.define('luter.store.UserStore', {
    extend: 'Ext.data.Store',
    autoLoad: true,//自動加載數據
    model: 'luter.model.UserModel',//使用的模型
    pageSize: 15,//每頁數據多少
    proxy: {
        type: 'ajax',//ajax獲取數據
        actionMethods: {
            create: 'POST',
            read: 'POST',
            update: 'POST',
            destroy: 'POST'
        },
        api: {
            read: 'app/testdata/user.json'//從這個地方獲取數據,固然,這裏用測試數據
        },
        reader: {//返回數據解析器
            type: 'json',
            root: 'root',//用戶列表數據在這個字段下
            successProperty: 'success',//成功與失敗的標誌位是這個字段
            totalProperty: 'total'//記錄總數在這個字段
        },
        listeners: {
            exception: function (proxy, response, operation, eOpts) {
                DealAjaxResponse(response);//監聽ajax異常提示錯誤
            }
        }
    },
    remoteSort: true,//服務器端排序
    sortOnLoad: true,//加載就排序
    sorters: {//拿ID排序
        property: 'id',
        direction: 'DESC'
    }

});

用戶管理模塊主視圖:app/luter/model/view/sys/user/User.js

Ext.define('luter.view.sys.user.User', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.userview',
    layout: 'fit',
    requires: ['luter.view.sys.user.UserList'],//引入用戶列表模塊
    border: false,
    initComponent: function () {
        var me = this;
        me.items = [{
            xtype: 'userlistview',
            layout: 'fit'

        }]
        me.callParent(arguments);
    }
});

用戶列表視圖:app/luter/model/view/sys/user/UserList.js

Ext.define('luter.view.sys.user.UserList', {
    extend: 'Ext.grid.Panel',
    alias: 'widget.userlistview',//其餘地方就能夠這麼用:xtype:‘userlistview’
    requires: [],
    store: 'UserStore',//用到的store
    itemId: 'userGrid',//本身的itemid
    columnLines: true,//是否顯示錶格線
    viewConfig: {
        emptyText: '<b>暫無數據</b>'//store沒數據的時候顯示這個
    },
    initComponent: function () {
        var me = this;
        me.columns = [{
            xtype: 'rownumberer',
            text: '序號',
            width: 60
        }, {
            header: "操做",
            xtype: "actioncolumn",
            width: 60,
            sortable: false,
            items: [{
                text: "刪除",
                iconCls: 'icon-delete',
                tooltip: "刪除這條記錄",
                handler: function (grid, rowIndex, colIndex) {
                    var record = grid.getStore().getAt(rowIndex);
                    if (!record) {
                        toast({
                            msg: '請選中一條要刪除的記錄'
                        })
                    } else {
                        showConfirmMesg({
                            message: '肯定刪除這條記錄?',
                            fn: function (btn) {
                                if (btn === 'yes') {
                                    Ext.Ajax.request({
                                        url: 'sys/user/delete',
                                        method: 'POST',
                                        params: {
                                            id: record.get('id')
                                        },
                                        success: function (response, options) {
                                            DealAjaxResponse(response);
                                            Ext.data.StoreManager.lookup('User').load();
                                        },
                                        failure: function (response, options) {
                                            DealAjaxResponse(response);
                                        }
                                    });
                                } else {
                                    return false;
                                }
                            }

                        })

                    }

                }
            }]
        }, {
        
            header: baseConfig.model.user.id,
            dataIndex: 'id',
            hidden: false,
            flex: 1
        },

            {
                header: baseConfig.model.user.username,
                dataIndex: 'username',
                flex: 1
            },
            {
                header: baseConfig.model.user.real_name,
                dataIndex: 'real_name',
                flex: 1
            }
        ]

        me.bbar = Ext.create('Ext.PagingToolbar', {
            store: me.store,
            displayInfo: true,
            displayMsg: '當前數據 {0} - {1} 總數: {2}',
            emptyMsg: "沒數據顯示",
            plugins: [new Ext.create('luter.ux.grid.PagingToolbarResizer', {
                options: [5, 10, 15, 20, 25, 50, 100]
            })]
        })
        me.dockedItems = [{
            xtype: 'toolbar',
            items: [{
                text: '添加',
                iconCls: baseConfig.appicon.add,
                tooltip: '添加',
                handler: function () {
                    var win = Ext.create('luter.view.sys.user.UserAdd');
                    win.loadView();
                    win.show();

                }
            }]
        }]
        me.listeners = {
            'itemdblclick': function (table, record, html, row, event, opt) {
                if (record) {
                    var id = record.get('id');
                    var view = Ext.create('luter.view.sys.user.UserEdit', {title: '編輯數據'});
                    view.loadView();
                    loadFormDataFromDb(view, 'sys/user/view?id=' + id);
                } else {
                    showFailMesg({
                        msg: '加載信息失敗,請確認。'
                    })
                }

            }
        }
        me.plugins = []
        me.callParent(arguments);
    }
});

//這裏的baseConfig定義在公共配置文件config.js中,以下:

公共配置參數定義文件:app/luter/config.js

別忘記在app.html中app.js以前引入這個文件。ajax

/**
 * icon_prefix font字體前綴定義
 * baseConfig 全局配置
 */
var icon_prefix = " fa blue-color ", baseConfig = {
    /**
     * 全局常量定義
     */
    cons: {
        noimage: 'app/resource/images/noimage.jpg',
        /**
         * 靜態服務器的地址
         */
        static_server: ''
    },
    /**
     * 渲染器,對Boolean類型的表格列的顯示內容進行渲染
     */
    renders: {
        trueText: '<i   class=" fa fa-lg fa-check green-color"></i>',
        falseText: '<i   class=" fa fa-lg fa-close red-color"></i>',
        cancel: '<i class=" fa fa-lg fa-undo"></i>'
    },
    /**
     * 圖標定義
     */
    appicon: {
        home: icon_prefix + 'fa-home',
        add: icon_prefix + "fa-plus",
        update: icon_prefix + "fa-edit",
        trash: icon_prefix + "fa-trash",
        delete: icon_prefix + "fa-remove red-color",
        set_wallpaper: icon_prefix + "fa-image",
        setting: icon_prefix + "fa-gears",
        desktop: icon_prefix + "fa-desktop",
        pailie: icon_prefix + "fa-cubes",
        logout: icon_prefix + "fa-power-off",
        avatar: icon_prefix + "fa-photo",
        key: icon_prefix + "fa-key",
        user: icon_prefix + "fa-user",
        refresh: icon_prefix + "fa-refresh blue-color",
        close: icon_prefix + "fa-close",
        male: icon_prefix + 'fa-male',
        female: icon_prefix + 'fa-female',
        role: icon_prefix + 'fa-users',
        user_add: icon_prefix + "fa-user-plus",
        undo: icon_prefix + 'fa-undo',
        search: icon_prefix + 'fa-search',
        reset: icon_prefix + 'fa-retweet',
        yes: icon_prefix + 'fa-check green-color',
        no: icon_prefix + 'fa-close red-color',
        list_ol: icon_prefix + ' fa-list-ol',
        list_alt: icon_prefix + ' fa-list-alt',
        ban: icon_prefix + "fa-ban",
        log: icon_prefix + "fa-tty",
        printer: icon_prefix + "fa-print",
        fax: icon_prefix + "fa-fax",
        download: icon_prefix + "fa-cloud-download",
        upload: icon_prefix + "fa-cloud-upload",
        comment: icon_prefix + " fa-commenting-o",
        credit: icon_prefix + "fa fa-gift"
    },
    /**
     * 模型定義
     */
    model: {
        /**
         * 系統用戶模型
         */
        user: {
            id: 'ID',
            username: '用戶名',
            real_name: '真實姓名'
        }
    }
};

最後,附上用戶列表的測試數據(固然,瞎編的......):app/testdata/user.jsonchrome

{
  "total": 33,
  "root": [
    {
      "id": "aaa",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "ccc",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "ddd",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "eee",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用戶"
    }
  ],
  "success": true
}

如上,沒問題的話,刷新頁面,應該能看到以下所示:json

圖片描述

上圖中,一些Extjs默認的樣式通過了hack。不是默認樣式。
最終,整個項目的目錄結構以下:
圖片描述後端

判斷是否是動態按需加載?

一、打開chrome的開發控制檯,切換到network面板的js下。
二、刷新頁面
三、重複點擊左側用戶管理,查看JS加載狀況。正常狀況下同一個模塊的js只加載一次。api

相關文章
相關標籤/搜索