上篇文章介绍了搭建一个空的框架,使得管理系统有了大致的模样,今天工作的主要内容就是菜单的实现以及点击菜单后在右边内容区打开一个新的Panel。本篇文章的内容主要包括两个方面,Extjs4 Tree及Extjs4 tabPanel。
在Extjs应用中实现菜单,无疑Tree是最好的选择,将菜单项直接写到Tree中,也未尝不可,但后期的维护会非常麻烦,那么最好的选择就是异步获取菜单节点,这样既有利于后期维护,也可以节省JS代码的编写量。
实现异步加载,那必须要有数据库和服务端程序。管理系统使用的是ACCESS数据库。在数据库中建立Menu表,表一共有4个字段,分别是ID、MenuName、ParentID和cls.
ID:自增长类型
MenuName:代表菜单的名字。ParentID
ParentID:父ID
cls:样式名(这个需要在样式表中体现出来,才会有效果)
数据库有了,接下来就是服务端的编写,其实JS框架的好处在于服务端无论用什么都可以,这里我就使用ASP来实现了。由于服务端涉及的方面比较多,较多代码帖出来会比较乱,这里只说下返回的形式。
菜单需要的JSON数据格式如下:
- [{"text":"管理员管理","id":"1","iconCls":"manage","leaf":true}]
这里注意一点:由于整棵菜单树都是异步获取的,所以节点并不需要递归,而树的node.id就是JSON数据中的ID。点击父节点展开子节点的时候,发送的数据也是node.id,这样正好解决获取子节点的问题。所有根节点的ParentID都为0。那么第一次加载菜单的时候,正好可以获取所有的根节点了。
重点,菜单树的数据源编写,根据MVC原则,在app目录下建立store文件夹,这个文件夹下放置所有的获取数据的js文件,在store文件夹下建立Menus.js,编写如下代码:
- Ext.define('SMS.store.Menus',{
- extend: 'Ext.data.TreeStore',
- root: {
- expanded: true
- },
- proxy: {
- type: 'ajax',
- url: '/server/MenuLoader.asp'
- }
- })
然后修改view文件夹下的Menu.js文件:
- Ext.define('SMS.view.Menu',{
- extend: 'Ext.tree.Panel',
- alias: 'widget.smsmenu',
- requires:['SMS.store.Menus'],
- initComponent : function(){
- Ext.apply(this,{
- id: 'menu-panel',
- title: '系统菜单',
- iconCls:'icon-menu',
- margins : '0 0 -1 1',
- region:'west',
- border : false,
- enableDD : false,
- split: true,
- width : 212,
- minSize : 130,
- maxSize : 300,
- rootVisible: false,
- containerScroll : true,
- collapsible : true,
- autoScroll: false,
- store:Ext.create('SMS.store.Menus'),
- });
- this.callParent(arguments);
- }
- })
如此,当页面打开时,就会自动加载菜单项了。但是目前来说,点击菜单的任何节点都没有任何作用,那么由于整个项目都要使用Extjs来完成,那么必须要实现点击节点在右边内容区显示相应的Grid或Panel等等。下面,我们编写代码实现这个功能。
原理:当点击菜单树的节点时,先要判断要打开的组件是否存在,如果存在,则在右边内容区激活当前组件,如果不存在,则创建一个组件,然后在右边内容区增加一个组件。当然。这里为了通用性更高,创建出的组件一律按panel为准。为了实现在右边内容区的tabPanel上增加或删除对应的table,根据分离原则,我们需要在控制器上完成该操作,接下来,我们在controller文件夹下建立Menu.js文件,Menu.js会完成这一系列的工作,具体代码如下:
- Ext.define('SMS.controller.Menu',{
- extend: 'Ext.app.Controller',
- refs:[
- {ref: 'smsmenu',selector: 'smstablepanel'},
- {ref: 'tabPanel',selector:'smstablepanel'}
- ],
- init:function(){
- this.control({
- 'smsmenu': {
- itemmousedown: this.loadMenu
- }
- })
- },
- loadMenu:function(selModel, record){
- if (record.get('leaf')) {
- var panel = Ext.getCmp(record.get('id'));
- if(!panel){
- panel ={
- title: 'New Tab ' + record.get('id'),
- iconCls: 'tabs',
- html: 'Tab Body ' + record.get('id') + '<br/><br/>',
- closable: true
- }
- this.openTab(panel,record.get('id'));
- }else{
- var main = Ext.getCmp("content-panel");
- main.setActiveTab(panel);
- }
- }
- },
- openTab : function (panel,id){
- var o = (typeof panel == "string" ? panel : id || panel.id);
- var main = Ext.getCmp("content-panel");
- var tab = main.getComponent(o);
- if (tab) {
- main.setActiveTab(tab);
- } else if(typeof panel!="string"){
- panel.id = o;
- var p = main.add(panel);
- main.setActiveTab(p);
- }
- }
- })
关键点:refs和this.control,refs在官方API中没有找到其解释,网上查了下,对该属性的解释是:凡是component都可以使用该属性在它的归属容器及归属容器的父节点中注入一个对该属性的引用名称。有了该引用名,和该组件有共同父节点的组件就可以比较方便的引用该组件。
而this.control则很容易理解了,即通过Ext.ComponentQuery为选定的组件添加监听。上面代码为我们的菜单节点添加了一个事件loadMenu,loadMenu中,先获取对应的panel,如果该panel不存在,则使用openTab方法在内容区的tabPanel上增加一个panel,如果存在,则激活该panel。而panel不存在的话,这里只简单的创建了一个panel,并没有任何意义,下篇文章,我们将详细讨论这个问题。
最后,我们需要修改app.js,将我们创建的控制器加载进来。app.js:
- Ext.Loader.setConfig({enabled: true});
- Ext.application({
- name: 'SMS',
- appFolder: 'app',
- autoCreateViewport:true,
- controllers: [
- 'Menu'
- ]
- });
这里唯一修改的地方就是将Main改为了Menu。后面,我们会逐步完善。
今天的工作比较多,整理下今日的工作内容:
1、在app目录下建立了store文件夹,并建立了Menus.js文件,这个文件负责菜单数据的加载。
2、修改了view文件夹下的Menu.js,使其可以加载菜单数据。
3、在controller文件夹下建立了Menu.js,使其可以在内容区的tabPanel上增加新的panel组件。
4、修改app.js,使其可以使菜单项正常使用。