最近在忙项目,导致之前的easyUI组件的博客暂停了一阵,再加上重新捡起来java后端代码,中间还是颇费时间的。之前讲过easyUI+springmvc的整体构建项目,在这里就直接进入主题了。
针对之前出现的tree代码不全的问题,在这里再次抱歉了。
好了言归正传,来先看看tree官方提供的API是怎么实现的。
1.easyUI tree组件的定义
官方提供了两种是实现方式:
1.使用easyUI提供的标签样式 easyui-tree来标记div是easyUI下的tree元素。
<ul id="tt" class="easyui-tree">
<li>
<span>Folder</span>
<ul>
<li>
<span>Sub Folder 1</span>
<ul>
<li><span><a href="#">File 11</a></span></li>
<li><span>File 12</span></li>
<li><span>File 13</span></li>
</ul>
</li>
<li><span>File 2</span></li>
<li><span>File 3</span></li>
</ul>
</li>
<li><span>File21</span></li>
</ul>
2.使用js来定义绑定tree
<ul id="tt"></ul>
$('#tt').tree({
url: ...,
loadFilter: function(data){
if (data.d){
return data.d;
} else {
return data;
}
}
});
url:指定后台请求路径loadFilter:返回后台response数据
3.来看看API所需要的json格式
[
{
"id": 1,
"text": "china",
"state": "open",
"attributes": "xxxxxxxxxxxx",
"children": [
{
"id": 2,
"text": "beijing",
"state": "open",
"attributes": "xxxxxxxxxxx",
"children": []
},
{
"id": 3,
"text": "shanghai",
"state": "open",
"attributes": "xxxxxxxxxxx",
"children": []
},
{
"id": 4,
"text": "anhui",
"state": "open",
"attributes": "xxxxxxxxxxx",
"children": [
{
"id": 5,
"text": "hefei",
"state": "closed",
"attributes": "xxxxxxxxxxx"
},
{
"id": 6,
"text": "wuhu",
"state": "closed",
"attributes": "xxxxxxxxxxx"
}
]
}
]
}
]
text:要显示文本节点信息。
state:当前节点打开或者关闭,open打开,closed关闭
attributes:可以用来添加自定义属性,一般表示url,因为我们需要在点击节点的时候去做一些事情,这个事情就是去后台请求一些数据用于在center中展示数据,比如点击北京这个节点,我想获取该地区所有的人口数量,并以列表的形式展现等。
children:数组形式,孩子节点存储。
关于使用js定义绑定树事件中也可以定义自己的不可变得数据或者初始化数据使用其data属性就可以,但是一般我们的数据都是通过后台存储的,可能有同时好几个人在处理这一棵树,如果数据不通过后台,那么你所看到的数据就不能保证是最新的。
2.easyUI结合后台获取数据
通过上面的API介绍,我们知道了easyUI的tree的使用方式,现在我们来结合前后台来实现这个功能。
先看一下效果图吧:
2.1 前端定义easyUI-tree
1.html绑定div-id
<%@ page language="java" contentType="text/html; charset=utf8"
pageEncoding="utf8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf8">
<title>Welcome to easyui</title>
<!-- 引入依赖jquery -->
<script type="text/javascript" src = "js/jquery-1.7.2.min.js"></script>
<!-- 引入easyUI js文件 -->
<script type="text/javascript" src="js/jquery-easyui-1.5.3/jquery.easyui.min.js"></script>
<!-- 引入EasyUI的样式文件-->
<link rel="stylesheet" href="js/jquery-easyui-1.5.3/themes/default/easyui.css" type="text/css"/>
<!-- 引入EasyUI的图标样式文件-->
<link rel="stylesheet" href="js/jquery-easyui-1.5.3/themes/icon.css" type="text/css"/>
</head>
<body class="easyui-layout">
<div data-options="region:'east',split:true" title="East" style="width:200px;">
</div>
<div data-options="region:'west',split:true" title="West" style="width:200px;">
<div id="tree"></div>
</div>
<div data-options="region:'north',title:'',split:true" style="height:100px;">
<div style="width:100%;text-align:center;line-height:100px">
Welcome to Easy-UI Classroom !
</div>
</div>
<div data-options="region:'center',title:'',split:true" style="text-align:center">
</div>
<div data-options="region:'south',title:''" style="padding:5px;background:#eee;">
<div style="width:100%;text-align:center">
@版权所有,转载请注明出处<br>
作者:不醉怎能入睡<br>
Emial:chenqk_123@163.com<br>
QQ:846049243
</div>
</div>
</body>
</html>
上面简单做了个easyUI的layout,同时在左侧定义了一个div并给定个id="tree",用于在js中绑定。2. js中绑定tree事件处理
$(function(){
$("#tree").tree({
url:'getNodesByParentId.do?id=1',//请求路径,id为根节点的id
onLoadSuccess:function(node,data){
var tree = $(this);
console.info(data);
if(data){
$(data).each(function(index,d) {
if (this.state=='closed') {
tree.tree('expandAll');
}
});
}
}
})
})
在登录之后跳转到主页面中进行tree的初始化,通过接口去后台取数据,因为在这里数据库中设计的根节点的id默认为1,所以往后台传递的值也就为1.其实这里可以看出,前台的使用是很简单的,定义并绑定tree的组件就行,麻烦的是后台。
2.2 tree后台支持
1.数据库结构设计
为了更好的展示json格式,给出了初始定义的一些节点 。
2. tree实体定义
package com.chenqk.springmvc.entity;
/**
* 测试类,用于实现easyUI tree组件的使用
* @author chenqk
*
*/
public class Tree {
private int id;
private int pid;
private String text;
private String attributes;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getAttributes() {
return attributes;
}
public void setAttributes(String attributes) {
this.attributes = attributes;
}
}
3. 配置文件
3.1 spring配置文件tree
spring配置文件中除了配置scan外,还需要实例化数据库,然后引入tree对应的配置文件,在tree配置文件中会实例化装载bean:
<import resource="applicationContext-tree.xml"/>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="treeDao" class="com.chenqk.springmvc.dao.impl.TreeDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
<bean id="treeService" class="com.chenqk.springmvc.service.impl.TreeServiceImpl">
<property name="treeDao" ref="treeDao"></property>
</bean>
<bean id="treeController" class="com.chenqk.springmvc.controller.TreeController">
<property name="treeService" ref="treeService"></property>
</bean>
</beans>
3.2 mybatis配置tree
mybatis的配置为文件在前面提到过,这里不再详细说明,只要按照官方提供的配置顺序就可以了,在这里主要
配置的就是别名和引入mybatis-tree.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 注意:每个标签必须按顺序写,不然蛋疼的DTD会提示错误:
The content of element type "configuration" must match
"(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,
objectWrapperFactory?,plugins?,environments?,mappers?)". -->
<configuration>
<!-- 属性配置 -->
<properties resource="com/chenqk/springmvc/config/properties/jdbc.properties">
<!-- 相同属性:最高优先级的属性是那些作为方法参数的,然后是资源/url 属性,
最后是 properties元素中指定的属性 -->
<property name="username" value="root"/>
<property name="password" value="123qwe"/>
</properties>
<!-- 设置缓存和延迟加载等等重要的运行时的行为方式 -->
<settings>
<!-- 设置超时时间,它决定驱动等待一个数据库响应的时间 -->
<setting name="defaultStatementTimeout" value="25000"/>
</settings>
<!-- 别名 -->
<typeAliases>
<typeAlias alias="userMap" type="com.chenqk.springmvc.entity.UserInfo"/>
<typeAlias alias="treeMap" type="com.chenqk.springmvc.entity.Tree"/>
</typeAliases>
<environments default="development">
<!-- environment 元素体中包含对事务管理和连接池的环境配置 -->
<environment id="development">
<transactionManager type="JDBC" />
<!-- type分三种:
UNPOOLED是每次被请求时简单打开和关闭连接
UNPOOLED的数据源仅仅用来配置以下 4 种属性driver,url,username,password
POOLED :JDBC连接对象的数据源连接池的实现,不直接支持第三方数据库连接池
-->
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<!-- ORM映射文件 -->
<mappers>
<mapper resource="com/chenqk/springmvc/config/mapper/user-mapper.xml"/>
<mapper resource="com/chenqk/springmvc/config/mapper/tree-mapper.xml"/>
</mappers>
</configuration>
mybatis-tree.xml 映射文件配置:
<?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.chenqk.springmvc.entity.Tree">
<resultMap type="com.chenqk.springmvc.entity.Tree" id="treeMap">
<id property="id" column="id"/>
<result property="pid" column="pid"/>
<result property="text" column="text"/>
<result property="attributes" column="attributes"/>
</resultMap>
<select id="getNodeById" resultMap="treeMap" parameterType="integer">
select * from tree where id =#{id};
</select>
<select id="getNodesByParentId" resultMap="treeMap" parameterType="integer">
select * from tree where pid =#{id};
</select>
</mapper>
这里的namespace对应的配置是必不可少的,在DAO中会使用到:sqlSession.selectOne("com.chenqk.springmvc.entity.Tree.getNodeById", id);
resultMap配置实体类型和数据库映射关系。
id是必不可少的,在上面的例子中也有体现。
resultType:数据库返回结果集类型,可以是一个基本类型,也可以是个对象或者集合。
parameterType:传值参数类型,可以是基本类型,也可以是个对象或者集合。
3.3 DAO层及其实现类
package com.chenqk.springmvc.dao;
import java.util.List;
import com.chenqk.springmvc.entity.Tree;
/**
* tree 数据访问层
* @author chenqk
*
*/
public interface TreeDao {
public Tree getNodeById(int id);
public List<Tree> getNodesByParentId(int pid);
}
package com.chenqk.springmvc.dao.impl;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.chenqk.springmvc.dao.TreeDao;
import com.chenqk.springmvc.entity.Tree;
/**
*
* @author chenqk
*
* @Repository 标识数据访问层DAO,将其标识为spring bean,并根据spring配置为扫描功能
* 来识别该注解<context:component-scan/>
* 如此,我们就不再需要在 XML 中显式使用 <bean/> 进行Bean 的配置。Spring 在容器初始化时将
* 自动扫描 base-package 指定的包及其子包下的所有 class文件,
* 所有标注了 @Repository 的类都将被注册为 Spring Bean。
* 为什么 @Repository 只能标注在 DAO 类上呢?这是因为该注解的作用不只是将类识别为Bean,同时
* 它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型。
* Spring本身提供了一个丰富的并且是与具体的数据访问技术无关的数据访问异常结构,用于封装不同的
* 持久层框架抛出的异常,使得异常独立于底层的框架。
*
* @Component 是一个泛化的概念,仅仅表示一个组件 (Bean) ,可以作用在任何层次
* @Service 通常作用在业务层,但是目前该功能与 @Component 相同。
* @Constroller 通常作用在控制层,但是目前该功能与 @Component 相同
* 通过在类上使用 @Repository、@Component、@Service 和 @Constroller 注解,Spring
* 会自动创建相应的 BeanDefinition 对象,并注册到 ApplicationContext 中。
* 这些类就成了 Spring受管组件。这三个注解除了作用于不同软件层次的类,其使用方式与 @Repository
* 是完全相同的。
* 另外,除了上面的四个注解外,用户可以创建自定义的注解,然后在注解上标注 @Component,那么,该自定义
* 注解便具有了与所@Component 相同的功能。不过这个功能并不常用。
* 当一个 Bean 被自动检测到时,会根据那个扫描器的 BeanNameGenerator 策略生成它的 bean名称。
* 默认情况下,对于包含 name 属性的 @Component、@Repository、 @Service
* 和@Controller,会把 name 取值作为 Bean 的名字。如果这个注解不包含 name值或是其他被自定义
* 过滤器发现的组件,默认 Bean 名称会是小写开头的非限定类名。
* 如果你不想使用默认 bean命名策略,可以提供一个自定义的命名策略。
*
* @Autowired 注解与自动配置,编写spring的时候,一直遵循是这样一个规则:所有在spring中注入的bean
* 都建议定义成私有的域变量。并且要配套写上 get 和 set方法。
* 使用该方法它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired的使用来消除
* set ,get方法。
* 当 Spring 容器启动时,AutowiredAnnotationBeanPostProcessor 将扫描 Spring 容器中所有
* Bean,当发现 Bean 中拥有 @Autowired 注释时就找到和其匹配(默认按类型匹配)的 Bean,
* 并注入到对应的地方中去。
*/
@Repository("TreeDao")
public class TreeDaoImpl implements TreeDao {
@Autowired
private SqlSessionFactory sqlSessionFactory;
public SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public Tree getNodeById(int id) {
SqlSession sqlSession =(SqlSession) this.getSqlSessionFactory().openSession();
return sqlSession.selectOne("com.chenqk.springmvc.entity.Tree.getNodeById", id);
}
@Override
public List<Tree> getNodesByParentId(int pid) {
SqlSession sqlSession =(SqlSession) this.getSqlSessionFactory().openSession();
return sqlSession.selectList("com.chenqk.springmvc.entity.Tree.getNodesByParentId", pid);
}
}
3.4 service及其实现类
package com.chenqk.springmvc.service;
import java.util.List;
import com.chenqk.springmvc.entity.Tree;
/**
* 业务逻辑层
* @author chenqk
* 2017年11月27日16:03:32
*/
public interface TreeService {
public Tree getNodeById(int id);
public List<Tree> getNodesByParentId(int pid);
}
package com.chenqk.springmvc.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;
import com.chenqk.springmvc.dao.TreeDao;
import com.chenqk.springmvc.entity.Tree;
import com.chenqk.springmvc.service.TreeService;
/**
* 逻辑业务层
* @author chenqk
*
* @Qualifier 匹配者,按类型自动装配可能多个bean实例的情况,可以使用Spring的@Qualifier
* 注解缩小范围(或指定唯一),也可以指定单独的构造器参数或方法参数。
*
* @Resource 默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入
* @Resource 有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name
* 属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,
* 则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name
* 也不指定type属性,这时将通过反射机制使用byName自动注入策略。
@Resource 装配顺序
1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为
一个原始类型进行匹配,如果匹配则自动装配;
*
* @Autowired 默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在,如果允许null值,
* 可以设置它required属性为false。如果我们想使用按名称装配,
* 可以结合@Qualifier注解一起使用。如下:
*
*/
@Repository("TreeService")
public class TreeServiceImpl implements TreeService{
@Autowired
@Qualifier("TreeDao")
private TreeDao treeDao;
@Override
public Tree getNodeById(int id) {
return treeDao.getNodeById(id);
}
public TreeDao getTreeDao() {
return treeDao;
}
public void setTreeDao(TreeDao treeDao) {
this.treeDao = treeDao;
}
@Override
public List<Tree> getNodesByParentId(int pid) {
return treeDao.getNodesByParentId(pid);
}
}
3.5 controller控制层
package com.chenqk.springmvc.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.chenqk.springmvc.entity.Tree;
import com.chenqk.springmvc.service.TreeService;
import com.chenqk.springmvc.util.CommonUtil;
/**
* 控制层
* @author chenqk
*
* @RequestMapping 是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,
* 表示类中的所有响应请求的方法都是以该地址作为父路径。
* 该注解分为6个属性
* value: 指定请求的实际地址,指定的地址可以是URI Template 模式,其中可以为普通的具体值,
* 含有某变量的一类值,包含正则表达式的一类值;
* method: 指定请求的method类型, GET、POST、PUT、DELETE等;
* consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
* produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;
* params: 指定request中必须包含某些参数值是,才让该方法处理。
* headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求。
*/
@Controller
@RequestMapping("/")
public class TreeController {
@Autowired
@Qualifier("TreeService")
private TreeService treeService;
public TreeService getTreeService() {
return treeService;
}
public void setTreeService(TreeService treeService) {
this.treeService = treeService;
}
/**
* 初始化tree数据
* @param pid
*/
@RequestMapping(value="/getNodesByParentId",method=RequestMethod.POST)
public void getNodesByParentId(@RequestParam("id") int id,HttpServletResponse response){
String node_str = "";
StringBuffer json = new StringBuffer();
//获得根节点信息,以便组装到json中
Tree treeRoot = treeService.getNodeById(id);
json.append("[");
json.append("{\"id\":" +String.valueOf(treeRoot.getId()));
json.append(",\"text\":\"" +treeRoot.getText() + "\"");
json.append(",\"state\":\"open\"");
json.append(",\"attributes\":\""+treeRoot.getAttributes() + "\"");
json.append(",\"children\":[");
//获取根节点下的所有子节点
List<Tree> treeList = new ArrayList<Tree>();
treeList = treeService.getNodesByParentId(id);
if(treeList != null && treeList.size() != 0){
for (Tree t : treeList) {
json.append("{\"id\":" +String.valueOf(t.getId()));
json.append(",\"text\":\"" +t.getText() + "\"");
json.append(",\"state\":\"open\"");
json.append(",\"attributes\":\""+t.getAttributes() + "\"");
json.append(",\"children\":[");
List<Tree> tList = treeService.getNodesByParentId(t.getId());
if(tList != null && tList.size() != 0){
json.append(CommonUtil.dealJsonFormat(tList));// 存在子节点的都放在一个工具类里面处理了
json.append("]},");
}else{
json.append("]},");
}
}
}else{
json.append("]");
}
node_str = json.toString();
node_str = node_str.substring(0, node_str.length()-1);
node_str+="]}]";
try {
response.getWriter().print(node_str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.6 工具类
package com.chenqk.springmvc.util;
import java.util.List;
import com.chenqk.springmvc.entity.Tree;
public class CommonUtil {
/**
* 处理数据集合,将数据集合转为符合格式的json
* @param tList 参数
* @return json字符串
*/
public static String dealJsonFormat(List<Tree> tList){
StringBuilder json = new StringBuilder();
for (Tree tree : tList) {
json.append("{\"id\":" +String.valueOf(tree.getId()));
json.append(",\"text\":\"" +tree.getText() + "\"");
json.append(",\"state\":\"closed\"");
json.append(",\"attributes\":\""+tree.getAttributes() + "\"");
json.append("},");
}
String str = json.toString();
str = str.substring(0, str.length()-1);
return str;
}
}
3.7 response对象
这是个正经的json格式,也是符合tree API所需要的格式,所以在页面我们也能看到一个正经的树。
在这里补充两点:
- 我这种方式并不唯一,而且有很多改进的地方,关于树的处理逻辑应该在service实现类里面处理。controller中
应该只包含逻辑跳转和response数据。
- 同时还有一种非常简单的方法,去处理这个返回的json数据。你可以再定义一个与页面一一对应的VO来实现。将数据从数据库中取出来,赋值给VO,然后VO的定义中不但包含数的节点信息,而且还要包含一个children集合。然后根据一级节点包含二级节点,二级节点包含三级节点,就可以生成一个VO对象这个VO对象就是根节点并且包含所有的子节点及其孙子节点等。然后使用JDK提供的JSONObject工具类,将VO对象转为json对象,就是完全符合。easyUI-tree API的json格式了,不需要像我代码中的自己拼接了。
代码已经上传,如果有问题,请联系。
源码下载地址:http://download.youkuaiyun.com/download/chenqk_123/10137539
参考文档:http://www.jeasyui.net/plugins/185.html