最近做一个项目,单纯用于分类的层级关系,并不是zTree的角色和权限,只是操作树的节点,对树的节点进行增加,修改,删除。
最近做一个项目,单纯用于分类的层级关系,并不是zTree的角色和权限,只是操作树的节点,对树的节点进行增加,修改,删除。
csdn资源下载 http://download.youkuaiyun.com/detail/maxwell_chia/8343375
用的jQuery的zTree是从官网上下载的最新版本。
中文官网地址:http://www.ztree.me/hunter/zTree.html
我上传的包里有最新版本API及插件包。上面是网址,可以自行下载最新版本。当前时间是2015年1月。
API提供了 demo 很详细,我在这里只提供一种思路,仅供参考。
我的项目需求:对物品分类进行树形展示,并且可以无限层级关系,然后通过操作节点来对类别信息进行增删改查。
技术难点在于多层级的删除。这也是本文的亮点。
下图为API样式。
API的使用:
API提供了中英文两种 cn代表中文
首先我这里先给出一些树的基础知识,还有其他文档,为不同水平的朋友提供参考,希望大家去下载学习。
zTree 是利用 JQuery 的核心代码,实现一套能完成大部分常用功能的 Tree 插件
· 兼容 IE、FireFox、Chrome 等浏览器
· 在一个页面内可同时生成多个 Tree 实例
· 支持 JSON 数据
· 支持一次性静态生成 和 Ajax 异步加载 两种方式
· 支持多种事件响应及反馈
· 支持 Tree 的节点移动、编辑、删除
· 支持任意更换皮肤 / 个性化图标(依靠css)
· 支持极其灵活的 checkbox 或 radio 选择功能
· 简单的参数配置实现 灵活多变的功能
部分函数和属性介绍(其他核心参数参见api有详细展示)
核心:zTree(setting, [zTreeNodes])
这个函数接受一个JSON格式的数据对象setting和一个JSON格式的数据对象zTreeNodes,从而建立 Tree。
核心参数:setting
zTree 的参数配置都在这里完成,简单的说:树的样式、事件、访问路径等都在这里配置
setting 举例:
var setting = { showLine: true, checkable: true };
核心参数:zTreeNodes
zTree 的全部节点数据集合,采用由JSON对象组成的数据结构,简单的说:这里使用Json格式保存了树的所有信息
zTreeNodes的格式分为两种:利用Json格式嵌套体现父子关系和Array简单格式
①带有父子关系的标准 zTreeNodes 举例:
var zTreeNodes = [
{"id":1, "name":"test1", "nodes":[ {"id":11, "name":"test11", "nodes":[ {"id":111, "name":"test111"} ]}, {"id":12, "name":"test12"} ]}, ...... ];
②带有父子关系的简单 Array 格式(isSimpleData)的 zTreeNodes 举例:
var treeNodes = [
{"id":1, "pId":0, "name":"test1"}, {"id":11, "pId":1, "name":"test11"}, {"id":12, "pId":1, "name":"test12"}, {"id":111, "pId":11, "name":"test111"}, ...... ];
开发步骤
前台页面部分
效果如上图 API里的demo,大家可以选取不同的demo样式及功能。
前台页面是直接拿的API里的demo的代码,只修改自己用到的功能,没用的功能无视他就行。
第一个功能树的显示:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<HEAD>
<TITLE> ZTREE DEMO - select menu</TITLE>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<!-- ztree js -->
<link rel="stylesheet" href="<%=request.getContextPath()%>/res/news/gonggao/jdd/ztree/css/demo.css" type="text/css">
<link rel="stylesheet" href="<%=request.getContextPath()%>/res/news/gonggao/jdd/ztree/css/zTreeStyle/zTreeStyle.css" type="text/css">
<script type="text/javascript" src="<%=request.getContextPath()%>/res/news/gonggao/jdd/ztree/js/jquery-1.4.4.min.js"></script>
<script type="text/javascript" src="<%=request.getContextPath()%>/res/news/gonggao/jdd/ztree/js/jquery.ztree.core-3.5.js"></script>
<script type="text/javascript" src="<%=request.getContextPath()%>/res/news/gonggao/jdd/ztree/js/jquery.ztree.excheck-3.5.js"></script>
<script type="text/javascript" src="<%=request.getContextPath()%>/res/news/gonggao/jdd/ztree/js/jquery.ztree.exedit-3.5.js"></script>
<SCRIPT type="text/javascript">
var setting = {
data: {
simpleData: {
enable: true,
idKey: "id",
pIdKey: "pId",
rootPId: 0
}
},
view: {
dblClickExpand: false
},
check: {
enable: true
},
callback: {
onRightClick: OnRightClick
}
};
var zNodes =${json};
function OnRightClick(event, treeId, treeNode) {
if (!treeNode && event.target.tagName.toLowerCase() != "button" && $(event.target).parents("a").length == 0) {
zTree.cancelSelectedNode();
showRMenu("root", event.clientX, event.clientY);
} else if (treeNode && !treeNode.noR) {
zTree.selectNode(treeNode);
showRMenu("node", event.clientX, event.clientY);
}
}
function showRMenu(type, x, y) {
$("#rMenu ul").show();
if (type=="root") {
$("#m_del").hide();
$("#m_check").hide();
$("#m_unCheck").hide();
} else {
$("#m_del").show();
$("#m_check").show();
$("#m_unCheck").show();
}
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
$("body").bind("mousedown", onBodyMouseDown);
}
function hideRMenu() {
if (rMenu) rMenu.css({"visibility": "hidden"});
$("body").unbind("mousedown", onBodyMouseDown);
}
function onBodyMouseDown(event){
if (!(event.target.id == "rMenu" || $(event.target).parents("#rMenu").length>0)) {
rMenu.css({"visibility" : "hidden"});
}
}
var addCount = 1;
function addTreeNode() {
hideRMenu();
var newNode = { name:"newNode " + (addCount++)};
if (zTree.getSelectedNodes()[0]) {
newNode.checked = zTree.getSelectedNodes()[0].checked;
zTree.addNodes(zTree.getSelectedNodes()[0], newNode);
} else {
zTree.addNodes(null, newNode);
}
}
function updateTreeNode() {
hideRMenu();
var nodes = zTree.getSelectedNodes();
var msg = "If you update this node ? \n\nPlease confirm!";
if (confirm(msg)==true){
var ckid=nodes[0].id;
alert(ckid)
window.location.href = "<c:url value='/ac/updateFac.do?acid='/>"+ckid;
//zTree.removeNode(nodes[0]);
}
if (zTree.getSelectedNodes()[0]) {
newNode.checked = zTree.getSelectedNodes()[0].checked;
zTree.addNodes(zTree.getSelectedNodes()[0], newNode);
} else {
zTree.addNodes(null, newNode);
}
}
function removeTreeNode() {
hideRMenu();
var nodes = zTree.getSelectedNodes();
if (nodes && nodes.length>0) {
if (nodes[0].children && nodes[0].children.length > 0) {
var msg = "你是要干掉当前节点及其子节点吗 ? \n\nPlease confirm!";
if (confirm(msg)==true){
var ckid=nodes[0].id;
alert(ckid)
window.location.href = "<c:url value='/ac/delFac.do?acid='/>"+ckid;
//zTree.removeNode(nodes[0]);
}
} else {
var ckid=nodes[0].id;
alert(ckid)
window.location.href = "<c:url value='/ac/delFac2.do?acid='/>"+ckid;
}
}
}
function checkTreeNode(checked) {
var nodes = zTree.getSelectedNodes();
if (nodes && nodes.length>0) {
zTree.checkNode(nodes[0], checked, true);
}
hideRMenu();
}
function resetTree() {
hideRMenu();
$.fn.zTree.init($("#treeDemo"), setting, zNodes);
}
var zTree, rMenu;
$(document).ready(function(){
$.fn.zTree.init($("#treeDemo"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("treeDemo");
rMenu = $("#rMenu");
});
</SCRIPT>
<!-- 增删改查触发方法 -->
<!-- 增加触发方法 添加我是用button 触发的并没有使用右击的menu -->
<script type="text/javascript">
function toICAdd(){
window.location.href = "<c:url value='/ac/toAddAC.do'/>";
}
</script>
<style type="text/css">
div#rMenu {position:absolute; visibility:hidden; top:0; background-color: #555;text-align: left;padding: 2px;}
div#rMenu ul li{
margin: 1px 0;
padding: 0 5px;
cursor: pointer;
list-style: none outside none;
background-color: #DFDFDF;
}
</style>
</HEAD>
<BODY>
<div class="box-positon">
<div class="rpos">当前位置: 文章管理 - 分类</div>
<div class="clear"></div>
</div>
<h1>公告分类</h1>
<div class="content_wrap">
<div class="zTreeDemoBackground left">
<ul id="treeDemo" class="ztree"></ul>
</div>
</div>
<%--tool bar --%>
<div class="tools">
<ul class="toolbar" >
<li onclick="toICAdd()" style="float: left">
<span><img src="<c:url value='/res/admin/images/t01.png'/>" />
</span>添加
</li>
<li onclick="toSeeIC()" style="float: left">
<span><img src="<c:url value='/res/admin/images/t04.png'/>" />
</span>统计
</li>
</ul>
</div>
<%--右击 tool bar --%>
<div id="rMenu">
<ul>
<!-- <li id="m_add" onclick="addTreeNode();">Add Node</li> -->
<li id="m_add" onclick="updateTreeNode();">Update Node</li>
<li id="m_del" onclick="removeTreeNode();">Delete Node</li>
<li id="m_check" onclick="checkTreeNode(true);">Check Node</li>
<li id="m_unCheck" onclick="checkTreeNode(false);">Uncheck Node</li>
<li id="m_reset" onclick="resetTree();">Resume zTree</li>
</ul>
</div>
</BODY>
以上为查询出的前台页面在看后台:
ACTION:查出List 以json格式传给页面,这是树的显示:
@Controller
public class ArticleClassAction {
@Autowired
private ArticleClassService service;
@RequestMapping(value="/ac/getTree")
public String getArticleClass(Model model) throws GenericBusinessException{
List<ArticleClassBean> list = service.findAcList();
JSONArray array =JSONArray.fromObject(list);
String json = array.toString();
json = json.replaceAll("acid", "id"); //将数据库里的字段,转换成 树需要的字段 ,进行了一下字段名的转换,树的显示需要id,pid(上级id),name.
json = json.replaceAll("acfid", "pId");
json = json.replaceAll("acname", "name");
System.out.println("--------------------- json---------------"+json);
model.addAttribute("json", json);
System.out.println("--------------------model---------------"+model);
return "/news/wenzhang/articleclass2";
}
添加:
点击页面button,然后到添加页面,只需选择父级节点就可以实现添加,是最简单的,代码如下:
添加之前 需要把已有的分类都查出来,作为父类,供将要添加的子类选择
上面页面到的action代码为:
@RequestMapping(value="/ac/toAddAC")
public String toAddAc(Model model){
List<ArticleClassBean> list = service.findAcList();
model.addAttribute("list",list);
return "/news/wenzhang/addac";
}
然后讲list 传到到页面:
<script type="text/javascript">
function doAdd(){
var acname= document.getElementsByName("acname")[0].value;
if(acname==null||acname==''){
alert("不能为空!")
return false;
}else{
document.forms[0].action="<c:url value='/ac/addAC.do'/>";
document.forms[0].submit();
}
}
</script>
</head>
<body>
<div class="box-positon">
<div class="rpos">当前位置: 文章管理 - 添加分类</div>
<div class="clear"></div>
</div>
<form action="" method="post">
<div class="formbody">
<div class="formtitle">
<span>文章分类</span>
</div>
<ul class="forminfo">
<li>
<label>分类名称</label>
<input name="acname" type="text" class="dfinput" />
</li>
<li>
<label>上级名称</label>
<div class="vocation">
<select name="acid" class="select1">
<c:forEach items="${list}" var="list">
<option value="${list.acid}">${list.acname}</option>
</c:forEach>
</select>
</div>
</li>
<li>
<label> </label>
<input onclick="doAdd()" type="button" class="btn" value="确认添加"/>
</li>
</ul>
</div>
</form>
</body>
</html>
然后再到ACTION执行 添加 ,这里就需要引出文章的亮点了
需要说明的是这里的acurl.
/**
* DO ADD
* 前台acid 是 分类父id
* @param acname
* @param acid
* @return
* @throws GenericBusinessException
*/
@RequestMapping(value="/ac/addAC")
public String doadd(String acname, Integer acid) throws GenericBusinessException{
ArticleClassBean Fbean=service.getFbeanByAcid(acid);
String Facurl=Fbean.getAcurl();
String acurl=Facurl+"-"+acid;
ArticleClassBean ac=new ArticleClassBean();
ac.setAcfid(acid);
ac.setAcname(acname);
ac.setAcurl(acurl);
service.save(ac);
return "redirect:/ac/getTree.do";
}
这个acurl是我为删除提供的一个数据库字段。可以把它当做一个标示码,这个码的规律是当前节点的所有上级节点id,组成。
目的是为了实现都层级关系时删除中间节点,级联删除他下面的所有节点,一般情况删除最后节点是最容易的,
删除倒数第二个节点也比较简单,如果层级关系超过三层,需要进行复杂判断了。
如图有七层,如当删除增加2时需要删出3,4.5.
节点1
节点2
节点3
节点4
如果每个节点都配一个code,即我代码里的acurl
节点1的当前节点code为 0-1 (0为根节点id,1为当前id, - 为间隔,用字符串拼接可以实现)
节点2的当前节点code为 0-1 -2 (0为根节点id,1为节点1 的id,2为当前节点id)
类推。。。
事实上我插入数据库的并不是当前节点code,而是上级节点code,
目的是新增节点时是无法获取新节点的id,导致当前节点code的最后一位无法实现拼接,
所以存入的是上级的父节点code,这个是可以通过添加时的父节点id ,从数据库查出。
也就是上面代码的拼接,
然后删除就变得简单了,用的是模糊删除。
如删除节点2 code 码为上级code, 实际节点code即0-1
需要拼接 上 当前节点的id -2 然后+% 模糊删除 节点2的子节点
还需一步删除节点2 ,代码如下 一目了然
如果删除的节点没有子节点 ,在页面时有一个判断,会调用另一个方法直接通过id删除 代码如下:
/**
* 删除 如果有子节点 当前节点用id删除,而子节点用 code原理删除
* @param model
* @param request
* @param ncid
* @return
*/
@RequestMapping(value="/ac/delFac")
public String delCK(Model model,HttpServletRequest request,Integer acid){
ArticleClassBean Fbean=service.getFbeanByAcid(acid);
String code=Fbean.getAcurl();
String acurl=code+"-"+acid;
service.delFac(acid);
service.delZincByAcurl(acurl);
return "redirect:/ac/getTree.do";
}
/**
* 删除子节点 树分支的最后节点
* @param model
* @param request
* @param ncid
* @return
*/
@RequestMapping(value="/ac/delFac2")
public String delCK2(Model model,HttpServletRequest request,Integer acid){
service.delFac(acid);
return "redirect:/ac/getTree.do";
}
@RequestMapping(value="/ac/updateFac")
public String update(Model model,Integer acid){
ArticleClassBean Fbean=service.getFbeanByAcid(acid);
List<ArticleClassBean> list = service.findAcList();
model.addAttribute("list",list);
model.addAttribute("Fbean",Fbean);
return "/news/wenzhang/updateac";
}
/**
* 前台取到Bean的 父id即ncfid,
* 然后通过ncfid作为id查出父类的Bean ,得到父类的code
* 父类的code + 父类的id = 本bean的Code 即 ncurl字段
* @param ncBean
* @return
*/
@RequestMapping(value="/ac/doUpdate")
public String doupdate(ArticleClassBean acBean){
Integer acfid=acBean.getAcfid();
System.out.println("++++++++++++++父id+++++++++++++++==="+acfid);
ArticleClassBean Fbean=service.getFbeanByAcid(acfid);
String facurl=Fbean.getAcurl();
System.out.println("++++++++++++++++父类的ncurl code+++++++++++++==="+facurl);
String acurl=facurl+"-"+acfid;
System.out.println("+++++++++++本类的+++++++++code+++++++++==="+acurl);
acBean.setAcurl(acurl);
System.out.println(acBean);
service.save(acBean);
return "redirect:/ac/getTree.do";
}
数据库操作:
hql语句
@Service
public class ArticleClassServiceImpl extends BaseServiceImpl<ArticleClassBean>implements ArticleClassService {
@Autowired
private BaseDao<ArticleClassBean> dao;
public List<ArticleClassBean> findAcList(){
String hql="from ArticleClassBean";
try {
return dao.find(hql);
} catch (GenericBusinessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
@Override
public PageModel<ArticleClassBean> findByPage(ArticleClassBean entity,
Integer pageNo, Integer pageSize) throws GenericBusinessException {
// TODO Auto-generated method stub
return null;
}
public void save(ArticleClassBean ac){
try {
dao.saveOrUpdate(ac);
} catch (GenericBusinessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public ArticleClassBean getFbeanByAcid(Integer acid){
String hql="From ArticleClassBean where acid="+acid;
try {
return (ArticleClassBean) dao.find(hql).get(0);
} catch (GenericBusinessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public void delFac(Integer acid){
String hql="delete from ArticleClassBean where acid="+acid+" or acfid="+acid+"";
System.out.println(hql);
try {
dao.executeHql(hql);
} catch (GenericBusinessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void delZincByAcurl(String acurl){
String hql="delete from ArticleClassBean where acurl like '"+acurl+"%'";
try {
dao.executeHql(hql);
} catch (GenericBusinessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}