北京交通大学硕士论文多叉树结合JavaScript树形控件实现无限级树形菜单(一种构建多级有序树形结构JSON(或XML)数据源的方法)一、问题研究的背景和意义在Web应用程序开发领域,基于Ajax技术的JavaScript树形控件已经被广泛使用,它用来在Html页面上展现具有层次结构的数据项。目前市场上常见的JavaScript框架及组件库中均包含自己的树形控件,例如jQuery、Dojo、YUI、ExtJS等,还有一些独立的树形控件,例如dhtmlxTree等,这些树形控件完美的解决了层次数据的展示问题。展示离不开数据,树形控件主要利用Ajax技术从服务器端获取数据源,数据源的格式主要包括JSON、XML等,而这些层次数据一般都存储在数据库中。“无限级树形菜单”,顾名思义,没有级别的限制,它的数据通常来自数据库中的无限级层次数据,这种数据的存储表通常包括id和parentId这两个字段,以此来表示数据之间的层次关系。现在问题来了,既然树形控件的数据源采用JSON或XML等格式的字符串来组织层次数据,而层次数据又存储在数据库的表中,那么如何建立起树形控件与层次数据之间的关系,换句话说,如何将数据库中的层次数据转换成对应的层次结构的JSON或XML格式的字符串,返回给客户端的JavaScript树形控件?这就是我们要解决的关键技术问题。本文将以目前市场上比较知名的ExtJS框架为例,讲述实现无限级树形菜单的方法,该方法同样适用于其它类似的JavaScript树形控件。ExtJS框架是富客户端开发中出类拔萃的框架之一。在Ext的UI控件中,树形控件无疑是最为常用的控件之一,它用来实现树形结构的菜单。TreeNode用来实现静态的树形菜单,AsyncTreeNode用来实现动态的异步加载树形菜单,后者最为常用,它通过接收服务器端返回来的JSON格式的数据,动态生成树形菜单节点。动态生成树有两种思路:一种是一次性生成全部树节点,另一种是逐级加载树节点(利用Ajax,每次点击节点时查询下一级节点)。对于大数据量的菜单节点来说,逐级加载是比较合适的选择,但是对于小数据量的菜单来说,一次性生成全部节点应该是最为合理的方案。在实际应用开发中,一般不会遇到特别大数据量的场景,所以一次性生成全部菜单节点是我们重点研究的技术点,也就是本文要解决的关键技术问题。本文以基于ExtJS的应用系统为例,讲述如何将数据库中的无限级层次数据一次性在界面中生成全部菜单节点(例如在界面中以树形方式一次性展示出银行所有分支机构的信息),同时对每一个层次的菜单节点按照某一属性和规则排序,展示出有序的菜单树。解决一次性构造无限级树形菜单的问题,可以拓展出更多的应用场景,例如树形结构表格TreeGrid,一次性生成树形表格,对树形表格进行完整分页,对表格列进行全排序;或者可以利用本文的思路扩展出其他的更复杂的应用场景。先看两个图例,有个直观上的认识:图一,银行分支机构树形结构菜单北京交通大学硕士论文图二,树形结构表格二、详细设计方案让我们先看两段代码片段:文件一,branchTree.html(Ext树形控件页面)Ext.onReady(function(){vartree=newExt.tree.TreePanel({height:300,width:400,北京交通大学硕士论文animate:true,enableDD:true,containerScroll:true,rootVisible:false,frame:true,//getBranch.do请求服务器返回多级树形结构的JSON字符串loader:newExt.tree.TreeLoader({dataUrl:'getBranch.do'}),root:newExt.tree.AsyncTreeNode({id:'0',text:'根结点'})});tree.expandAll();});文件二,branchTreeJSON.jsp(接收getBranch.do请求,返回多级树形结构的JSON字符串)%//读取银行分支机构的层次数据Listresult=DataAccess.getBankInfoList();//将层次数据转换为多叉树对象(本文下面会详细介绍该数据结构的实现方法)Noderoot=ExtTreeHelper.createExtTree(result);%[%=root.toString()%!--以JSON的形式返回响应数据,Ext.tree.TreeLoader会根据此数据生成树形菜单--]以上两个程序文件是一次性生成无限级树形菜单所必须的,其中最为关键的部分就是如何生成一个无限级的树形结构JSON字符串,返回给客户端的Ext树形控件。对于银行分支机构来说,需要返回类似如下的JSON串:{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}]}]}同时还需要对树中每一个层次的节点按照某一属性(比如分支机构编号)进行排序,以展示出有序的树形菜单。现在可以把问题概括为:1、把数据库中的层次数据转换成多级树形结构的JSON格式的字符串2、对树中每一个层次的节点按照某一属性(比如分支机构编号)进行排序下面介绍解决问题的思路:在数据结构这门课中,我们都学过树,无限级树形菜单就可以抽象成一种多叉树结构,即每个节点下包含多个子节点的树形结构,首先就需要把数据库中的层次数据转换成多叉树结构的对象树,也就是构造出一棵多叉树。有了数据结构,还要实现相应的算法,我们需要实现两种算法:北京交通大学硕士论文1、兄弟节点横向排序算法,对隶属于同一个父节点下面的所有直接子节点按照某一节点属性和规则进行排序,保持兄弟节点横向有序;2、先序遍历算法,递归打印出无限级JSON字符串。概括起来分为三步:1、构造无序的多叉树结构2、实现兄弟节点横向排序方法3、实现先序遍历方法,打印出JSON字符串如图所示:北京交通大学硕士论文为了便于记住这个方法,我们给这棵树起个名字,叫做M树(M即multiple的首字母)。北京交通大学硕士论文三、源代码实现(Java语言版)实现这样一颗树,需要设计两个类:树类(MultipleTree)、节点类(Node);排序时还需要一个比较器类(NodeIDComparator);为了方便演示,还需要构造一些假的层次数据,因此还需要建一个构造假数据的类(VirtualDataGenerator),以下代码拷贝出来之后可直接运行测试:packagetest;importjava.util.ArrayList;importjava.util.Comparator;importjava.util.HashMap;importjava.util.Iterator;importjava.util.List;importjava.util.Map;importjava.util.Set;importjava.util.Collections;/***多叉树类*/publicclassMultipleTree{publicstaticvoidmain(String[]args){//读取层次数据结果集列表ListdataList=VirtualDataGenerator.getVirtualResult();//节点列表(散列表,用于临时存储节点对象)HashMapnodeList=newHashMap();//根节点Noderoot=null;//将结果集存入散列表(后面将借助散列表构造多叉树)for(Iteratorit=dataList.iterator();it.hasNext();){MapdataRecord=(Map)it.next();Nodenode=newNode();node.id=(String)dataRecord.get(id);node.text=(String)dataRecord.get(text);node.parentId=(String)dataRecord.get(parentId);nodeList.put(node.id,node);}//构造无序的多叉树SetentrySet=nodeList.entrySet();for(Iteratorit=entrySet.iterator();it.hasNext();){Nodenode=(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格式化工具查看,例如在线查看器)://{//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//}//]//}//]//}}}/***节点类*/classNode{/***节点编号*/publicStringid;/***节点内容*/publicStringtext;/***父节点编号*/publicStringparentId;/***孩子节点列表*/privateListchildren=newArrayList