前面幾篇文檔,咱們基本實現了一個靜態的extjs頁面,本篇開始,實現左側導航樹與右側內容的聯動,也就是點擊導航菜單,加載對應模塊頁面和業務邏輯,實現js文件的按需加載。
左側的treelist,當點擊某個節點的時候,系統根據tree數據裏配置的模塊信息,加載這個模塊,而且把模塊對應的主頁面顯示在中間區域的tabpanel裏。javascript
監聽導航樹的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'; } });
通常狀況下,這個菜單數據是保存在後端的,經過權限判斷加載用戶可訪問的資源造成樹結構返回給前端使用。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
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,就能夠在這裏加載用戶數據 } } }); } });
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'} ] });
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' } });
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); } });
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.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