JavaScript樹形組件實現(xiàn)無限級樹形結構
?一、問題研究的背景和意義?
在Web應用程序開發(fā)領域,基于Ajax技術的JavaScript樹形組件已經(jīng)被廣泛使用,它用來在Html頁面上展現(xiàn)具有層次結構的數(shù)據(jù)項。目前市場上常見的JavaScript框架及組件庫中均包含自己的樹形組件,例如jQuery、Ext JS等,還有一些獨立的樹形組件,例如dhtmlxTree等,這些樹形組件完美的解決了層次數(shù)據(jù)的展示問題。展示離不開數(shù)據(jù),樹形組件主要利用Ajax技術從服務器端獲取數(shù)據(jù)源,數(shù)據(jù)源的格式主要包括JSON、XML等,而這些層次數(shù)據(jù)一般都存儲在數(shù)據(jù)庫中。“無限級樹形結構”,顧名思義,沒有級別的限制,它的數(shù)據(jù)通常來自數(shù)據(jù)庫中的無限級層次數(shù)據(jù),這種數(shù)據(jù)的存儲表通常包括id和parentId這兩個字段,以此來表示數(shù)據(jù)之間的層次關系。現(xiàn)在問題來了,既然樹形組件的數(shù)據(jù)源采用JSON或XML等格式的字符串來組織層次數(shù)據(jù),而層次數(shù)據(jù)又存儲在數(shù)據(jù)庫的表中,那么如何建立起樹形組件與層次數(shù)據(jù)之間的關系,換句話說,如何將數(shù)據(jù)庫中的層次數(shù)據(jù)轉換成對應的層次結構的JSON或XML格式的字符串,返回給客戶端的JavaScript樹形組件?這就是我們要解決的關鍵技術問題。本文將以目前市場上比較知名的Ext JS框架為例,講述實現(xiàn)無限級樹形結構的方法,該方法同樣適用于其它類似的JavaScript樹形組件。
Ext JS框架是富客戶端開發(fā)中出類拔萃的框架之一。在Ext的UI組件中,樹形組件無疑是最為常用的組件之一,它用來實現(xiàn)樹形結構的視圖。TreeNode用來實現(xiàn)靜態(tài)的樹形結構,AsyncTreeNode用來實現(xiàn)動態(tài)的異步加載樹形結構,后者最為常用,它通過接收服務器端返回來的JSON格式的數(shù)據(jù),動態(tài)生成樹形結構節(jié)點。動態(tài)生成樹有兩種思路:一種是一次性生成全部樹節(jié)點,另一種是逐級加載樹節(jié)點(利用Ajax,每次點擊節(jié)點時查詢下一級節(jié)點)。對于大數(shù)據(jù)量的樹節(jié)點來說,逐級加載是比較合適的選擇,但是對于小數(shù)據(jù)量的樹節(jié)點來說,一次性生成全部節(jié)點應該是最為合理的方案。在實際應用開發(fā)中,一般不會遇到特別大數(shù)據(jù)量的場景,所以一次性生成全部樹節(jié)點是我們重點研究的技術點,也就是本文要解決的關鍵技術問題。本文以基于Ext JS的應用系統(tǒng)為例,講述如何將數(shù)據(jù)庫中的無限級層次數(shù)據(jù)一次性在界面中生成全部樹節(jié)點(例如在界面中以樹形方式一次性展示出銀行所有分支機構的信息),同時對每一個層次的節(jié)點按照某一屬性和規(guī)則排序,展示出有序的樹形結構。
解決一次性構造無限級樹形結構的問題,可以拓展出更多的應用場景,例如樹形結構表格TreeGrid,一次性生成樹形表格,對樹形表格進行完整分頁,對表格列進行全排序;或者可以利用本文的思路擴展出其他的更復雜的應用場景。
先看兩個圖例,有個直觀上的認識:
圖一,銀行分支機構樹形結構:

圖二,樹形結構表格:

?二、詳細設計方案?
讓我們先看兩段代碼片段:
文件一,branchTree.html (Ext樹形組件頁面)
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字符串)
<% // 讀取銀行分支機構的層次數(shù)據(jù) List result = DataAccess.getBankInfoList(); // 將層次數(shù)據(jù)轉換為多叉樹對象(本文下面會詳細介紹該數(shù)據(jù)結構的實現(xiàn)方法) Node root = ExtTreeHelper.createExtTree(result); ? %> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [ <%=root.toString()%> <!-- 以JSON的形式返回響應數(shù)據(jù),Ext.tree.TreeLoader會根據(jù)此數(shù)據(jù)生成樹形結構 --> ]
以上兩個程序文件是一次性生成無限級樹形結構所必須的,其中最為關鍵的部分就是如何生成一個無限級的樹形結構JSON字符串,返回給客戶端的Ext樹形組件。
對于銀行分支機構來說,需要返回類似如下的JSON串:
{
?id: '100000',
?text: '廊坊銀行總行',
?children: [
? ?{
? ? ?id: '110000',
? ? ?text: '廊坊分行',
? ? ?children: [
? ? ? ?{
? ? ? ? ?id: '113000',
? ? ? ? ?text: '廊坊銀行開發(fā)區(qū)支行',
? ? ? ? ?leaf: true
? ? ? ?},
? ? ? ?{
? ? ? ? ?id: '112000',
? ? ? ? ?text: '廊坊銀行解放道支行',
? ? ? ? ?children: [
? ? ? ? ? ?{
? ? ? ? ? ? ?id: '112200',
? ? ? ? ? ? ?text: '廊坊銀行三大街支行',
? ? ? ? ? ? ?leaf: true
? ? ? ? ? ?},
? ? ? ? ? ?{
? ? ? ? ? ? ?id: '112100',
? ? ? ? ? ? ?text: '廊坊銀行廣陽道支行',
? ? ? ? ? ? ?leaf: true
? ? ? ? ? ?}
? ? ? ? ?]
? ? ? ?},
? ? ? ?{
? ? ? ? ?id: '111000',
? ? ? ? ?text: '廊坊銀行金光道支行',
? ? ? ? ?leaf: true
? ? ? ?}
? ? ?]
? ?}
?]
}同時還需要對樹中每一個層次的節(jié)點按照某一屬性(比如分支機構編號)進行排序,以展示出有序的樹形結構。
現(xiàn)在可以把問題概括為:
- 1、 把數(shù)據(jù)庫中的層次數(shù)據(jù)轉換成多級樹形結構的JSON格式的字符串
- 2、 對樹中每一個層次的節(jié)點按照某一屬性(比如分支機構編號)進行排序
下面介紹解決問題的思路:
在數(shù)據(jù)結構這門課中,我們都學過樹,無限級樹形結構就可以抽象成一種多叉樹結構,即每個節(jié)點下包含多個子節(jié)點的樹形結構,首先就需要把數(shù)據(jù)庫中的層次數(shù)據(jù)轉換成多叉樹結構的對象樹,也就是構造出一棵多叉樹。
有了數(shù)據(jù)結構,還要實現(xiàn)相應的算法,我們需要實現(xiàn)兩種算法:
- 1、兄弟節(jié)點橫向排序算法,對隸屬于同一個父節(jié)點下面的所有直接子節(jié)點按照某一節(jié)點屬性和規(guī)則進行排序,保持兄弟節(jié)點橫向有序;
- 2、先序遍歷算法,遞歸打印出無限級JSON字符串。
概括起來分為三步:
- 1、 構造無序的多叉樹結構
- 2、 實現(xiàn)兄弟節(jié)點橫向排序方法
- 3、 實現(xiàn)先序遍歷方法,打印出JSON字符串
如圖所示:

?三、源代碼實現(xiàn)(Java版)?
實現(xiàn)這樣一顆樹,需要設計兩個類:樹類(MultipleTree)、節(jié)點類(Node);排序時還需要一個比較器類(NodeIDComparator);為了方便演示,還需要構造一些假的層次數(shù)據(jù),因此還需要建一個構造假數(shù)據(jù)的類(VirtualDataGenerator),以下代碼拷貝出來之后可直接運行測試:
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) {
? // 讀取層次數(shù)據(jù)結果集列表 ?
?List dataList = VirtualDataGenerator.getVirtualResult();
?// 節(jié)點列表(映射表,用于臨時存儲節(jié)點對象)
?HashMap nodeList = new HashMap();
?// 根節(jié)點
?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 : '廊坊銀行開發(fā)區(qū)支行', ?
?// ? ? ? 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 : '廊坊銀行開發(fā)區(qū)支行', ?
?// ? ? ? leaf : true
?// ? ? ? }
?// ? ? ]
?// ? ? }
?// ? ]
?// ?} ?
}
}
/**
* 節(jié)點類
*/
class Node {
/**
?* 節(jié)點編號
?*/
public String id;
/**
?* 節(jié)點內容
?*/
public String text;
/**
?* 父節(jié)點編號
?*/
public String parentId;
/**
?* 孩子節(jié)點列表
?*/
private List children = new ArrayList();
// 添加孩子節(jié)點
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 (int i = 0; i < children.size(); i++) {
? ? result += ((Node) children.get(i)).toString() + ","; ? ?
? }
? result = result.substring(0, result.length() - 1);
? result += "]";
?} else {
? result += ", leaf : true";
?}
?return result + "}";
}
// 兄弟節(jié)點橫向排序
public void sortChildren() {
?if (children.size() != 0) {
? // 對本層節(jié)點進行排序(可根據(jù)不同的排序屬性,傳入不同的比較器,這里 傳入ID比較器)
? Collections.sort(children, new NodeIDComparator());
? ? // 對每個節(jié)點的下一層節(jié)點進行排序 ? ?
? for (int i = 0; i < children.size(); i++) {
? ?((Node) children.get(i)).sortChildren();
? }
?}
}
}
/**
* 節(jié)點比較器
*/
class NodeIDComparator implements Comparator {
// 按照節(jié)點編號比較
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));
}
}
/**
* 構造虛擬的層次數(shù)據(jù)
*/
class VirtualDataGenerator {
// 構造無序的結果集列表,實際應用中,該數(shù)據(jù)應該從數(shù)據(jù)庫中查詢獲得;
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", "廊坊銀行開發(fā)區(qū)支行");
?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;
}
}好了,通過上面的代碼,就可以實現(xiàn)多叉樹的兄弟節(jié)點橫向排序和先序遍歷了,實現(xiàn)了將層次數(shù)據(jù)轉換為有序無限級樹形結構JSON字符串的目的。
在實際的項目中,可以把上面的有效代碼融入其中,或者在此基礎上進行一些擴展:
- 1、 實現(xiàn)對指定層次的排序(例如只排序第一層的節(jié)點,或者只排序某一父節(jié)點下的所有子節(jié)點)
- 2、 遍歷輸出樹形結構時可以加入判斷條件過濾掉某些節(jié)點
- 3、 實現(xiàn)節(jié)點的刪除功能
- 4、 在節(jié)點類中增加一個父節(jié)點的引用,就可以計算出某一節(jié)點所處的級別
- 5、 在不支持層次查詢的數(shù)據(jù)庫應用系統(tǒng)中使用該算法實現(xiàn)相同的效果
?四、思考與總結?
這篇文章的重點是如何構造有序的無限級的樹形結構JSON字符串,一次性生成樹形結構,而不是利用Ajax的方式,反復向服務器端發(fā)送請求,一級接一級的加載樹節(jié)點。
既然可以構造無限級的JSON字符串,那么也可以根據(jù)這個思路構造無限級的XML字符串,或者構造具有層次結構的UL – LI組合(用UL - LI來展示樹形結構),或者構造具有層次結構的TABLE(用TABLE來展示樹形結構)。
如下所示:
(1)XML層次結構
<nodeGroup id="100000" name="廊坊銀行總行"> <nodeGroup id="110000" name="廊坊分行"> ?<node id="113000" name="廊坊銀行開發(fā)區(qū)支行"> ? ?</node> ?<node id="111000" name="廊坊銀行金光道支行"> ? ?</node> ?<nodeGroup id="112000" name="廊坊銀行解放道支行"> ? ? <node id="112200" name="廊坊銀行三大街支行"> ? ? ? </node> ? ? <node id="112100" name="廊坊銀行廣陽道支行"> ? ? ? </node> ?</nodeGroup> </nodeGroup> </nodeGroup>
(2)UL - LI 層次結構
<ul> <li>廊坊銀行總行</li> <ul> ?<li>廊坊分行</li> ?<ul> ? ? <li>廊坊銀行開發(fā)區(qū)支行</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> 廊坊銀行開發(fā)區(qū)支行</td></tr> <tr><td> 廊坊銀行解放道支行</td></tr> <tr><td> 廊坊銀行三大街支行</td></tr> <tr><td> 廊坊銀行廣陽道支行</td></tr> <tr><td> 廊坊銀行金光道支行</td></tr> </table>
另外對TreeGrid樹形表格也有一定的價值:
- 1、 一次性構造樹形表格,實現(xiàn)數(shù)據(jù)分級展示
- 2、 通過更換比較器,實現(xiàn)對不同表格列的全排序(全排序指的是對所有頁的數(shù)據(jù)進行排序,而不是只對當前頁的數(shù)據(jù)排序;排序規(guī)則與Oracle數(shù)據(jù)庫中的層次查詢類似,即兄弟節(jié)點橫向排序)
- 3、 實現(xiàn)對樹形表格的完整分頁(每次分頁時,只取固定數(shù)目的第一層節(jié)點,之后調用toString方法,展示出完整條數(shù)的分級數(shù)據(jù),即每頁的記錄條數(shù)是不固定的,但必須是完整的樹形結構)
到此這篇關于JavaScript樹形組件實現(xiàn)無限級樹形結構的文章就介紹到這了,更多相關JavaScript無限級樹形結構內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JavaScript實現(xiàn)圖片懶加載與預加載的代碼詳解
圖片懶加載與預加載是前端優(yōu)化中比較常見的方法,也是前端面試中會被問到的問題,如果不做懶加載和預加載,瀏覽器的回流重繪很快,而圖片的加載是需要發(fā)送網(wǎng)絡請求的,一次性發(fā)很多請求就會導致網(wǎng)絡的堵塞,影響用戶體驗,接下來就讓我們來實現(xiàn)一下懶加載以及預加載的效果2025-03-03
游戲開發(fā)中如何使用CocosCreator進行音效處理
這篇文章主要介紹了游戲開發(fā)中如何使用CocosCreator進行音效處理,并對音效組件進行封裝,方便以后使用,同學們看完之后,一定要親手實驗一下2021-04-04

