Extjs4 treePanel异步加载菜单(后台从数据库读取)

时间:2022-04-16 13:48:44

运行环境:springMVC+mybatis

一、建表

Extjs4 treePanel异步加载菜单(后台从数据库读取)

说明:0表示此节点为非叶子节点,即此节点还包括了子节点;1表示此节点为叶子节点,即此节点没有子节点。;关于图标iconCls是从Extjs的文件的icons文件夹找的。命名方式是把找到的图标名去掉下划线,然后首字母大写即可。

二、运用mybatis 的generator插件自动生成pojo、映射文件及访问接口并做适当的添加修改。

1.pojo

package com.shyy.web.entity;

import java.util.List;

public class Tree {
private String id; private String text; private String iconCls; private Boolean leaf; private String fatherId; private List<Tree> children; public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getText() {
return text;
} public void setText(String text) {
this.text = text;
} public String getIconCls() {
return iconCls;
} public void setIconCls(String iconCls) {
this.iconCls = iconCls;
} public Boolean getLeaf() {
return leaf;
} public void setLeaf(Boolean leaf) {
this.leaf = leaf;
} public String getFatherId() {
return fatherId;
} public void setFatherId(String fatherId) {
this.fatherId = fatherId;
} public List<Tree> getChildren() {
return children;
} public void setChildren(List<Tree> children) {
this.children = children;
} @Override
public String toString() {
return "Tree{" +
"id='" + id + '\'' +
", text='" + text + '\'' +
", iconCls='" + iconCls + '\'' +
", leaf=" + leaf +
", fatherId='" + fatherId + '\'' +
", children=" + children +
'}';
}
}

2.映射文件:

需要注意的是, <result property="leaf" column="leaf" jdbcType="TINYINT" javaType="Boolean" />这样的话,mybatis就会自动将数据库中的TINYINT类型的leaf转化成布尔类型,0会转化为false,1会转化为true。这里一定要有这个,不然在执行时会报异常。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.shyy.web.service.TreeMapper" >
<resultMap id="BaseResultMap" type="com.shyy.web.entity.Tree" >
<id column="id" property="id" jdbcType="VARCHAR" />
<result column="text" property="text" jdbcType="VARCHAR" />
<result column="iconCls" property="iconCls" jdbcType="VARCHAR" />
<result column="leaf" property="leaf" jdbcType="TINYINT" javaType="Boolean" />
<result column="fatherId" property="fatherId" jdbcType="VARCHAR" />
</resultMap>
<sql id="Base_Column_List" >
id, text, iconCls, leaf, fatherId
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String" >
select
<include refid="Base_Column_List" />
from t_tree
where id = #{id,jdbcType=VARCHAR}
</select>
<select id="getFather" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM t_tree
WHERE fatherId IS NULL
</select>
<select id="getChildren" resultMap="BaseResultMap" parameterType="java.lang.String">
SELECT
<include refid="Base_Column_List" />
FROM t_tree
WHERE fatherId = #{fatherId}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.String" >
delete from t_tree
where id = #{id,jdbcType=VARCHAR}
</delete>
<insert id="insert" parameterType="com.shyy.web.entity.Tree" >
insert into t_tree (id, text, iconCls,
leaf, fatherId)
values (#{id,jdbcType=VARCHAR}, #{text,jdbcType=VARCHAR}, #{iconCls,jdbcType=VARCHAR},
#{leaf,jdbcType=TINYINT}, #{fatherId,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" parameterType="com.shyy.web.entity.Tree" >
insert into t_tree
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="id != null" >
id,
</if>
<if test="text != null" >
text,
</if>
<if test="iconCls != null" >
iconCls,
</if>
<if test="leaf != null" >
leaf,
</if>
<if test="fatherId != null" >
fatherId,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="id != null" >
#{id,jdbcType=VARCHAR},
</if>
<if test="text != null" >
#{text,jdbcType=VARCHAR},
</if>
<if test="iconCls != null" >
#{iconCls,jdbcType=VARCHAR},
</if>
<if test="leaf != null" >
#{leaf,jdbcType=TINYINT},
</if>
<if test="fatherId != null" >
#{fatherId,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.shyy.web.entity.Tree" >
update t_tree
<set >
<if test="text != null" >
text = #{text,jdbcType=VARCHAR},
</if>
<if test="iconCls != null" >
iconCls = #{iconCls,jdbcType=VARCHAR},
</if>
<if test="leaf != null" >
leaf = #{leaf,jdbcType=TINYINT},
</if>
<if test="fatherId != null" >
fatherId = #{fatherId,jdbcType=VARCHAR},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="com.shyy.web.entity.Tree" >
update t_tree
set text = #{text,jdbcType=VARCHAR},
iconCls = #{iconCls,jdbcType=VARCHAR},
leaf = #{leaf,jdbcType=TINYINT},
fatherId = #{fatherId,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

3.访问映射文件的接口(没有实现类,直接由该接口访问对应的映射文件)

package com.shyy.web.service;

import com.shyy.web.entity.Tree;

import java.util.List;

public interface TreeMapper {
int deleteByPrimaryKey(String id); int insert(Tree record); int insertSelective(Tree record); Tree selectByPrimaryKey(String id); List<Tree> getFather(); List<Tree> getChildren(String id); int updateByPrimaryKeySelective(Tree record); int updateByPrimaryKey(Tree record);
}

4.控制层代码

package com.shyy.web.controller.anntation;

import java.util.ArrayList;
import java.util.List; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import com.shyy.web.entity.Tree;
import com.shyy.web.service.TreeMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import com.shyy.web.controller.response.EmptyResponse;
import com.shyy.web.controller.response.NormalResponse;
import com.shyy.web.controller.response.Response;
import com.shyy.web.entity.Privilege;
import com.shyy.web.service.PrivilegeService;
import com.shyy.web.service.impl.PrivilegeServiceImpl; @Controller
@RequestMapping("/menu/")
public class PrivilegeController { Logger logger = LoggerFactory.getLogger(PrivilegeController.class); @Autowired
private TreeMapper treeMapper; @SuppressWarnings("unused")
@RequestMapping("showmyMenu")
@ResponseBody
public List<Tree> myMenus(HttpServletRequest req, HttpServletResponse resp) {
System.out.println("-aaa--");
List<Tree> pris = treeMapper.getFather();//一级菜单
List<Tree> menus = new ArrayList<Tree>(); for(Tree pri:pris){//迭代一级菜单找二级菜单
List<Tree> prisNode = treeMapper.getChildren(pri.getId());//二级菜单
pri.setChildren(prisNode); for (Tree privilege : prisNode) {//迭代二级菜单找三级菜单
List<Tree> childNode = treeMapper.getChildren(privilege.getId());//三级菜单
privilege.setChildren(childNode);
//如果有四级菜单,则需迭代三级菜单找四级菜单
}
menus.add(pri);
} System.out.println(menus); if (menus != null || menus.size() > 0) {
return menus;
} else {
return null;
}
} public String test(){
return "wang";
} }

js代码:

Ext.onReady(function(){

            var model = Ext.define("TreeModel", { // 定义树节点数据模型
extend : "Ext.data.Model",
fields : [{name : "id",type : "string"},
{name : "text",type : "string"},
{name : "iconCls",type : "string"},
{name : "leaf",type : "boolean"},
{name : 'url',type:"string"},
{name : 'description',type:"string"}]
}); var store = Ext.create('Ext.data.TreeStore', {
model : model,//定义当前store对象的Model数据模型
// autoLoad : true,
proxy : {
type : 'ajax',
url : '../menu/showmyMenu',//请求
reader : {
type : 'json',
// root : 'data'//数据
}
},
root : {//定义根节点,此配置是必须的
// text : '管理菜单',
expanded : true
} }); Ext.create('Ext.tree.Panel', {
title: 'Simple Tree',
width: 200,
height: 550,
store: store,
rootVisible: false,//隐藏根节点
renderTo: Ext.getBody()
}); });

说明:在http://liuchangming1993-126-com.iteye.com/blog/1938482文章中的博主用的struts2,返回形式与本文的springMVC不同,因此这里没有指定root属性。

下面看一下js中的ajax请求返回的数据(片段):

[Tree{id='101', text='图像报表', iconCls='Chartbar', leaf=false, fatherId='null',
children=[Tree{id='1001', text='饼状图', iconCls='Chartpie', leaf=false, fatherId='101', children=[Tree{id='10001', text='饼状图一', iconCls='Chartpieadd', leaf=true, fatherId='1001', children=null}, Tree{id='10002', text='饼状图二', iconCls='Chartpiedelete', leaf=true, fatherId='1001', children=null}]},
Tree{id='1002', text='线状图', iconCls='Chartline', leaf=false, fatherId='101', children=[Tree{id='10003', text='线状图一', iconCls='Chartcurveadd', leaf=true, fatherId='1002', children=null}, Tree{id='10004', text='线状图二', iconCls='Chartcurvedelete', leaf=true, fatherId='1002', children=null}]}]}]

可以看出是reader 将对象的数据转化成了json格式。

运行效果:

Extjs4 treePanel异步加载菜单(后台从数据库读取)

方法二

这里的逻辑是一次性加载完所有的菜单返回至前台,在http://liuchangming1993-126-com.iteye.com/blog/1938482的博客中用到的是另一种方式,即根据用户点击的菜单展开子菜单。

现在用这种方式实现一下:

说明:因为之前建的表t_tree与pojo的主键都是id,发现与Extjs冲突,所以这次将表的主键改为tid,然后自动生成相应代码。

一、在映射文件中加一个sql

    <select id="findLeaf" parameterType="String" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM t_tree
WHERE 1=1
<choose>
<when test="_parameter!=null and _parameter!=''">
and fatherId = #{fatherId}
</when>
<otherwise>
and fatherId IS NULL
</otherwise>
</choose>
</select>

二、修改访问映射文件的接口,即增加访问上面sql的方法

package com.shyy.web.service;

import com.shyy.web.entity.Tree;

import java.util.List;

public interface TreeMapper {
int deleteByPrimaryKey(String tid); int insert(Tree record); int insertSelective(Tree record); Tree selectByPrimaryKey(String tid); List<Tree> getFather(); List<Tree> getChildren(String id); List<Tree> findLeaf(String id); int updateByPrimaryKeySelective(Tree record); int updateByPrimaryKey(Tree record);
}

三、修改控制器,增加一个方法

    @SuppressWarnings("unused")
@RequestMapping("showmyMenu2")
@ResponseBody
public List<Tree> myMenus2(HttpServletRequest req, HttpServletResponse resp,@RequestParam String tid) { System.out.println("tid:"+tid); List<Tree> menus = new ArrayList<Tree>();
menus = treeMapper.findLeaf(tid); if (menus != null || menus.size() > 0) {
return menus;
} else {
return null;
}
}

四、修改js

        Ext.onReady(function(){
var store = Ext.create('Ext.data.TreeStore', {
autoLoad : true,
proxy : {
type : 'ajax',
url : '../menu/showmyMenu2',//请求
reader : {
type : 'json',
// root : 'menuList'//数据
},
//传参
extraParams : {
tid : ''
}
},
root : {
// text : '管理菜单',
expanded : true
},
listeners : {
'beforeexpand' : function(node,eOpts){
//点击父亲节点的菜单会将节点的id通过ajax请求,将到后台
this.proxy.extraParams.tid = node.raw.tid;
}
}
}); Ext.create('Ext.tree.Panel', {
renderTo : Ext.getBody(),
title : '动态加载TreePanel',
width : 300,
height : 500,
useArrows : true,
rootVisible: false,
store : store
}); });

注:

1.autoLoad 这个配置默认是false,但是即使注释掉此配置也是没有影响的;

2.可以看出上面的js已去掉了model 配置,是没有影响的,在Ext.data.TreeStore的API中有这样的说明:

Using Models 用例模型

如果未指定模型Model,将创建实现于Ext.data.NodeInterface的一种隐式模型。 标准的Tree字段列表也将被复制到Model上来保持自身的状态。 在Ext.data.NodeInterface文档中列出了这些字段。

那么model是干嘛的(如上面的创建的继承于Ext.data.Model的实例"TreeModel"),NodeInterface又是干嘛的?下面看一下Ext.data.NodeInterface的API简介:

本类是一个应用到Model的原型上的方法集合,使其具有Node API。这意味着树状模型 具有所有与树相关的方法。通常情况下, 开发者不会直接使用本类。为了保存树的状态和UI, 本类会在模型上创建一些额外的字段(如果它们不存在)。 这些字段记录为config选项。

到这里model的作用就明确了,举例来说,像本文定义的model,比如它有一个字段名为“leaf”,那么由于在Ext.data.NodeInterface中也具有这个配置属性,所以这个leaf的值会在加载时起作用。其他定义的字段在树面板上起到什么作用则均可在Ext.data.NodeInterface找到答案。

在api中可以看出Ext.data.TreeStore继承了Ext.data.NodeStore,而Ext.data.NodeStore的接口正是Ext.data.NodeInterface

3.上面js中listeners 定义的‘beforeexpand’事件也来源于Ext.data.NodeInterface。在本节点展开前触发。

4.reader 中的root配置,在Ext.data.TreeStore的API中有这样的说明:

Reading Nested Data 读取内嵌数据

对于树读取内嵌数据,在Ext.data.reader.Reader中必须配置一个root属性, 这样reader可以找到关于每个节点的内嵌数据。 如果未指定root,那么它将默认为'children'。

这里介绍它的作用是“读取内嵌数据”,我并未指定,然而它说默认为‘children’,刚好我在设计菜单的pojo时对于子菜单属性的引用名就是children:

private List<Tree> children;

所以如果上面的引用是别的什么,那么就要指定了?——这里不作测试了。

运行效果:

Extjs4 treePanel异步加载菜单(后台从数据库读取)