精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (2)
|
|
---|---|
作者 | 正文 |
发表时间:2012-03-28
最后修改:2012-08-19
多叉树结合JavaScript树形控件实现无限级树形菜单(一种构建多级有序树形结构JSON(或XML)数据源的方法)
Ext JS框架是富客户端开发中出类拔萃的框架之一。在Ext的UI控件中,树形控件无疑是最为常用的控件之一,它用来实现树形结构的菜单。TreeNode用来实现静态的树形菜单,AsyncTreeNode用来实现动态的异步加载树形菜单,后者最为常用,它通过接收服务器端返回来的JSON格式的数据,动态生成树形菜单节点。动态生成树有两种思路:一种是一次性生成全部树节点,另一种是逐级加载树节点(利用AJAX,每次点击节点时查询下一级节点)。对于大数据量的菜单节点来说,逐级加载是比较合适的选择,但是对于小数据量的菜单来说,一次性生成全部节点应该是最为合理的方案。在实际应用开发中,一般不会遇到特别大数据量的场景,所以一次性生成全部菜单节点是我们重点研究的技术点,也就是本文要解决的关键技术问题。本文以基于Ext JS的应用系统为例,讲述如何将数据库中的无限级层次数据一次性在界面中生成全部菜单节点(例如在界面中以树形方式一次性展示出银行所有分支机构的信息),同时对每一个层次的菜单节点按照某一属性和规则排序,展示出有序的菜单树。
解决一次性构造无限级树形菜单的问题,可以拓展出更多的应用场景,例如树形结构表格TreeGrid,一次性生成树形表格,对树形表格进行完整分页,对表格列进行全排序;或者可以利用本文的思路扩展出其他的更复杂的应用场景。
图二,树形结构表格
让我们先看两段代码片段:
Ext.onReady( function(){ var tree = new Ext.tree.TreePanel({ height: 300, width: 400, animate:true, enableDD:true, containerScroll: true, rootVisible: false, frame: true, // getBranch.do请求服务器返回多级树形结构的JSON字符串 loader: new Ext.tree.TreeLoader({dataUrl:'getBranch.do'}), root : new Ext.tree.AsyncTreeNode({id:'0',text:'根结点'}) }); tree.expandAll(); } ); <% // 读取银行分支机构的层次数据 List result = DataAccess.getBankInfoList(); // 将层次数据转换为多叉树对象(本文下面会详细介绍该数据结构的实现方法) Node root = ExtTreeHelper.createExtTree(result); %> [ <%=root.toString()%> <!-- 以JSON的形式返回响应数据,Ext.tree.TreeLoader会根据此数据生成树形菜单 --> ]
{ id: '100000', text: '廊坊银行总行', children: [ { id: '110000', text: '廊坊分行', children: [ { id: '113000', text: '廊坊银行开发区支行', leaf: true }, { id: '112000', text: '廊坊银行解放道支行', children: [ { id: '112200', text: '廊坊银行三大街支行', leaf: true }, { id: '112100', text: '廊坊银行广阳道支行', leaf: true } ] }, { id: '111000', text: '廊坊银行金光道支行', leaf: true } ] } ] }
同时还需要对树中每一个层次的节点按照某一属性(比如分支机构编号)进行排序,以展示出有序的树形菜单。
现在可以把问题概括为:
下面介绍解决问题的思路:
有了数据结构,还要实现相应的算法,我们需要实现两种算法:
概括起来分为三步:
package test; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Collections; /** * 多叉树类 */ public class MultipleTree { public static void main(String[] args) { // 读取层次数据结果集列表 List dataList = VirtualDataGenerator.getVirtualResult(); // 节点列表(散列表,用于临时存储节点对象) HashMap nodeList = new HashMap(); // 根节点 Node root = null; // 将结果集存入散列表(后面将借助散列表构造多叉树) for (Iterator it = dataList.iterator(); it.hasNext();) { Map dataRecord = (Map) it.next(); Node node = new Node(); node.id = (String) dataRecord.get("id"); node.text = (String) dataRecord.get("text"); node.parentId = (String) dataRecord.get("parentId"); nodeList.put(node.id, node); } // 构造无序的多叉树 Set entrySet = nodeList.entrySet(); for (Iterator it = entrySet.iterator(); it.hasNext();) { Node node = (Node) ((Map.Entry) it.next()).getValue(); if (node.parentId == null || node.parentId.equals("")) { root = node; } else { ((Node) nodeList.get(node.parentId)).addChild(node); } } // 输出无序的树形菜单的JSON字符串 System.out.println(root); // 对多叉树进行横向排序 root.sortChildren(); // 输出有序的树形菜单的JSON字符串 System.out.println(root); // 程序输出结果如下: // // 无序的树形菜单(格式化后的结果,可使用JSON格式化工具查看, // 例如 http://jsonviewer.stack.hu/ 在线查看器): // { // id : '100000', // text : '廊坊银行总行', // children : [ // { // id : '110000', // text : '廊坊分行', // children : [ // { // id : '113000', // text : '廊坊银行开发区支行', // leaf : true // }, // { // id : '111000', // text : '廊坊银行金光道支行', // leaf : true // }, // { // id : '112000', // text : '廊坊银行解放道支行', // children : [ // { // id : '112200', // text : '廊坊银行三大街支行', // leaf : true // }, // { // id : '112100', // text : '廊坊银行广阳道支行', // leaf : true // } // ] // } // ] // } // ] // } // 有序的树形菜单(格式化后的结果): // { // id : '100000', // text : '廊坊银行总行', // children : [ // { // id : '110000', // text : '廊坊分行', // children : [ // { // id : '111000', // text : '廊坊银行金光道支行', // leaf : true // }, // { // id : '112000', // text : '廊坊银行解放道支行', // children : [ // { // id : '112100', // text : '廊坊银行广阳道支行', // leaf : true // }, // { // id : '112200', // text : '廊坊银行三大街支行', // leaf : true // } // ] // }, // { // id : '113000', // text : '廊坊银行开发区支行', // leaf : true // } // ] // } // ] // } } } /** * 节点类 */ class Node { /** * 节点编号 */ public String id; /** * 节点内容 */ public String text; /** * 父节点编号 */ public String parentId; /** * 孩子节点列表 */ private List children = new ArrayList(); // 添加孩子节点 public void addChild(Node node) { children.add(node); } // 先序遍历,拼接JSON字符串 public String toString() { String result = "{" + "id : '" + id + "'" + ", text : '" + text + "'"; if (children.size() != 0) { result += ", children : ["; for (Iterator it = children.iterator(); it.hasNext();) { result += ((Node) it.next()).toString() + ","; } result = result.substring(0, result.length() - 1); result += "]"; } else { result += ", leaf : true"; } return result + "}"; } // 兄弟节点横向排序 public void sortChildren() { if (children.size() != 0) { // 对本层节点进行排序(可根据不同的排序属性,传入不同的比较器,这里传入ID比较器) Collections.sort(children, new NodeIDComparator()); // 对每个节点的下一层节点进行排序 for (Iterator it = children.iterator(); it.hasNext();) { ((Node) it.next()).sortChildren(); } } } } /** * 节点比较器 */ class NodeIDComparator implements Comparator { // 按照节点编号比较 public int compare(Object o1, Object o2) { int j1 = Integer.parseInt(((Node) o1).id); int j2 = Integer.parseInt(((Node) o2).id); return (j1 < j2 ? -1 : (j1 == j2 ? 0 : 1)); } } /** * 构造虚拟的层次数据 */ class VirtualDataGenerator { // 构造无序的结果集列表,实际应用中,该数据应该从数据库中查询获得; public static List getVirtualResult() { List dataList = new ArrayList(); HashMap dataRecord1 = new HashMap(); dataRecord1.put("id", "112000"); dataRecord1.put("text", "廊坊银行解放道支行"); dataRecord1.put("parentId", "110000"); HashMap dataRecord2 = new HashMap(); dataRecord2.put("id", "112200"); dataRecord2.put("text", "廊坊银行三大街支行"); dataRecord2.put("parentId", "112000"); HashMap dataRecord3 = new HashMap(); dataRecord3.put("id", "112100"); dataRecord3.put("text", "廊坊银行广阳道支行"); dataRecord3.put("parentId", "112000"); HashMap dataRecord4 = new HashMap(); dataRecord4.put("id", "113000"); dataRecord4.put("text", "廊坊银行开发区支行"); dataRecord4.put("parentId", "110000"); HashMap dataRecord5 = new HashMap(); dataRecord5.put("id", "100000"); dataRecord5.put("text", "廊坊银行总行"); dataRecord5.put("parentId", ""); HashMap dataRecord6 = new HashMap(); dataRecord6.put("id", "110000"); dataRecord6.put("text", "廊坊分行"); dataRecord6.put("parentId", "100000"); HashMap dataRecord7 = new HashMap(); dataRecord7.put("id", "111000"); dataRecord7.put("text", "廊坊银行金光道支行"); dataRecord7.put("parentId", "110000"); dataList.add(dataRecord1); dataList.add(dataRecord2); dataList.add(dataRecord3); dataList.add(dataRecord4); dataList.add(dataRecord5); dataList.add(dataRecord6); dataList.add(dataRecord7); return dataList; } }
在实际的项目中,可以把上面的有效代码融入其中,或者在此基础上进行一些扩展:
既然可以构造无限级的JSON字符串,那么也可以根据这个思路构造无限级的XML字符串,或者构造具有层次结构的UL – LI组合(用UL - LI来展示树形结构),或者构造具有层次结构的TABLE(用TABLE来展示树形结构)。如下所示:
(1)XML层次结构 <menuGroup id="100000" name="廊坊银行总行"> <menuGroup id="110000" name="廊坊分行"> <menu id="113000" name="廊坊银行开发区支行"> </menu> <menu id="111000" name="廊坊银行金光道支行"> </menu> <menuGroup id="112000" name="廊坊银行解放道支行"> <menu id="112200" name="廊坊银行三大街支行"> </menu> <menu id="112100" name="廊坊银行广阳道支行"> </menu> </menuGroup> </menuGroup> </menuGroup>
(2)UL - LI 层次结构 <ul> <li>廊坊银行总行</li> <ul> <li>廊坊分行</li> <ul> <li>廊坊银行开发区支行</li> <li>廊坊银行解放道支行</li> <ul> <li>廊坊银行三大街支行</li> <li>廊坊银行广阳道支行</li> </ul> <li>廊坊银行金光道支行</li> </ul> </ul> </ul>
(3)TABLE层次结构 <table> <tr><td>廊坊银行总行</td></tr> <tr><td> 廊坊分行</td></tr> <tr><td> 廊坊银行开发区支行</td></tr> <tr><td> 廊坊银行解放道支行</td></tr> <tr><td> 廊坊银行三大街支行</td></tr> <tr><td> 廊坊银行广阳道支行</td></tr> <tr><td> 廊坊银行金光道支行</td></tr> </table>
五、参考书籍
注:本文的思路方法是受到Oracle层次查询的启发,所以将《OCA Oracle Database 11g SQL Fundamentals I Exam Guide》这本书也写在了参考书籍里面,构造多叉树的方法是从数据结构中获得灵感, 故也把《数据结构与算法分析(Java语言描述)》这本书列了出来。
六、联系方式
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2012-03-28
用Ext做这样的大项目,楼主你等着挨骂吧,我在05年的时候用过Ext+dwr做过类似的,后来加载数据慢的要死就放弃了,祝你好运。
PS:一个月前好像有人发过类似的代码。 |
|
返回顶楼 | |
发表时间:2012-03-28
特别是从数据库迭代生成树的时候,如果数据库在本地还略好,如果在远程简直无法忍受。
|
|
返回顶楼 | |
发表时间:2012-03-29
第一次发表时间:2012-01-01,原文连接:http://www.iteye.com/topic/1119490
第二次发表时间:2012-01-24 ,原文连接:http://www.iteye.com/topic/1119961
第三次发表时间:2012-02-01 ,原文连接:http://www.iteye.com/topic/1120062
敢问:哥们,重复发表一篇文章多次,有意思吗?? |
|
返回顶楼 | |
发表时间:2012-03-29
最后修改:2012-07-11
lib 写道
第一次发表时间:2012-01-01,原文连接:http://www.iteye.com/topic/1119490
第二次发表时间:2012-01-24 ,原文连接:http://www.iteye.com/topic/1119961
第三次发表时间:2012-02-01 ,原文连接:http://www.iteye.com/topic/1120062
敢问:哥们,重复发表一篇文章多次,有意思吗??
|
|
返回顶楼 | |
发表时间:2012-03-29
memorymultitree 写道
lib 写道
第一次发表时间:2012-01-01,原文连接:http://www.iteye.com/topic/1119490
第二次发表时间:2012-01-24 ,原文连接:http://www.iteye.com/topic/1119961
第三次发表时间:2012-02-01 ,原文连接:http://www.iteye.com/topic/1120062
敢问:哥们,重复发表一篇文章多次,有意思吗??
而且不光在大陆发表,还要在台湾发表,已经发在台湾的javaworld上了,链接是:http://www.javaworld.com.tw/jute/post/view?bid=49&id=305563&sty=1
希望英语能力强的同志帮我翻译成英文,广泛转载,谢谢。
后台二叉树算法,使用json和前台进行数据传输。难道使用的技术很先进吗?难道这种方式很超前吗?
我很欣赏也很佩服你能无私的把实现的思路和源代码分享给大家。但是,无休止地反复发帖,就让人产生厌恶感。
宣传是好的,但是这种方式反而会有适得其反的效果。 |
|
返回顶楼 | |
发表时间:2012-03-29
lib 写道
memorymultitree 写道
lib 写道
第一次发表时间:2012-01-01,原文连接:http://www.iteye.com/topic/1119490
第二次发表时间:2012-01-24 ,原文连接:http://www.iteye.com/topic/1119961
第三次发表时间:2012-02-01 ,原文连接:http://www.iteye.com/topic/1120062
敢问:哥们,重复发表一篇文章多次,有意思吗??
而且不光在大陆发表,还要在台湾发表,已经发在台湾的javaworld上了,链接是:http://www.javaworld.com.tw/jute/post/view?bid=49&id=305563&sty=1
希望英语能力强的同志帮我翻译成英文,广泛转载,谢谢。
后台二叉树算法,使用json和前台进行数据传输。难道使用的技术很先进吗?难道这种方式很超前吗?
我很欣赏也很佩服你能无私的把实现的思路和源代码分享给大家。但是,无休止地反复发帖,就让人产生厌恶感。
宣传是好的,但是这种方式反而会有适得其反的效果。
支持一个....深有同感。 |
|
返回顶楼 | |
发表时间:2012-03-29
lib 写道
memorymultitree 写道
lib 写道
第一次发表时间:2012-01-01,原文连接:http://www.iteye.com/topic/1119490
第二次发表时间:2012-01-24 ,原文连接:http://www.iteye.com/topic/1119961
第三次发表时间:2012-02-01 ,原文连接:http://www.iteye.com/topic/1120062
敢问:哥们,重复发表一篇文章多次,有意思吗??
而且不光在大陆发表,还要在台湾发表,已经发在台湾的javaworld上了,链接是:http://www.javaworld.com.tw/jute/post/view?bid=49&id=305563&sty=1
希望英语能力强的同志帮我翻译成英文,广泛转载,谢谢。
后台二叉树算法,使用json和前台进行数据传输。难道使用的技术很先进吗?难道这种方式很超前吗?
我很欣赏也很佩服你能无私的把实现的思路和源代码分享给大家。但是,无休止地反复发帖,就让人产生厌恶感。
宣传是好的,但是这种方式反而会有适得其反的效果。
|
|
返回顶楼 | |
发表时间:2012-03-30
在ztree 面前,你这mTree 可以说是mintree 功能少,形势大一,设计思路技术一般,拿过来要修改半天才能用,改完以后我可以说这个是我自己写的。
|
|
返回顶楼 | |
发表时间:2012-03-30
[quote="jiuyuehe"]在ztree 面前,你这mTree 可以说是mintree 功能少,形势大一,设计思路技术一般,拿过来要修改半天才能用,改完以后我可以说这个是我自己写的。[/quote]
兄弟,把你改过的代码贴出来,我看看到底改了多少,以便我进行完善。 |
|
返回顶楼 | |