Ext JS4序列教程之二 :异步加载accordion和Tree菜单

1、序言

EXT JS4序列教程主要讲解WEB开发中一些常用的组件,例如Tree,Grid,Combobox,form等,EXT JS4的出现为广大程序员带来了福音,我们可以用较少的代码,实现很炫丽的效果,我在很多项目的架构中都使用EXT JS作为核心的WEB框架,配合jQuery框架,大家很容易实现一个用户体验很不错的软件系统(我们称之为高大上,哈哈哈)。EXT JS自推出以来,其性能就饱受开发的砰击,在EXT JS4以前的版本,性能确实不是很好,不过比起jQuery Easy UI,那还是要好很多的,从EXT JS4.2以后的版本开始,性能还是很不错的,代码也比较精简,结构清晰,纯面像对象的语法,BUG也较EXT JS4.1少了很多,相对比较稳定,EXT JS4推出了MVC模式的设计风格,使得代码结构更加清晰,可读性更好,非常类似于使用JAVA SWINGC# WinForm开发,但如果没有接触过AJAX框架的程序员,第一次使用EXT JS4会碰到各种各样的问题,本教程教从零开始讲解EXT JS4,从客户端到服务器都有完整的代码,服务端使用SSH框架,用注解方式进行开发,抛弃了繁锁的配置文件(我本人相当讨厌配置文件,在我设计的架构中,配置文件几乎为零)。关于源码,由于Google无法访问(IT业的一大悲剧),大家可以到CSDN上下载。

本文从实际应用出发,讲解与WEB系统开发息息相关的实例,EXT JS功能很丰富,由有时间的原因,我不会所有的功能都讲到(我都是利用业余时间写教程,目前在一家公司担任高级架构师,工作很忙,我写教程主要是在互联网上和大家一起分享自己的开发经验),大家按照本套系列教程来逐步开发代码,可以实现一个功能比较完整的WEB系统。本教程后端使用的架构为Struts2+Hibernate4+Spring4,后续我将会逐一介绍SSH架构的搭建。关于ASP.NET的教程,会在后续推出。

 

 

                                                                                                                                          作者:山人

1、 异步accordion和Tree菜单

    好了,各位观众,前面我们讲了layout布局中的border布局,本章我要介绍一个另大家兴奋的东西,那就是异步accordion和Tree菜单,这类菜单在实际的项目中经常会用到,accordion菜单作为功能模块菜单,Tree菜单作为功能点菜单,由其是规模较大的项目,应用较为普遍。我在网上搜索发现类似的例子有很多,但是很少有异步加载Tree的例子,很多都是一次性加载,这不仅会消耗多余的资源,造成服务端和客户端的查询、显示效率下降,而且不利于权限控制,例如Spring-acegi安全框架。如果是政府类的安全性和保密性要求较高的应用系统,是不适用的,前段时间闹的沸沸扬扬的香港占中事件,国外黑客宣布要入侵中国的电子政务系统,以支持香港占中,这对我国的电子政务系统的安全性提出了挑战,一个很小的漏网都可能会成为黑客入侵的目标,造成较大的损失。所以,菜单的异步加载和权限控制是很有必要的,因为只有异常加载菜单才能较好的与安全框架集成,如果把权限控制放在客户端脚本里,黑客就可以通过修改脚本执行顺序、跨站脚本攻击等技术攻击我们的系统,得到系统管理员的权限。说了这么多题外话,我们先来看一下效果:

     




    怎么样,高大上吧?其实高大上就是这么来的。当然,很多按钮位置错乱、节点错乱、半天不响应的UI也是这么来的,我们称之为“土肥圆”,不知道你有没有见过,反正我是见过,最让人佩服的是,这样的系统还被客户使劲的夸,我们只能彻底拜服在这位项目经理的脚下。由此可知,做好一个系统,不是你的功能做的有多强就可以的,关键还是要维护好客户关系的。以上界面具体实现步骤如下:

 

第一步:老姜一块,我们需要在JSP中引入Ext JS4的类库,这一步是必须的。


  
<%@ page pageEncoding="UTF-8"%>
<%
	String path = request.getContextPath();
	String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
			+ path + "/";
%>
<%-- 样式文件,加载全部样式 --%>
<link rel="stylesheet" type="text/css" href="<%=path%>/javascript/extjs-4.1.0/resources/css/ext-all.css" />
<%-- ext js 文件 --%>
<script type="text/javascript" src="<%=path%>/javascript/extjs-4.1.0/ext-all.js"></script>
<script type="text/javascript" src="<%=path%>/javascript/extjs-4.1.0/ext-lang-zh_CN.js"></script>

注意,大家可以把EXT JS类库定义在一个JSP中,当其他页面要引用的时候,可以使用JSP的包含动作将类库引入,这样也符合代码重用的目的,同时要把ext-lang-zh_CN.js这个文件引进来,这个是Ext JS的语言文件,用来汉化EXT JS

 

接下来讲解几个EXT JS的函数,第一个函数EXTAJAX请求函数,这个函数用来发送异步请求,向服务器请求数据,服务器收到请求后将数据返回给客户端,由EXT JS进行处理,函数如下:

  Ext.Ajax.request({….})

第二个函数,哦,对了,这里我们不能称之为函数,应该称之为模型(Model),在JAVAC#里,我们经常要定义Model,用来定义和存储对象的数据,EXT JS的模型和各种服务端编程语言是一样的,只是写法不一样,EXT JS的模型用来存储JSON格式的数据,Model存储在store里面,store就像一个数据库,里面可以有表,而Model的角色就是表,定义一个模型是这样的。

Ext.define('Menu', {
			extend : 'Ext.data.Model',
			fields : [ {
				name : 'id',
				type : 'string'
			}
此处略去N个字

第二步:定义一个MODEL

//定义一个菜单的MODEL,用来保存对应的JSON数据
		Ext.define('Menu', {
			extend : 'Ext.data.Model',
			fields : [ {
				name : 'id',
				type : 'string'
			}, {
				name : 'text',
				type : 'string'
			}, {
				name : 'url',
				type : 'string'
			} ]
		});


第三步:定义一个TreePanel

/**
	* 生成treepanel
	*/
	var buildTree = function(json) {
		//创建一颗树了
		return Ext.create('Ext.tree.Panel',
		{
			useArrows : true,
			rootVisible : false,
			border : false,
			store : Ext.create('Ext.data.TreeStore', {
			model : 'Menu',
			//使用AJAX动态装载Tree
			proxy: {
					  type: 'ajax',
					  url: ctx+'/queryChildMenu'
				   },
			root : {
					  expanded : true,
					 children : json
				   }
			}),
			listeners : {
			//节点单击事件
			'itemclick' : function(view, record, item,index, e) {
				var id = record.get('id');
				var text = record.get('text');
				var url = record.get('url');
				var leaf = record.get('leaf');
				if (leaf) {
						centerPanel.loadPage(url,'menu' + id, text);
				}
			},
			//单击节点展开之前的事件
			'beforeitemclick':function(view, record, item,index, e)
			{
				return;
			},
			scope : this
			}
		});
	};

第四步:创建AJAX请求,从服务器中获取菜单数据


/**
	* 创建AJAX请求,从服务器请求菜单数据生成 accordion和Tree 菜单
	*/
	Ext.Ajax.request({
	         url : ctx+"/queryMenu",
				success : function(response) {
					var json = Ext.JSON.decode(response.responseText);
					var rows=json["rows"];
					for(var index in rows)
					{
						var panel = Ext.create('Ext.panel.Panel', {
							title : rows[index].text,
							layout : 'fit'
						});
						var menus=rows[index].menus;
						if(menus)
						{
							panel.add(buildTree(menus));
						}
						leftPanel.add(panel);
					}
			},
			failure : function(request) {
				Ext.MessageBox.show( {
					title : '操作提示',
					msg : "连接服务器失败",
					buttons : Ext.MessageBox.OK,
					icon : Ext.MessageBox.ERROR
				});
			},
			method : 'post'
	});

客户端的完整代码如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<jsp:include page="include/Ext4Lib.jsp"></jsp:include>
<title>功能菜单</title>
<script type="text/javascript">
Ext.onReady(function() {
	var ctx="${pageContext.request.contextPath}";
	/**
	*定义右侧面版
	*/
	Ext.define('mainTabPanel', {
		extend: 'Ext.tab.Panel',
		//重写页面加载方法,在该方法中,定义一个iframe,用来装载JSP页面
		loadPage:function(url,id,title,icon,reload){
			var tab = this.getComponent(id);
			debugger;
			if(tab){
				this.setActiveTab(tab);
			}else
			{
				var p = this.add(new Ext.panel.Panel({
					id:id,
					title:title,
					closable:true,
					icon:icon,
					html:'<iframe src="' + url + '"width="100%" height="100%" frameborder="0" scrolling="auto"></iframe>'
				
				}));
				this.setActiveTab(p);
			}
		}
	 });
	
	/**
	*创建顶部面板
	*/
    var topPanel = Ext.create('Ext.panel.Panel', {
                            region : 'north',
                            height : 55
    });
    /**
	*定义顶左侧面板
	*/
    var leftPanel = Ext.create('Ext.panel.Panel', {
                            region : 'west',
                            title : '导航栏',
                            width : 230,
                            layout : 'accordion',
							split:true,
                            collapsible : true//是否可以折叠收缩
     });
    /**
	*创建中间面板
	*/
    var centerPanel = Ext.create('mainTabPanel', {
                            region : 'center',
                            layout : 'fit',
                            tabWidth : 120,
                            items : [{
                                title : '首页'
                            }]
    });
    
	/**
	* 创建AJAX请求,从服务器请求菜单数据生成 accordion和Tree 菜单
	*/
	Ext.Ajax.request({
	         url : ctx+"/queryMenu",
				success : function(response) {
					var json = Ext.JSON.decode(response.responseText);
					var rows=json["rows"];
					for(var index in rows)
					{
						var panel = Ext.create('Ext.panel.Panel', {
							title : rows[index].text,
							layout : 'fit'
						});
						var menus=rows[index].menus;
						if(menus)
						{
                             //为accordion添加树菜单
							panel.add(buildTree(menus));
						}
						leftPanel.add(panel);
					}
			},
			failure : function(request) {
				Ext.MessageBox.show( {
					title : '操作提示',
					msg : "连接服务器失败",
					buttons : Ext.MessageBox.OK,
					icon : Ext.MessageBox.ERROR
				});
			},
			method : 'post'
	});

	 //定义一个菜单的MODEL,用来保存对应的JSON数据
	Ext.define('Menu', {
			extend : 'Ext.data.Model',
			fields : [ {
				name : 'id',
				type : 'string'
			}, {
				name : 'text',
				type : 'string'
			}, {
				name : 'url',
				type : 'string'
			} ]
	});
	
	/**
	* 生成treepanel
	*/
	var buildTree = function(json) {
		//创建一颗树了
		return Ext.create('Ext.tree.Panel',
		{
			useArrows : true,
			rootVisible : false,
			border : false,
			store : Ext.create('Ext.data.TreeStore', {
			model : 'Menu',
			//使用AJAX动态装载Tree
			proxy: {
					  type: 'ajax',
					  url: ctx+'/queryChildMenu'
				   },
			root : {
					  expanded : true,
					 children : json
				   }
			}),
			listeners : {
			//节点单击事件
			'itemclick' : function(view, record, item,index, e) {
				var id = record.get('id');
				var text = record.get('text');
				var url = record.get('url');
				var leaf = record.get('leaf');
				if (leaf) {
						centerPanel.loadPage(url,'menu' + id, text);
				}
			},
			//单击节点展开之前的事件
			'beforeitemclick':function(view, record, item,index, e)
			{
				return;
			},
			scope : this
			}
		});
	};

	/**
	* 创建视图
	*/
	Ext.create('Ext.container.Viewport', {
			layout : 'border',
			renderTo : Ext.getBody(),
			items : [ topPanel, leftPanel, centerPanel ]
		});
	});
</script>
</head>
<body></body>
</html>

第五步:服务端代码

我们在Struts2Action中添加两个方法,第一个方法用来加载accordion菜单和Tree的根菜单所需的数据

@Action(value="queryMenu",results = { @Result(type = "json",params={"root","pageBean"})})
	public String queryMenu() {
		
		menuPanels=menuPanelService.queryMenuPanel();
		this.pageBean.setRows(menuPanels);
		this.pageBean.setTotal(menuPanels.size());//不使用分页
		return "success";
	}

第二个方法用来查询Tree菜单下的子菜单

/**
	 * 查询子菜单事件
	 * @return
	 */
	@Action(value="queryChildMenu",results = { @Result(type = "json",params={"root","menus"})})
	public String queryChildMenu() {
		
		menus=menuPanelService.queryChildMenu(node);
		return "success";
	}

完整的Action代码

package com.mcs.user.action;

import java.util.List;

import javax.annotation.Resource;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Result;

import com.mcs.core.action.BaseAction;
import com.mcs.user.pojo.Menu;
import com.mcs.user.pojo.MenuPanel;
import com.mcs.user.service.MenuPanelService;



public class UserAction extends BaseAction<MenuPanel>{
	
	@Resource
	private MenuPanelService menuPanelService;
	private boolean ignoreHierarchy=false;
	
	private List<MenuPanel> menuPanels;
	private List<Menu> menus;
	
	private String node;
	@Action(value="queryUser",results={@Result(location="main.jsp")})
	public String queryUser(){
		return "success";
	}
	private String userName;
	
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;//
	}
	@Action(value="queryMenu",results = { @Result(type = "json",params={"root","pageBean"})})
	public String queryMenu() {
		
		menuPanels=menuPanelService.queryMenuPanel();
		this.pageBean.setRows(menuPanels);
		this.pageBean.setTotal(menuPanels.size());//不使用分页
		return "success";
	}
	/**
	 * 查询子菜单事件
	 * @return
	 */
	@Action(value="queryChildMenu",results = { @Result(type = "json",params={"root","menus"})})
	public String queryChildMenu() {
		
		menus=menuPanelService.queryChildMenu(node);
		return "success";
	}
	public List<MenuPanel> getMenuPanels() {
		return menuPanels;
	}

	public void setMenuPanels(List<MenuPanel> menuPanels) {
		this.menuPanels = menuPanels;
	}
	public boolean isIgnoreHierarchy() {
		return ignoreHierarchy;
	}
	public void setIgnoreHierarchy(boolean ignoreHierarchy) {
		this.ignoreHierarchy = ignoreHierarchy;
	}
	
	public String getNode() {
		return node;
	}
	public void setNode(String node) {
		this.node = node;
	}
	public List<Menu> getMenus() {
		return menus;
	}
	public void setMenus(List<Menu> menus) {
		this.menus = menus;
	}
	
	
	
	
}


第五步:编写Server

public List<MenuPanel> queryMenuPanel()
{
		return menuPanelDao.queryMenuPanel();
}

@Override
public List<Menu> queryChildMenu(String id) {
		// TODO Auto-generated method stub
		return menuPanelDao.queryChildMenu(id);
}

第六步:编写DAO

/**
	 * 查询MenuPanl,作为accordion菜单和Tree菜单的根节点
	 */
	@Override
	public List<MenuPanel> queryMenuPanel() {
		// TODO Auto-generated method stub
		String hql="from MenuPanel";
		Query query=super.getSession().createQuery(hql);
		return query.list();
	}

	/**
	 * 使用本地查询,查询父节点等于当前节点的菜单
	 */
	@Override
	public List<Menu> queryChildMenu(String id) {
		// TODO Auto-generated method stub
		String hql="select id,text,url,leaf from menu where parentId=?";
		SQLQuery query=super.getSession().createSQLQuery(hql);
		query.setString(0, id);
		List<Menu> nodes=new ArrayList<Menu>();
		List<Object[]> list=query.list();
		for (Object[] object : list) {
			Menu menu=new Menu();
			menu.setId(object[0]==null?null:object[0].toString());
			menu.setText(object[1]==null?null:object[1].toString());
			menu.setUrl(object[2]==null?null:object[2].toString());
			menu.setLeaf(object[3]==null?false:Boolean.parseBoolean(object[3].toString()));
			nodes.add(menu);
		}
		
		return nodes;
	}

第七步:POJO

package com.mcs.user.pojo;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import com.mcs.pojo.base.GenericObject;

/**
 * 这个是MenuPanel对应的是accordion菜单
 * @author lishengbo
 *
 */
@Entity
@Table(name = "MENUPANEL")
public class MenuPanel extends GenericObject {
	private String text;
	private List<Menu> menus=new ArrayList<Menu>();

	public MenuPanel() {
	}

	public MenuPanel(String text) {
		this.text = text;
	}
	public MenuPanel(long id,String text) {
		this.text = text;
		super.setId(id+"");
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

	/**
	 * 一对多关联Menu菜单,作为Tree中的根节点,这里使用立即加载和MenuPanel一起加载到客户端,注意,一定要使用立即加载
	 * @return
	 */
	@OneToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER,mappedBy="menuPanel")
	public List<Menu> getMenus() {
		return menus;
	}

	public void setMenus(List<Menu> menus) {
		this.menus = menus;
	}

}


package com.mcs.user.pojo;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import com.mcs.pojo.base.GenericObject;


@Entity
@Table(name="MENU")
public class Menu extends GenericObject{
	private String text;
	private String url;
	private boolean leaf=true;//默认是叶子节点
	private MenuPanel menuPanel;
	private List<Menu> children;
	private Menu menu;
	
	
	public Menu() {
	}

	public Menu(String text, String url) {
		super();
		this.text = text;
		this.url = url;
	}
	public Menu(long id,String text, String url,MenuPanel menuPanel) {
		super();
		super.setId(id+"");
		this.text = text;
		this.url = url;
		this.menuPanel=menuPanel;
	}
	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	@ManyToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE}, targetEntity=MenuPanel.class)
	@JoinColumn(name="menuPanelId",referencedColumnName="id",insertable=true,updatable=true)
	public MenuPanel getMenuPanel() {
		return menuPanel;
	}

	public void setMenuPanel(MenuPanel menuPanel) {
		this.menuPanel = menuPanel;
	}

	@Column(length=1000)
	public boolean isLeaf() {
		return leaf;
	}

	public void setLeaf(boolean leaf) {
		this.leaf = leaf;
	}

	@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY,mappedBy="menu")
	public List<Menu> getChildren() {
		return children;
	}

	public void setChildren(List<Menu> children) {
		this.children = children;
	}

	@ManyToOne(cascade={CascadeType.PERSIST,CascadeType.MERGE}, targetEntity=Menu.class)
	@JoinColumn(name="parentId",referencedColumnName="id",insertable=true,updatable=true)
	public Menu getMenu() {
		return menu;
	}

	public void setMenu(Menu menu) {
		this.menu = menu;
	}
	
	
}

第九步:数据库中的数据展示

   Menu表中的数据



MenuPanel表中的数据



1、Ext类 ………………………………… 2 2、Array类 …………………………… 4 3、Number类 …………………………… 4 4、String类 …………………………… 4 5、Date类 ……………………………… 5 6、Function类 ………………………… 6 7、Ext.Element类 ………………………… 7 8、Ext.DomQuery类 ………………… 13 9、Ext.DomHelper类 …………………… 14 10、Ext.Template类 …………………… 14 11、Ext.EventManager类 ……………… 15 12、Ext.EventObject类 ………………… 15 13、Ext.CompositeElement类 ………… 16 14、Ext.CompositeElementLite类 ……… 16 15、Ext.Fx类 …………………………… 16 16、Ext.KeyNav类 ……………………… 19 17、Ext.KeyMap类 …………………… 19 18、Ext.util.JSON类 ……………………… 20 19、Ext.util.Format类 ………………… 20 20、Ext.util.DelayedTask类 ……………… 20 21、Ext.util.TaskRunner类 …………… 21 22、Ext.util.TextMetrics类 …………… 21 23、Ext.XTemplate类 ………………… 21 24、Ext.data.Connection类 ……………… 22 25、Ext.Ajax类 ………………………… 22 26、Ext.data.Record类 ………………… 23 27、Ext.data.DataProxy类 …………… 24 28、Ext.data.HttpProxy类 …………… 24 29、Ext.data.MemoryProxy类 ……… 25 30、Ext.data.ScriptTagProxy类 ………… 25 31、Ext.data.DataReader类 ……………26 32、Ext.data.ArrayReader类 …………… 26 33、Ext.data.JsonReader类 …………… 26 34、Ext.data.XmlReader类 …………… 27 35、Ext.data.Store类 …………………… 28 36、Ext.data.GroupingStore类 ………… 32 37、Ext.data.SimpleStore类 ………… 34 38、Ext.data.Tree类 …………………… 34 39、Ext.data.Node类 ………………… 34 40、Ext.Action类 ……………………… 35 41、Ext.Button类 …………………… 36 42、Ext.SplitButton类 ……………… 38 43、Ext.CycleButton类 ……………… 39 44、Ext.form.BasicForm类 …………… 40 45、Ext.form.Field类 …………………… 41 46、Ext.form.Checkbox类 …………… 42 47、Ext.form.Radio类 ………………… 43 48、Ext.form.HtmlEditor类 …………… 43 49、Ext.form.TextField类 …………… 44 50、Ext.form.NumberField类 ………… 44 51、Ext.form.TextArea类 …………… 45 52、Ext.form.TriggerField类 ……… 45 53、Ext.form.DateField类 ………… 45 54、Ext.form.ComboBox类 ……………… 46 55、Ext.form.TimeField类 ………… 47 56、Ext.menu.Menu类 ………………… 50 57、Ext.menu.BaseItem类 …………… 50 58、Ext.menu.Adapter类 ……………… 51 59、Ext.menu.Item类 ………………… 51 60、Ext.menu.CheckItem类 …………… 51 61、Ext.menu.Separator类 ………… 52 62、Ext.menu.TextItem类 …………… 52 63、Ext.Toolbar类 …………………… 55 64、Ext.Toolbar.Item类 ……………… 56 65、Ext.Toolbar.Separator类 ……… 56 66、Ext.Toolbar.Spacer类 …………… 56 67、Ext.Toolbar.TextItem类 ……… 56 68、Ext.Toolbar.Fill类 ……………… 56 69、Ext.grid.ColumnModel类 ……… 58 70、Ext.grid.PropertyColumnModel类 … 59 71、Ext.grid.GridView类 …………… 59 72、Ext.grid.GroupingView类 ………… 60 73、Ext.grid.EditorGridPanel类 ……… 62 74、Ext.grid.PropertyGrid类 …………… 65
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lishengbo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值