前面几篇文档,咱们基本实现了一个静态的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