1、問題研究的背景和意義 html
目前在Web應用程序開發領域,Ext JS框架已經逐漸被普遍使用,它是富客戶端開發中出類拔萃的框架之一。在Ext的UI控件中,樹形控件無疑是最爲經常使用的控件之一,它用來實現樹形結構的菜單。TreeNode用來實現靜態的樹形菜單,AsyncTreeNode用來實現動態的異步加載樹形菜單,後者最爲經常使用,它經過接收服務器端返回來的JSON格式的數據,動態生成樹形菜單節點。動態生成樹有兩種思路:一種是一次性生成所有樹節點,另外一種是逐級加載樹節點(利用AJAX,每次點擊節點時查詢下一級節點)。對於大數據量的菜單節點來講,逐級加載是比較合適的選擇,可是對於小數據量的菜單來講,一次性生成所有節點應該是最爲合理的方案。在實際應用開發中,通常不會遇到特別大數據量的場景,因此一次性生成所有菜單節點是咱們重點研究的技術點,本文就是介紹基於Ext JS的應用系統中如何將數據庫中的無限級層次數據一次性在界面中生成所有菜單節點(例如在界面中以樹形方式一次性展現出銀行全部分支機構的信息),同時對每個層次的菜單節點按照某一屬性和規則排序,展現出有序的菜單樹。 java
解決Ext JS無限級樹形菜單的問題,能夠拓展出更多的應用場景,例如樹形結構表格TreeGrid,一次性生成樹形表格,對樹形表格進行完整分頁,對錶格列進行全排序;或者能夠利用本文的思路擴展出其餘的更復雜的應用場景。 node
先看兩個圖例,有個直觀上的認識:
圖一,銀行分支機構樹形結構菜單 算法
圖二,樹形結構報表 數據庫
2、詳細設計方案 服務器
讓咱們先看一段代碼片斷: 數據結構
文件一,branchTree.html (Ext樹形控件頁面) 框架
Js代碼
- 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();
- }
- );
文件二,branchTreeJSON.jsp (接收getBranch.do請求,返回無限級JSON字符串) 異步
Java代碼
- <%
- // 讀取銀行分支機構的層次數據
- List result = DataAccess.getBankInfoList();
- // 將層次數據轉換爲多叉樹對象(本文下面會詳細介紹該數據結構的實現方法)
- Node root = ExtTreeHelper.createExtTree(result);
- %>
- [
- <%=root.toString()%> <!-- 以JSON的形式返回響應數據,Ext.tree.TreeLoader會根據此數據生成樹形菜單 -->
- ]
以上兩個程序文件是一次性生成無限級樹形菜單所必須的,其中最爲關鍵的部分就是如何生成一個無限級的JSON字符串,返回給客戶端的Ext樹形控件。對於銀行分支機構來講,須要返回相似以下的JSON串: jsp
Js代碼
- {
- 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
- }
- ]
- }
- ]
- }
同時還可能須要對樹中每個層次的節點按照某一屬性(好比分支機構編號)進行排序,以展現出有序的樹形菜單。
如今能夠把問題歸納爲:
一、 把數據庫中的層次數據轉換成JSON格式的字符串
二、 對樹中每個層次的節點按照某一屬性(好比分支機構編號)進行排序
下面介紹解決問題的思路:
咱們都學過數據結構,即組織大量數據的方法,無限級樹形菜單就能夠抽象成一種多叉樹結構,即每一個節點下包含多個子節點的樹形結構,首先就須要把數據庫中的層次數據轉換成多叉樹結構的對象樹,也就是構造出一棵多叉樹。
有了數據結構,還要實現相應的算法,咱們須要實現兩種算法:
一、兄弟節點橫向排序算法,對隸屬於同一個父節點下面的全部直接子節點按照某一節點屬性和規則進行排序,保持兄弟節點橫向有序;
二、先序遍歷算法,遞歸打印出無限級JSON字符串。
歸納起來分爲三步:
一、 構造無序的多叉樹結構
二、 實現兄弟節點橫向排序方法
三、 實現先序遍歷方法,打印出JSON字符串
如圖所示:
3、源代碼實現(Java語言版)
實現這樣一顆樹,須要設計三個類:樹類(ExtTree.java)、節點類(Node.java)、孩子列表類(Children.java);爲了方便演示,還須要構造一些假的層次數據,所以還須要建一個構造假數據的類(VirtualDataGenerator.java),如下代碼拷貝出來以後可直接運行測試:
Java代碼
- 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.toString());
- // 對多叉樹進行橫向排序
- root.sortChildren();
- // 輸出有序的樹形菜單的JSON字符串
- System.out.println(root.toString());
-
- // 程序輸出結果以下(無序的樹形菜單)(格式化後的結果):
- // {
- // 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 Children children = new Children();
-
- // 先序遍歷,拼接JSON字符串
- public String toString() {
- String result = "{"
- + "id : '" + id + "'"
- + ", text : '" + text + "'";
-
- if (children != null && children.getSize() != 0) {
- result += ", children : " + children.toString();
- } else {
- result += ", leaf : true";
- }
-
- return result + "}";
- }
-
- // 兄弟節點橫向排序
- public void sortChildren() {
- if (children != null && children.getSize() != 0) {
- children.sortChildren();
- }
- }
-
- // 添加孩子節點
- public void addChild(Node node) {
- this.children.addChild(node);
- }
- }
-
- /**
- * 孩子列表類
- */
- class Children {
- private List list = new ArrayList();
-
- public int getSize() {
- return list.size();
- }
-
- public void addChild(Node node) {
- list.add(node);
- }
-
- // 拼接孩子節點的JSON字符串
- public String toString() {
- String result = "[";
- for (Iterator it = list.iterator(); it.hasNext();) {
- result += ((Node) it.next()).toString();
- result += ",";
- }
- result = result.substring(0, result.length() - 1);
- result += "]";
- return result;
- }
-
- // 孩子節點排序
- public void sortChildren() {
- // 對本層節點進行排序
- // 可根據不一樣的排序屬性,傳入不一樣的比較器,這裏傳入ID比較器
- Collections.sort(list, new NodeIDComparator());
- // 對每一個節點的下一層節點進行排序
- for (Iterator it = list.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字符串的目的。
在實際的項目中,能夠把上面的有效代碼融入其中,或者在此基礎上進行一些擴展:
一、 實現對指定層次的排序(例如只排序第一層的節點,或者只排序某一父節點下的全部子節點)
二、 遍歷輸出樹形結構時能夠加入判斷條件過濾掉某些節點
三、 實現節點的刪除功能
四、 在節點類中增長一個父節點的引用,就能夠計算出某一節點所處的級別
五、 在不支持層次查詢的數據庫應用系統中使用該算法實現相同的效果
4、思考與總結
這篇文章的重點是如何構造有序的無限級的樹形結構JSON字符串,一次性生成樹形菜單,而不是利用AJAX的方式,反覆向服務器端發送請求,一級接一級的加載樹節點。
既然能夠構造無限級的JSON字符串,那麼也能夠根據這個思路構造無限級的XML字符串,或者構造具備層次結構的UL – LI組合(用UL - LI來展現樹形結構),或者構造具備層次結構的TABLE(用TABLE來展現樹形結構)。以下所示:
(1)XML層次結構
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 層次結構
Html代碼
- <ul>
- <li>廊坊銀行總行</li>
- <ul>
- <li>廊坊分行</li>
- <ul>
- <li>廊坊銀行開發區支行</li>
- <li>廊坊銀行解放道支行</li>
- <ul>
- <li>廊坊銀行三大街支行</li>
- <li>廊坊銀行廣陽道支行</li>
- </ul>
- <li>廊坊銀行金光道支行</li>
- </ul>
- </ul>
- </ul>
(3)TABLE層次結構
Html代碼
- <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>
另外對TreeGrid樹形表格也有必定的價值:
一、一次性構造樹形表格,實現數據分級展現
二、經過更換比較器,實現對不一樣表格列的全排序(全排序指的是對全部頁的數據進行排序,而不是隻對當前頁的數據排序)
三、實現對樹形表格的完整分頁(每次分頁時,只取固定數目的第一層節點,以後調用toString方法,展現出完整條數的分級數據)
5、參考書籍
一、Mark Allen Weiss,數據結構與算法分析(Java語言描述)
二、Bruce Eckel,Thinking In Java Third Edition
三、David Flanagan,JavaScript: The Definitive Guide, 5th Edition
四、OCA Oracle Database 11g SQL Fundamentals I Exam Guide