树形列表菜单的数据组织形式一般有两种:一种是一次性向服务器请求所有节点的数据,一种是先请求根目录的节点,当用户点击该节点时,再去请求该节点的子叶节点的数据。这里我们的需求是第一种。
树形菜单是我们经常会用到的一种菜单展现方式,这里我推荐bootstrap-treeview,它是一款效果非常酷的基于bootstrap的jQuery多级列表树插件。该jQuery插件基于Twitter Bootstrap,以简单和优雅的方式来显示一些继承树结构,如视图树、列表树等。
前端页面的编写,比较简单,故简略描述,我们着重将后端如何按照要求组织返回所需的Json。
使用方法
首先要在页面中引入依赖的css样式和 bootstrap-treeview.js文件。
<!-- Required Stylesheets -->
<link href="./css/bootstrap.css" rel="external nofollow" rel="stylesheet">
<!-- Required Javascript -->
<script src="./js/jquery.js"></script>
<script src="./js/bootstrap-treeview.js"></script>
可以使用任何HTML DOM元素来作为该列表树的容器:
<div id="tree"></div>
调用
<script type="text/javascript">
$(function() {$.ajax({
type: "Get",
url: "doctype/FarmDoctypeLoadTreeNodes.do",
data: {id: ""},
dataType: "json",
success: function (result) {
$("#tree").treeview({
data: result.nodes, // 数据源
levels:1, //设置继承树默认展开的级别
showTags: true, //是否在每个节点右边显示tags标签。tag值必须在每个列表树的data结构中给出
onNodeSelected: function (event, data) {
/* console.log(data); */
/* alert(data.nodeId); */
window.location = basePath + "webtype/view"
+ data.id + "/Pub1.html";
}
});
},
error: function () {
alert("加载失败!")
}
});
});
</script>
数据源要求的数据格式:
var tree = [
{
text: "Parent 1",
tags: ['2'],
nodes: [
{
text: "Child 1",
tags: ['3'],
nodes: [
{
text: "Grandchild 1",
tags: ['1']
},
{
text: "Grandchild 2",
tags: ['1']
}
]
},
{
text: "Child 2",
tags: ['1']
}
]
},
{
text: "Parent 2",
tags: ['1']
},
{
text: "Parent 3",
tags: ['1']
},
{
text: "Parent 4",
tags: ['1']
},
{
text: "Parent 5",
tags: ['1']
}
];
看到这个数据结构,我们首先想到的是数据结构中的二叉树。欧拉,我们不妨可以把它理解成,这样的数据结构:
创建一个实例,组织所需要的数据结构的json.
public class BootstrapUiTreeNode implements Serializable {
private static final long serialVersionUID = 1L;
static final Logger log = Logger.getLogger(BootstrapUiTreeNode.class);
private String id;
private String parentId;
private String text;
//子叶节点
private List<BootstrapUiTreeNode> nodes = new ArrayList<>();
//统计该节点分类下文档的数量
private List<String> tags = new ArrayList<>(); // tags: ['NUM']
public BootstrapUiTreeNode(String id, String parentId, String text) {
super();
this.id = id;
this.parentId = parentId;
this.text = text;
}
public BootstrapUiTreeNode(String id, String parentId, String text, List<BootstrapUiTreeNode> nodes) {
super();
this.id = id;
this.parentId = parentId;
this.text = text;
this.nodes = nodes;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
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 List<BootstrapUiTreeNode> getNodes() {
return nodes;
}
public void setNodes(List<BootstrapUiTreeNode> nodes) {
this.nodes = nodes;
}
public List<String> getTags() {
return tags;
}
public void setTags(List<String> tags) {
this.tags = tags;
}
/**
* 加载第一层树节点 父节点的根目录的父id必须为"NONE",包含字段名ID,PARENTID,NAME
*
* @param parentId
* 父亲节点id
* @param SortTitle
* 排序字段index
* @param tableTitle
* 表名index
* @param id_title
* 表id的index
* @param parentId_title
* 表parentid的index
* @param name_title
* 表name的index
* @param icon_title
*/
public static List<Map<String, Object>> queryTreeNodeOne(String parentId, String SortTitle, String tableTitle, String id_title,
String parentId_title, String name_title) {
if (StringUtils.isEmpty(parentId)) {
parentId = "NONE";
}
DataResult nodeResult = null;
DBSort sort = new DBSort("a." + SortTitle, "ASC");
DataQuery query = DataQuery.getInstance("1",
id_title
+ ","
+ parentId_title
+ " as "
+ parentId_title
+ ","
+ name_title
+ " as "
+ name_title,
tableTitle + " a");
query.setPagesize(100);
query.addSort(sort);
DataQuerys.wipeVirus(parentId);
query.setSqlRule("and " + parentId_title + "='" + parentId + "'");
try {
nodeResult = query.search();
} catch (SQLException e) {
log.error(e.getMessage());
}
return nodeResult.getResultList();
}
public static List<TypeBrief> getPopTypesForReadDoc() {
DataQuery query = DataQuery.init(new DataQuery(),
"(SELECT a.NAME as NAME,a.SORT as SORT, a.ID as ID, a.READPOP as READPOP,a.WRITEPOP AS WRITEPOP, a.AUDITPOP AS AUDITPOP, a.PARENTID AS PARENTID, (SELECT COUNT(B1.ID) FROM FARM_DOC B1 LEFT JOIN FARM_RF_DOCTYPE B2 ON B1.ID = B2.DOCID LEFT JOIN FARM_DOCTYPE B3 ON B3.ID = B2.TYPEID WHERE B1.STATE='1' and B3.TREECODE LIKE CONCAT(A.TREECODE,'%') AND B1.STATE='1') AS NUM,f.oid as OID,f.FUNTYPE as FUNTYPE FROM farm_doctype AS a left join FARM_DOCTYPE_POP as f on f.TYPEID=a.ID WHERE 1 = 1 AND (TYPE = '1' OR TYPE = '3') AND PSTATE = '1' ) AS e",
"NAME,ID,PARENTID,NUM,OID,READPOP,AUDITPOP,WRITEPOP,FUNTYPE,SORT");
query.setPagesize(1000);
query.setNoCount();
query.setCache(Integer.valueOf(0), CACHE_UNIT.second);
query.addSort(new DBSort("SORT", "ASC"));
try {
DataResult result = query.search();
//利用反射获取对象集合
return result.getObjectList(TypeBrief.class);
} catch (SQLException e) {
log.error(e.toString());
return new ArrayList<TypeBrief>();
}
}
/**
* 遍历集合获得对象
*
* @param nodeList
* @return
*/
public static List<BootstrapUiTreeNode> findNode(List<TypeBrief> typeBriefs, List<Map<String, Object>> data, String id_title,
String parentId_title, String name_title) {
List<BootstrapUiTreeNode> nodeList = new ArrayList<>();
try {
for (Map<String, Object> node : data) {
if (node.get(id_title) == null) {
continue;
}
BootstrapUiTreeNode item = new BootstrapUiTreeNode(node.get(id_title).toString(),
node.get(parentId_title).toString(), node.get(name_title).toString());
//统计该节点下分类的文档数
for(TypeBrief typeBrief : typeBriefs) {
if(typeBrief.getId().equals(node.get(id_title).toString())) {
item.getTags().add(typeBrief.getNum() + "");
break;
}
}
nodeList.add(item);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return nodeList;
}
/**
* 先用递归构造除第一层几个根节点外的类多叉树结构
* @param node
* @param parentId
* @param sortTitle
* @param tableTitle
* @param id_title
* @param parentId_title
* @param name_title
* @return
*/
public static BootstrapUiTreeNode createSubtreeTreeNodes(List<TypeBrief> typeBriefs, BootstrapUiTreeNode node, String parentId, String sortTitle, String tableTitle, String id_title,
String parentId_title, String name_title) {
//获取该根节点下的所有子节点
List<Map<String, Object>> data = queryTreeNodeOne(parentId, sortTitle, tableTitle, id_title, parentId_title, name_title);
List<BootstrapUiTreeNode> roots = findNode(typeBriefs, data, id_title, parentId_title, name_title);
if(null != roots && roots.size() > 0) {
//递归继续查询
for(BootstrapUiTreeNode child : roots) {
BootstrapUiTreeNode treeNode = createSubtreeTreeNodes(typeBriefs, child, child.getId(), sortTitle, tableTitle, id_title, parentId_title, name_title);
node.getNodes().add(treeNode);
}
} else {
node.setNodes(null);
}
return node;
}
/**
* 补上第一层的几个根节点
* @param parentId
* @param sortTitle
* @param tableTitle
* @param id_title
* @param parentId_title
* @param name_title
* @return
*
* 注意:所以会存在叶子节点中的nodes没有数据的情况,这个时候会出现叶子节点是可以伸缩的,
* 因为nodes属性就是一个对象数组,它会被认为它还有子节点,其实是没有的。
*
*/
public static List<BootstrapUiTreeNode> createTreeNodes(String parentId, String sortTitle, String tableTitle, String id_title,
String parentId_title, String name_title) {
//统计所有节点分类对应的文档数目
List<TypeBrief> typeBriefs = getPopTypesForReadDoc();
List<BootstrapUiTreeNode> nodes = new ArrayList<>();
//查询第一层根节点
List<Map<String, Object>> data = queryTreeNodeOne(parentId, sortTitle, tableTitle, id_title, parentId_title, name_title);
List<BootstrapUiTreeNode> treeNodes = findNode(typeBriefs, data, id_title, parentId_title, name_title);
for(BootstrapUiTreeNode node : treeNodes) {
BootstrapUiTreeNode treeNode = createSubtreeTreeNodes(typeBriefs, node, node.getId(), sortTitle, tableTitle, id_title, parentId_title, name_title);
nodes.add(treeNode);
}
return nodes;
}
}
控制器:
@RequestMapping("/FarmDoctypeLoadTreeNodes")
@ResponseBody
public Map<String, Object> loadTreeNodes(DataQuery query, HttpServletRequest request, String id) {
query = EasyUiUtils.formatGridQuery(request, query);
try {
List<BootstrapUiTreeNode> treeNodes = BootstrapUiTreeNode.createTreeNodes(id, "SORT", "FARM_DOCTYPE", "ID", "PARENTID", "NAME");
return ViewMode.getInstance().putAttr("nodes", treeNodes).returnObjMode();
} catch (Exception e) {
log.error(e.getMessage());
return ViewMode.getInstance().setError(e.getMessage()).returnObjMode();
}
}
访问结果:
最终树形菜单效果: