如下图所示,为Extjs部分代码提供的网页结构:
网站看上去本来是这样的
前端采用ExtJS,与后台的SpringMVC+Spring+Hibernate进行数据交互。
之前分析过登录的过程,不赘述
在loginController处理登录返回结果的最后,如下语句
也就是如果正确登录的话,跳转到xtype为appcentral的页面
这是一个普通的panel
它的内容是经典的四分空间
如果仔细看代码,会看到就是我们这片博文最开始的那个示意图所示的分割方式。
这里面我们重点看看“菜单”那个空间,也就是left
网页中,菜单的形式是这样的
下拉框的样子,挺复杂
但是点击最左侧的小三角,也可以变换形式:
看到至少有两种其他表现形式
下面看看ExtJS的代码是如何获取菜单内容的:
Central.js这个View带有一个ViewController
名为CentralController.js
这个View还带有一个ViewModel
名为CentralModel.js,样子如下
在它的constructor方法中
EU.RS();方法是作者自己定义的js方法。
发送给SpringMVC
看到buildListToTree()方法是static的,因此可以直接使用,没有什么依赖注入之类的。
也不能这么说,注入了一个一成不变的参数
@Service
public class SystemFrameService {@Resourceprivate DaoImpl dao;@SystemLogs("获取系统左侧树形菜单")public List<TreeNode> getMenuTree() {String userid = Local.getUserid();String usertype = Local.getUserBean().getUsertype();String companyid = Local.getCompanyid();String sql = "select" + " a.menuid,a.menuname as text,a.parentid as parentId,a.icon,a.iconCls,a.iconColor, "+ " c.moduletype as type,c.modulesource as url,c.objectid,a.menutype,a.orderno,a.isexpand as expanded "+ " from" + " f_companymenu a,f_companymodule b,f_module c " + " where a.companyid = b.companyid "+ " and a.cmoduleid = b.cmoduleid " + " and b.moduleid = c.moduleid "+ " and a.companyid = '" + companyid + "' ";if (!usertype.equals("01")) {sql += " and (" + " (" + " select count(1) c "+ " from f_modulefunction mf1,f_userfunctionlimit ufl "+ " where mf1.cmoduleid = b.cmoduleid and mf1.functionid = ufl.functionid and ufl.userid = '"+ userid + "' " + " ) > 0" + " or " + " ( " + " select count(1) c "+ " from f_modulefunction mf2,f_rolefunctionlimit rfl,f_userrole ur "+ " where mf2.cmoduleid = b.cmoduleid and mf2.functionid = rfl.functionid and rfl.roleid = ur.roleid and ur.userid ='"+ userid + "' " + " ) > 0" + " ) ";}sql += " order by a.orderno";List<TreeNode> dataList = dao.executeSQLQuery(sql, TreeNode.class);Map<String, TreeNode> parentMap = new HashMap<String, TreeNode>();for (TreeNode node : dataList) {if (!CommonUtils.isEmpty(node.getParentId())) {parentNode(parentMap, node);}}for (String key : parentMap.keySet()) {dataList.add(parentMap.get(key));}return dataList;}
}
这部分获取参数的代码还是比较壮观的,执行了sql而非hql语句,返回值是自定义类型。
上述sql语句中直接使用了数据库中数据表的名字和字段,我们补充一下对应的信息:
其实在phpmyadmin中只能看到字段的含义,数据表的内容,是无法直观用图形的方式看到不同数据表之间关系的。推荐使用powerdesigner读取mysql导出的sql文件,形成pdm图,可以方便的看到数据库各种关系的全貌,直观而且准确。
使用powerdesigner生成pdm图的过程,可以参考
http://blog.csdn.net/duanchangqing90/article/details/38089557
这篇文章
使用chrome浏览器看看http request的内容以及response的内容
Request URL:http://www.jhopesoft.com:8080/cfcmms/platform/systemframe/getmenutree.do
也就是说,前端和后台的通讯完全在意料之中。
回到ExtJS代码中Central.js的ViewModel文件
上述文件只是初始化,为js中的变量menus赋值
这些数据,如何使用呢?
上图为菜单的View文件
这些数据从后台返回,并处理,然后存放在js变量中,赋值给store
看来我对于filterer的猜测是错的
下面看看TreeStore的root属性(config)
对于Ext.data.TreeStore来说,root属性中的expanded属性设置为true,意味着不管这个store是否设置为autoLoad,整个store都会直接load。
写到这里,我们还是没有搞清楚Component和Store之间是什么关系,为什么这些数据可以被显示成View中菜单的样子。
我们来看看Ext官网上 Ext.data.TreeStore 的案例代码:
//Store部分的代码
var store = Ext.create('Ext.data.TreeStore', {root: {expanded: true,children: [{ text: 'detention', leaf: true },{ text: 'homework', expanded: true, children: [{ text: 'book report', leaf: true },{ text: 'algebra', leaf: true}] },{ text: 'buy lottery tickets', leaf: true }]}
});//View部分的代码
Ext.create('Ext.tree.Panel', {title: 'Simple Tree',width: 200,height: 500,store: store,rootVisible: false,renderTo: Ext.getBody()
});
上述代码的效果图如下
也就是说一个View和一个Store就可以轻易创建出上述样子的菜单。
我们也看到treepanel没什么代码,store的核心就是root children 和 text。
很显然c必须是字符串的数组也就是
c = [{text:菜单项1},{text:菜单项2},{text:菜单项3}]
那么菜单上面的文字,必须出现在text这个属性中,如下图
我们看看c的来源
就是Ext采用Ajax()方法对某个url发送request的返回值
因此我们有必要先去浏览器的开发者工具看看,返回值究竟是怎样的形式:
也就是说,ajax发送的返回值,在被ExtJS的js代码处理之前,就已经有了text这个字段,那么就肯定是Spring代码的自定义返回值,就已经安排好的。
上述的POJO是自定义返回值,其中一个字段就是text
而执行查询的executeSQLQuery()方法,也是DAO中自定义返回值的查询方法(非自定义返回值意味着,采用PO作为返回类型)
上面的描述基本讲清楚了,菜单上面的中文字符串的来源。
下面看看菜单上的菜单项,点击以后,有什么效果。
菜单的View部分页面的本质是Ext.tree.Panel
上述js代码就是,你点击菜单上的文字后,将一个完全新的tab页,并列到“首页”tab右边的js代码
/*** 将标准模块加入tabpanel中了,如果已经有了,就转至该tab页 itemId:module_(moduleName)*/addModuleToMainRegion : function(menuitem, donotActive) {var moduleName = menuitem.moduleName;var menuid = menuitem.menuid;var view = this.getView().down('maincenter');var tabItemId = 'module_' + menuid; // tabPanel中的itemIdvar tab = view.down('> panel#' + tabItemId);// 查找当前主区域中是否已经加入了此模块了if (!tab) {var tabPanel = null;// type : 01=外部xtype,03=实体对象if (menuitem.type == '01') {tabPanel = Ext.getCmp(tabItemId);if (!tabPanel) {tabPanel = Ext.create(menuitem.url, {id : tabItemId,autoDestroy : true,title : menuitem.text,closable : true});}} else if (menuitem.type == '03') {tabPanel = modules.getModuleInfo(moduleName).getModulePanel(tabItemId);}if (!tabPanel)return;if (!Ext.isEmpty(menuitem.glyph))tabPanel.glyph = menuitem.glyph;if (!Ext.isEmpty(menuitem.iconCls))tabPanel.iconCls = menuitem.iconCls;tab = view.add(tabPanel);}if (!donotActive)view.setActiveTab(tab);}
这段代码应该好好学学
上图中item
其实写到这里我的感觉就是强弩之末。
你点击了菜单的某一个选项,你想知道都发生了什么。
其实你很清楚,不论点击那个菜单项,结果是一样的:在首页tab右边,生成一个新的tab,至于这个tab是什么结构,里面是什么内容,取决于菜单项的id之类的信息。也就是刚刚进入网站,默认load那个treePanel,也就是菜单。(菜单是个treePanel,刚进入网站的时候默认发送ajax类型的request获取了菜单的所有内容)
那么,点击了菜单项以后,究竟发生了什么呢?
我们之前分享的那段代码,有一段十分重要:
//定位到maincenter
var view = this.getView().down('maincenter');
...................
//创建tabelse if (menuitem.type == '03') {tabPanel = modules.getModuleInfo(moduleName).getModulePanel(tabItemId);}......................//把tab加到view上,也就是让它显示tab = view.add(tabPanel);
上述代码的核心就是modules
/*** 取得模块的定义* @param {} moduleid ,参数可以是moduleid、modulecode、objectname、objectid* @return {}*/getModuleInfo : function(moduleid){if (Ext.isEmpty(moduleid)) {EU.toastWarn('加载moduleid不能为空!');return;}var me = this,result = me.modules.get(me.modulesKeys[moduleid.toUpperCase()]);if (result) return result;var url = "platform/module/getmoduleinfo.do",params = {moduleid : moduleid};EU.RS({url : url,params : params,async : false,callback : function(moduleinfo){me.replaceRef(moduleinfo, moduleinfo);if (moduleinfo) {result = new Ext.create('app.view.platform.module.ModuleInfo', moduleinfo);me.modules.add(moduleinfo.moduleid, result);me.modulesKeys[moduleinfo.moduleid.toUpperCase()] = moduleinfo.moduleid;me.modulesKeys[moduleinfo.modulecode.toUpperCase()] = moduleinfo.moduleid;me.modulesKeys[moduleinfo.fDataobject.objectid.toUpperCase()] = moduleinfo.moduleid;me.modulesKeys[moduleinfo.fDataobject.objectname.toUpperCase()] = moduleinfo.moduleid;} else {EU.toastWarn('加载' + moduleid + '的模块数据时失败!');}}});return result;},
Ctrl + H 搜索一下
modulepanel 意味着:
同志们,我们找到所有的核心了!!!
Module.js这个文件太关键了,因为它就是模板,这个文件就是个普通panel但是这个文件中,定义了Store和Items
也就是说,你点击菜单项创建的新的tab,取哪些数据,显示哪些UI就完全取决于这个文件了~!!!
我们上代码:
initComponent : function(){var me = this;if (Ext.isObject(me.param)) Ext.apply(me, me.param)me.moduleInfo = modules.getModuleInfo(me.moduleId);me.objectName = me.moduleInfo.fDataobject.objectname;me.model = me.moduleInfo.model;me.istreemodel = me.moduleInfo.fDataobject.istreemodel;//Store是那种类型:二选一me.store =Ext.create('app.view.platform.module.' + (me.istreemodel ? 'treegrid.TreeGridStore' : 'grid.GridStore'), {module : me.moduleInfo,modulePanel : me,model : me.model});me.store.getProxy().extraParams.moduleName = me.moduleInfo.fDataobject.objectname;if (me.parentFilter) me.store.parentFilter = me.parentFilter;me.enableNavigate =!me.istreemodel && me.enableNavigate&& (me.moduleInfo.fDataobject.navigatedesign || me.moduleInfo.getNavigateSchemeCount() > 0);me.collapseNavigate = me.moduleInfo.getNavigateSchemeCount() == 0 || me.collapseNavigate;me.defaults = {moduleInfo : me.moduleInfo,objectName : me.objectName,modulePanel : me,parentFilter : me.parentFilter};//Items中的核心UI Componentvar center = {xtype : me.istreemodel ? 'moduletreegrid' : 'modulegrid',store : me.store,region : 'center',modulePanel : me,inWindow : me.inWindow};me.items = [me.centerRegionNest ? {xtype : 'panel',region : 'center',layout : 'fit',items : [Ext.apply(center, me.defaults)]} : center];
写到这里真的写不下去了,因为,你为什么这么傻呢?
读代码多枯燥啊,我们直接去chrome上看数据传递,不就很直观么?走着!
我们看到执行了三次request
getmoduleinfo.do
fetchdata.do
这明显就是发送给SpringMVC的
如果看看response中的数据:
getmoduleinfo.do
fetchdata.do
看上去上面两类request分别用来获取
整个模板的数据和数据库中对应数据的数据。
fetchdata.do在整个代码中,只出现一次,靠,复用啊
我们看到了一个经典的Store,看到了吧,完美