如下圖所示,為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,看到了吧,完美