EasyUI 樹菜單
EasyUI 樹菜單
通過ssm框架項目實現EasyUI 的樹菜單的單選,復選,異步加載樹,同步加載樹和樹權限控制等功能。
本章知識點
效果圖:
需求:通過SSM框架,實現EasyUI 樹菜單的單選,多選,異步加載,同步加載的功能
技術:Spring,SpringMVC,Mybatis,EasyUI
明說:使用EasyUI-Tree,必須嚴格遵守它的規則,如異步加載樹節點的 id,異步加載樹返回值的格式等。如果按照其規則來做,你會發現 EasyUI 很簡單。反之到處都是吭!
源碼:見文章底部
場景:樹菜單,在電商中很場景。筆者是在電商公司上班,類目樹菜單隨處可見。比如給廣告員設置類目級別,刊登商品選擇類目加載對應的產品規格參數等等
初始化靜態樹
大部分的功能,並非一步完成。都是從最基礎的功能開始。這裏是EasyUI-Tree 基礎結構
<ul class="easyui-tree"> <li> <span>根目錄</span> <ul> <li data-options="state:'closed'"> <span>關閉狀態的子目錄</span> <ul> <li>ITDragon</li> <li>博客</li> </ul> </li> <li> <span>默認展開的子目錄</span> <ul> <li>歡迎</li> <li>You!</li> </ul> </li> <li>你是最棒的!</li> </ul> </li> </ul>
Maven Web項目實戰
項目框架結構是:Spring,SpringMVC,Mybatis。 沒有其他的額外配置,都是基礎的整合配置。這裏就不貼代碼。讀者也可以直接從github上clone下來(sql文件也在項目中)。
POJO層
本章的主角,類目實體類 Category.java
package com.itdragon.pojo; import java.util.Date; public class Category { private Integer id; private String name; private Integer isLeaf; private Integer parentId; private Date createddate; private Date updateddate; private Integer status; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name == null ? null : name.trim(); } public Integer getIsLeaf() { return isLeaf; } public void setIsLeaf(Integer isLeaf) { this.isLeaf = isLeaf; } public Integer getParentId() { return parentId; } public void setParentId(Integer parentId) { this.parentId = parentId; } public Date getCreateddate() { return createddate; } public void setCreateddate(Date createddate) { this.createddate = createddate; } public Date getUpdateddate() { return updateddate; } public void setUpdateddate(Date updateddate) { this.updateddate = updateddate; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } }
按照EasyUI規範封裝的Tree節點實體類 EUTreeNode.java
package com.itdragon.common.pojo;
/**
* 樹的數據格式(Tree Data Format)
* 每個節點可以包括下列屬性:
* id:節點的 id,它對於加載遠程數據很重要。
* text:要顯示的節點文本。
* state:節點狀態,'open' 或 'closed',默認是 'open'。當設置為 'closed' 時,該節點有子節點,並且將從遠程站點加載它們。
* checked:指示節點是否被選中。
* attributes:給一個節點添加的自定義屬性。
* children:定義了一些子節點的節點數組
*
* 這裏先封裝常用的 id,text,state
*/
public class EUTreeNode {
private long id;
private long parentId;
private String text;
private String state;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getParentId() {
return parentId;
}
public void setParentId(long parentId) {
this.parentId = parentId;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
說明:
① Category.java 屬性 createdDate 和 updatedDate 的類型都是java.util.Date。實際上也可以是 String 類型,這樣可以在顯示(日期格式化),排序,篩選時減少很多工作量。
② 這裏的 Category.java,CategoryExample.java,CategoryMapper.java,CategoryMapper.xml 是通過 Mybatis 提供的逆向工程自動生成的。文章底部會提供鏈接。
Service 層
提供查詢類目的接口 CategoryService.java 感覺怪怪的 -.-||
package com.itdragon.service;
import java.util.List;
import com.itdragon.common.pojo.EUTreeNode;
public interface CategoryService {
/**
* 通過父節點,異步加載樹菜單
* @param parentId
*/
List<EUTreeNode> getCategoryList(int parentId);
/**
* 一次全部加載所有樹節點
*/
List<EUTreeNode> getCategoryList();
}
類目接口的實現類 CategoryServiceImpl.java
package com.itdragon.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.itdragon.common.pojo.EUTreeNode;
import com.itdragon.mapper.CategoryMapper;
import com.itdragon.pojo.Category;
import com.itdragon.pojo.CategoryExample;
import com.itdragon.pojo.CategoryExample.Criteria;
import com.itdragon.service.CategoryService;
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Override
public List<EUTreeNode> getCategoryList(int parentId) {
// 1. 創建查詢條件
CategoryExample example = new CategoryExample();
Criteria criteria = example.createCriteria();
criteria.andParentIdEqualTo(parentId); // 查詢父節點下的所有子節點
criteria.andStatusEqualTo(0); // 查詢未刪除狀態的菜單
// TODO 權限攔截
// 2. 根據條件查詢
List<Category> list = categoryMapper.selectByExample(example);
List<EUTreeNode> resultList = new ArrayList<>();
// 3. 把列表轉換成 EasyUI Tree 需要的json格式
for (Category category : list) {
EUTreeNode node = new EUTreeNode();
node.setId(category.getId());
node.setText(category.getName());
node.setState(category.getIsLeaf() == 1?"open":"closed");
resultList.add(node);
}
// 4. 返回結果
return resultList;
}
@Override
public List<EUTreeNode> getCategoryList() {
// 1. 創建查詢條件
CategoryExample example = new CategoryExample();
Criteria criteria = example.createCriteria();
criteria.andStatusEqualTo(0); // 查詢未刪除狀態的菜單
// TODO 權限攔截
// 2. 根據條件查詢
List<Category> list = categoryMapper.selectByExample(example);
List<EUTreeNode> resultList = new ArrayList<>();
// 3. 把列表轉換成 EasyUI Tree 需要的json格式
for (Category category : list) {
EUTreeNode node = new EUTreeNode();
node.setId(category.getId());
node.setText(category.getName());
node.setState(category.getIsLeaf() == 1?"open":"closed");
node.setParentId(category.getParentId());
resultList.add(node);
}
// 4. 返回結果
return resultList;
}
}
說明:樹菜單的權限攔截,並沒有提供代碼,是考慮到涉及其他實體類。其實有了思路,其他的都好說..................好吧!我承認自己懶=。=
Controller 層
用於頁面跳轉的 PageController.java
package com.itdragon.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class PageController {
@RequestMapping("/")
public String showIndex() {
return "tree";
}
@RequestMapping("/{page}")
public String showpage(@PathVariable String page) {
return page;
}
}
負責加載類目樹菜單的 CategoryController.java
package com.itdragon.controller;
import java.util.List;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.itdragon.common.pojo.EUTreeNode;
import com.itdragon.service.CategoryService;
@Controller
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* http://www.jeasyui.net/plugins/185.html
* 當展開一個關閉的節點時,如果該節點沒有子節點加載,它將通過上面定義的 URL 向服務器發送節點的 id 值作為名為 'id' 的 http 請求參數,以便檢索子節點。
* 所以這裏的參數value必須是id,若是其他值則接收不到。缺省值是0,表示初始化一級菜單。
*
* @param parentId
* @return
*/
@RequestMapping("/async")
@ResponseBody
private List<EUTreeNode> getAsyncCatList(@RequestParam(value="id",defaultValue="0") int parentId) {
List<EUTreeNode> results = categoryService.getCategoryList(parentId);
return results;
}
@RequestMapping("/sync")
@ResponseBody
private List<EUTreeNode> getSyncCatList() {
List<EUTreeNode> results = categoryService.getCategoryList();
return results;
}
}
說明:這裏的@RequestParam(value="id",defaultValue="0"),value值必須是id,不能是其他值。
Views視圖層
演示EasyUI-Tree 類目樹菜單的 tree.jsp
<%@ 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">
<title>EasyUI-Tree</title>
<link rel="stylesheet" type="text/css" href="js/jquery-easyui-1.4.1/themes/default/easyui.css" />
<link rel="stylesheet" type="text/css" href="js/jquery-easyui-1.4.1/themes/icon.css" />
<script type="text/javascript" src="js/jquery-easyui-1.4.1/jquery.min.js"></script>
<script type="text/javascript" src="js/jquery-easyui-1.4.1/jquery.easyui.min.js"></script>
<script type="text/javascript" src="js/jquery-easyui-1.4.1/locale/easyui-lang-zh_CN.js"></script>
</head>
<body class="easyui-layout">
<div data-options="region:'west',title:'EasyUI 樹菜單',split:true" style="width:205px;">
<ul id="menu" class="easyui-tree" style="margin-top: 10px;margin-left: 5px;">
<li>
<span>EasyUI</span>
<ul>
<li>靜態樹</li>
<li>結構為ul li 標簽</li>
<li>ul定義class為easyui-tree</li>
</ul>
</li>
<li>
<span>本章知識點</span>
<ul>
<li>創建靜態樹菜單</li>
<li>創建異步樹菜單</li>
<li>創建異步樹多選菜單</li>
<li>樹菜單權限管理</li>
</ul>
</li>
</ul>
</div>
<div id="content" region="center" title="ITDragon博客" style="padding:5px;">
<span>
<h3>創建靜態樹菜單</h3>
<ul id="" class="easyui-tree">
<li>
<span>父節點</span>
<ul>
<li>子節點一</li>
<li>子節點二</li>
</ul>
</li>
</ul>
<h4>使用方法</h4>
<p>ul 標簽 定義 class="easyui-tree"</p>
<a href="http://www.jeasyui.net/plugins/185.html">EasyUI 樹菜單教程 </a> <br/>
<a href="http://www.jeasyui.net/plugins/180.html">EasyUI 窗口教程 </a>
</span>
<hr/>
<span>
<h3>創建異步樹菜單</h3>
<a href="javascript:void(0)" class="easyui-linkbutton selectCategory">創建異步樹菜單</a>
<input type="hidden" name="categoryId" style="width: 280px;"></input>
<br/>
<h4>創建思路</h4>
<p>一:初始加載一級類目菜單,通過點擊一級類目菜單再查詢其子節點菜單</p>
<p>二:類目表設計實例,一級類目的parentId為0,子節點類目的parentId是父節點類目的id</p>
<p>三:返回數據結構類型只要滿足EasyUI的規範即可</p>
</span>
<hr/>
<span>
<h3>創建異步樹多選菜單</h3>
<a href="javascript:void(0)" class="easyui-linkbutton selectMoreCategory">創建異步樹多選菜單</a>
<input type="hidden" name="categoryIds" style="width: 280px;"></input>
<br/>
<h4>註意</h4>
<p>若采用異步樹加載菜單,會出現勾選父節點。保存後只打印了父節點信息,未打印子節點(因為子節點都沒有加載)</p>
<h4>解決思路</h4>
<p>讓業務每個都點開(不合實際);本章節采用同步加載的方式;你們有沒有更好的辦法?</p>
<a href="http://www.jeasyui.net/tutorial/57.html"> EasyUI 采用同步加載教程 </a>
</span>
<hr/>
<span>
<h3>樹菜單權限管理:</h3>
<p>業務邏輯:需要一張用戶組管理表,設置當前登錄用戶所屬組。</p>
<p>後臺邏輯:樹菜單表新增字段permission用來匹配用戶所屬組,說簡單點就是多了一層查詢條件。</p>
</span>
</div>
<script type="text/javascript">
$(function(){
initAsyncCategory ();
initMoreSyncCategory ();
});
// 異步加載樹菜單
function initAsyncCategory (){
$(".selectCategory").each(function(i,e){
var _ele = $(e);
_ele.after("<span style='margin-left:10px;'></span>"); // 避免被按鈕遮住
_ele.unbind('click').click(function(){
$("<div>").html("<ul>").window({ // 使用 javascript 創建窗口(window)
width:'500', height:"450", modal:true, closed:true, iconCls:'icon-save', title:'異步樹菜單',
onOpen : function(){ // 窗口打開後執行
var _win = this;
$("ul",_win).tree({
url:'/category/async', // 采用異步加載樹節點,返回數據的格式要滿足EasyUI Tree 的要求
animate:true,
onClick:function(node){ // 樹菜單點擊後執行
if($(this).tree("isLeaf",node.target)){ // 如果該節點是葉節點就填寫到categoryId中,並關閉窗口
_ele.parent().find("[name=categoryId]").val(node.id);
_ele.next().text(node.text).attr("categoryId",node.id);
$(_win).window('close');
}
}
});
},
onClose : function(){ // 窗口關閉後執行
$(this).window("destroy");
}
}).window('open'); // 使用 javascript 打開窗口(window)
});
});
}
// 同步加載復選樹菜單
function initMoreSyncCategory (){
$(".selectMoreCategory").each(function(i,e){
var _ele = $(e);
_ele.after("<span style='margin-left:10px;'></span>");
_ele.unbind('click').click(function(){
$("<div>").html("<ul id='moreItemCat'>").window({ // 使用 javascript 創建窗口(window)
width:'500', height:"450", modal:true, closed:true, iconCls:'icon-save', title:'多選樹菜單,關閉窗口後保存數據',
onOpen : function(){ // 窗口打開後執行
var _win = this;
$("ul",_win).tree({
url:'/category/sync', // 采用同步的方式加載所有樹節點
animate:true,
checkbox:true, // js 聲明樹菜單可以復選
loadFilter: function(rows){
return convert(rows);
}
});
},
onClose : function(){ // 窗口關閉後執行
var nodes = $("#moreItemCat").tree('getChecked');
var categoryIds = '';
var categoryTexts = '';
for(var i = 0; i < nodes.length; i++){
if ('' != categoryIds) {
categoryIds += ',';
categoryTexts += ' , ';
}
categoryIds += nodes[i].id;
categoryTexts += nodes[i].text;
}
_ele.parent().find("[name=categoryIds]").val(categoryIds);
_ele.next().text(categoryTexts).attr("categoryId",categoryTexts);
$(this).window("destroy");
}
}).window('open'); // 使用 javascript 打開窗口(window)
});
});
}
// 官方提供的 js 解析 json 代碼
function convert(rows){
function exists(rows, parentId){
for(var i=0; i<rows.length; i++){
if (rows[i].id == parentId) return true;
}
return false;
}
var nodes = [];
for(var i=0; i<rows.length; i++){ // get the top level nodes
var row = rows[i];
if (!exists(rows, row.parentId)){
nodes.push({
id:row.id,
text:row.text,
state:row.state
});
}
}
var toDo = [];
for(var i=0; i<nodes.length; i++){
toDo.push(nodes[i]);
}
while(toDo.length){
var node = toDo.shift(); // the parent node
for(var i=0; i<rows.length; i++){ // get the children nodes
var row = rows[i];
if (row.parentId == node.id){
var child = {id:row.id,text:row.text,state:row.state};
if (node.children){
node.children.push(child);
} else {
node.children = [child];
}
toDo.push(child);
}
}
}
return nodes;
}
</script>
</body>
</html>
說明:
① tree.jsp 除了EasyUI-Tree 的知識點外,還涉及了一點點窗口的知識
使用 javascript 創建窗口(window)
<div id="win"></div> $('#win').window({ width:600, height:400, modal:true });
打開和關閉窗口(window)
$('#win').window('open'); // open a window $('#win').window('close'); // close a window
② tree.jsp 主要包含了單選異步加載樹菜單和多選同步加載樹菜單兩大知識點,所以內容較長,請耐心閱讀。
③ 若異步加載樹菜單,支持多選,會出現子節點沒有打印的問題
總結
- 如何初始化靜態的樹菜單。
- 如何實現異步加載樹菜單,單選後顯示在頁面上。
- 如何實現同步加載樹菜單,多選後顯示在頁面上。
- 樹菜單表的設計思路。
源碼:
https://github.com/ITDragonBlog/daydayup/tree/master/EasyUI/EasyUI-tree
逆向工程:
https://github.com/ITDragonBlog/daydayup/tree/master/mybatis/generatorSqlmapCustom
最後,EasyUI 樹菜單到這裏就結束了,感謝大家的閱讀。覺得不錯的可以點個贊!
EasyUI 樹菜單