最近做一个项目,需要一个树形菜单,本来可以使用DTree实现,也比较简单,不过觉得最近EXT火的很,于是考虑使用EXt 的tree ,做一个异步刷新的树,也就是在点了父亲节点才会去异步夹杂孩子结点,先发个图晒晒!
实现原理:树的每一个节点都有自己的ID,和它的父亲节点的ID,还有自己的文本内容,以及点击后在哪个frame中打开哪个连接,是否是叶子节点等内容,树的第一级节点的父亲节点的ID我们将它置为0,以后每次点解一个非叶子节点的时候,我们都去异步加载他的所有孩子结点,将信息组装成JSON字符串,返回给前台,前台的EXT Tree使用JSON数据构造树
主要步骤:
第一步:构造 树的 表结构
CREATE TABLE `ump_functions` (
`item_id` int(11) NOT NULL,
`item_name` varchar(60) DEFAULT NULL,
`parent_id` int(11) DEFAULT NULL,
`isleaf` tinyint(1) DEFAULT NULL,
`Item_url` varchar(60) DEFAULT NULL,
`remark` varchar(120) DEFAULT NULL,
PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk
第二步 构造和表关联的 javaBean对象 FuctionTreeNode.java
package cn.com.xinli.tree.bean;
/**
*
* @author huxl
*
* 代表系统左边的导航树的节点,根据节点的信息 异步动态加载 extTree
* 根节点的 父节点的id是0
*/
public class FuctionTreeNode
{
/*树节点id*/
private int id;
/*树节点名称*/
private String text;
/*树节点url*/
private String href;
/*点击叶子在指定的 frame中刷新*/
private String hrefTarget="_blank";
/*是否是叶子节点 */
private boolean leaf;
/*树节点的样式*/
private String cls;
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getText()
{
return text;
}
public void setText(String text)
{
this.text = text;
}
public String getHref()
{
return href;
}
public void setHref(String href)
{
this.href = href;
}
public String getHrefTarget()
{
return hrefTarget;
}
public void setHrefTarget(String hrefTarget)
{
this.hrefTarget = hrefTarget;
}
public boolean isLeaf()
{
return leaf;
}
public void setLeaf(boolean leaf)
{
this.leaf = leaf;
}
public String getCls()
{
return cls;
}
public void setCls(String cls)
{
this.cls = cls;
}
}
第三步 根据节点id去找节点的孩子结点 FuctionTreeDaoImpl.java
package cn.com.xinli.tree.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import cn.com.xinli.tree.dao.FuctionTreeDao;
import cn.com.xinli.tree.bean.FuctionTreeNode;
import cn.com.xinli.tree.util.DBUtil;
public class FuctionTreeDaoImpl implements FuctionTreeDao
{
private Logger log=Logger.getLogger(FuctionTreeDaoImpl.class);
public List<FuctionTreeNode> queryNodeById(String nodeId) throws Exception
{
Connection conn=null;
PreparedStatement pstmt;
ResultSet rs;
List<FuctionTreeNode> nodeList=new ArrayList<FuctionTreeNode>();
try {
conn=DBUtil.getConnection();
// String sql="select t.* from ump_functions t ,ump_role_function s where t.item_id = s.item_id and t.parent_id="+nodeId+" and s.role_id="+roleId ;
String sql="select t.* from ump_functions t where t.parent_id="+nodeId;
log.info("sql:"+sql);
pstmt=conn.prepareStatement(sql);
//pstmt.setInt(1, nodeId);
rs=pstmt.executeQuery();
while(rs.next())
{
FuctionTreeNode node=new FuctionTreeNode();
node.setId(rs.getInt("item_id"));
node.setText(rs.getString("item_name"));
node.setLeaf(rs.getBoolean("isleaf"));
node.setHref(rs.getString("item_url"));
nodeList.add(node);
}
return nodeList;
}
catch (Exception e)
{
log.info("查询节点出错:", e);
throw new Exception("查询节点出错");
}
finally
{
if(conn!=null)
{
try
{
conn.close();
}
catch (SQLException e)
{
log.info("数据库连接出错:", e);
throw new Exception("数据库连接出错");
}
}
}
}
}
第4步: 写ext 的 tree 的js
/* * Ext JS Library 2.0.1 * Copyright(c) 2006-2008, Ext JS, LLC. * licensing@extjs.com * * http://extjs.com/license */ Ext.onReady(function(){ //从本地加载树的图片 Ext.BLANK_IMAGE_URL = 'extjs/resources/images/vista/s.gif'; var Tree = Ext.tree; var tree = new Tree.TreePanel({ el:'tree-div', rootVisible:true, //隐藏根节点 border:true, //边框 animate:true, //动画效果 autoScroll:true, //自动滚动 enableDD:false, //拖拽节点 containerScroll:true, loader: new Tree.TreeLoader({ // dataUrl:'http://localhost:9090/struts2/menus.action' }) }); // set the root node var root = new Tree.AsyncTreeNode({ text: '统一监控平台', draggable:false, //树的根节点的ID设置成0有个好处就是初始化树的时候默认先加载父亲节点为0的孩子结点 id:'0' }); tree.setRootNode(root); tree.on('beforeload', function(node) { tree.loader.dataUrl='http://localhost:9090/struts2/menus!loadTree.action?pid='+node.id //tree.loader.dataUrl='treedata2.txt' }); // render the tree tree.render(); root.expand(); //展开树的所有节点,有一些特殊需求会要求我们一次展开所有的节点,传true //root.expand(true); //只展开根节点 root.expand(); });
还有一个Ext 发送Ajax 请求的一个小例子 主要是使用到了 Ext.Ajax.request
附件中 是 整个项目 ,下载即可使用
1.需要建立数据库和表结构 sql 和数据脚本 见上面
2.讲项目放在TOMCat 可以直接运行
对于前台接收json字符串 的一个思考(2010.05.23):
对于上面的例子,我们是使用 menu.jsp 来接收后台的返回的json字符串
menu.jsp 的内容很简单 2行
<%@ taglib prefix="s" uri="/struts-tags" %>
<s:property value="menuString" escape="false"/>
其中:
<s:property>标签的escape属性默认值为true,即不解析html代码,直接将其输出。
如:novel.NTitle的值为
<font color=red>邪门</font>
则<s:property value="#novel.NTitle">就直接输出原值。若想让标签解析html标签输出:
邪门
则将escape设为false
<s:property value="#novel.NTitle" escape="false"/>
这个例子中我们希望 输出解析后的值,不想看到html标签
<action name="menus" class="cn.com.xinli.tree.action.QueryNodeAction">
<result name="success">/menu.jsp</result>
</action>
并且使用的是 <package name="struts2" extends="struts-default">
我们把 后台的json字符串放在了 menuString 字符串中,
而extends="struts-default" 下面的action 都是页面跳转的意思
<result name="success">/menu.jsp</result>
的意思就是 action执行后 跳转到 menu.jsp
而
tree.loader.dataUrl='http://localhost:9090/struts2/menus!loadTree.action?pid='+node.id
是ext 中的方法 他是用来接受json字符串的 ,首先看看 tree.loader.dataUrl 的源码,可见
tree.loader.dataUrl 是一个ajax请求 接受返回结果,而loadTree.action 返回一个json字符串,刚好绑定在
menu.jsp 中,因此 tree.loader.dataUrl 得到了json字符串,由于是ajax请求,也不会引起页面跳转
requestData : function(node, callback){ if(this.fireEvent("beforeload", this, node, callback) !== false){ this.transId = Ext.Ajax.request({ method:this.requestMethod, url: this.dataUrl||this.url, success: this.handleResponse, failure: this.handleFailure, scope: this, argument: {callback: callback, node: node}, params: this.getParams(node) }); }else{ // if the load is cancelled, make sure we notify // the node that we are done if(typeof callback == "function"){ callback(); }
这种从后台得到 json 字符串,传递到前台一个Jsp页面的做法有点 臃肿,比如我们还需要在 action中 使用json.jar
使用下面的方法 将一个对象转化为 json 字符串
JSONArray jsonObject = JSONArray.fromObject(menus);
menuString = jsonObject.toString();
其实 strtus2已经为我们提供了action 返回 json字符串的支持,下面我们改造 以前手工得到json字符,用一个Jsp页面来接受的例子,换成 使用strtus2 json 插件支持
步骤:
1.首先在以前的项目中加入
jsonplugin-0.32.jar
2. 修改 后台构造Json串的方法
package cn.com.xinli.tree.action;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import cn.com.xinli.tree.bean.FuctionTreeNode;
import cn.com.xinli.tree.dao.FuctionTreeDao;
import cn.com.xinli.tree.dao.impl.FuctionTreeDaoImpl;
public class QueryNodeAction extends BaseAction
{
private Logger log=Logger.getLogger(QueryNodeAction.class);
private String menuString;
/*返回给前台树的Json 对象,使用struts2 json插件可以直接将这个对象转换为
* json字符串
* 需要在struts.xml中进行配置
*
* */
private List<FuctionTreeNode> menusList;
public List<FuctionTreeNode> getMenusList() {
return menusList;
}
public void setMenusList(List<FuctionTreeNode> menusList) {
this.menusList = menusList;
}
public String getMenuString() {
return menuString;
}
public void setMenuString(String menuString) {
this.menuString = menuString;
}
/*异步加载树*/
public String loadTree()
{
log.info("开始根据父亲节点查找孩子节点");
HttpServletRequest request=getRequest();
String nodeId=request.getParameter("pid");
log.info("父亲节点的ID是:"+nodeId);
FuctionTreeDao treeDao=new FuctionTreeDaoImpl();
try
{
menusList=treeDao.queryNodeById(nodeId);
log.info("孩子结点的数目是:"+menusList.size());
//以下三行代码可以让返回的JSON数据不包含需要过滤的字段
/*
JsonConfig config = new JsonConfig();
//过滤cls属性
config.setExcludes( new String[]{"cls"});
JSONArray jsonObject2 = JSONArray.fromObject(menus,config);
*/
//JSONArray jsonObject = JSONArray.fromObject(menus);
// menuString = jsonObject.toString();
}
catch (Exception e)
{
e.printStackTrace();
}
return "success";
}
/*ajax 调用,返回json 字符串*/
public String ajax()
{
//HttpServletResponse resp= getResponse();
//resp.setContentType("application/json;charset=UTF-8");
HttpServletRequest req= getRequest();
try
{
System.out.println("从前台Ajax请求参数是:"+req.getParameter("foo"));
menuString="[{success:true,msg:'修改权限成功'}]";
//resp.getWriter().write("{success:true,msg:'修改权限成功'}");
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return "aa";
}
}
3 .修改 struts.xml 关键是 修改 packega 的 extends="json-default"
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="struts2" extends="json-default"> <action name="login" class="cn.com.xinli.tree.action.LoginAction"> <result name="success">/result.jsp</result> <result name="input">/login2.jsp</result> <result name="error">/login2.jsp</result> </action> <action name="menus" class="cn.com.xinli.tree.action.QueryNodeAction"> <result name="success" type="json"> <param name="root">menusList</param> </result> <result name="aa" type="json"> <param name="root">menuString</param> </result> </action> </package> </struts>
其中
<result name="success" type="json">
<param name="root">menusList</param>
</result>
代表 这个action的返回结果是 json字符串, 其中json串的 root节点 为 menusList
如果你返回的对象 不需要那个属性 可以这么写,则格式化 page 对象为json字符串的时候
conditions,limit,start,success,objCondition 几个属性将被忽略
<param name="root">page</param>
<param name="excludeProperties">
conditions,limit,start,success,objCondition
</param>
相反的 如果只需要对象的 某个属性转化为Json串 可以这么写
<result type="json">
<param name="includeProperties">
success,msg
</param>
</result>
附件中 extTreeJSON.rar 为修改以后例子 ,其中 依赖的Lib 可以下载 以前的lib