前面几篇文档,我们基本实现了一个静态的extjs页面,本篇开始,实现左侧导航树与右侧内容的联动,也就是点击导航菜单,加载对应模块页面和业务逻辑,实现js文件的按需加载。
业务需求是这样的:
左侧的treelist,当点击某个节点的时候,系统根据tree数据里配置的模块信息,加载这个模块,并且把模块对应的主页面显示在中间区域的tabpanel里。
改造主控制器:app/luter/controller/MainController.js
监听导航树的node点击事件,进行后续处理:
'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 + '创建模块失败。' + ' 可能的原因 :1、该模块当前没有实现.' + ' 2、模块文件名称与模块名称不一致,请检查' + 'Error: ' + error + '' }); return false; } finally { } //判断模块是否加载下来,因为是ajax加载,所以还是判断一下比较好 if (!module) { showFailMesg({ msg: 'B:load module fail,the module object is null.' + ' 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.js
[ { "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:- 控制器: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: '暂无数据'//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之前引入这个文件。
/** * icon_prefix font字体前缀定义 * baseConfig 全局配置 */var icon_prefix = " fa blue-color ", baseConfig = { /** * 全局常量定义 */ cons: { noimage: 'app/resource/images/noimage.jpg', /** * 静态服务器的地址 */ static_server: '' }, /** * 渲染器,对Boolean类型的表格列的显示内容进行渲染 */ renders: { trueText: '', falseText: '', cancel: '' }, /** * 图标定义 */ 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.json
{ "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}
如上,没问题的话,刷新页面,应该能看到如下所示:
上图中,一些Extjs默认的样式经过了hack。不是默认样式。
最终,整个项目的目录结构如下:判断是不是动态按需加载?
1、打开chrome的开发控制台,切换到network面板的js下。
2、刷新页面3、重复点击左侧用户管理,查看JS加载情况。正常情况下同一个模块的js只加载一次。