梅花雪樹

MzTreeView 一次加載數據的樹


好東西當然要與大家分享。。。@與羊共舞的狼

MzTreeView 1.0 from--->>> http://www.meizz.com/

開發文檔: http://www.meizz.com/Web/Article.asp?id=436
控件下載: http://www.meizz.com/Web/Download/MzTreeView10.rar
應用示例: http://www.meizz.com/Web/Demo/MzTreeView10.htm

說明 MzTreeView 1.0 是數據一次性加載,客戶端節點異步展示的WEB腳本樹。MzTreeView 1.0 的理論節點數設計上限爲十萬節點,在節點數三萬的情況下頁面打開時間小於 3 秒。無限層次無限節點的數的層級組成方式:id parentId。即每個節點除本身的節點id之外還有它的父層節點id,通過這種方式就可以組合成無限層級的樹了。

在 MzTreeView 裏都有一個虛的根節點,其ID爲0,用戶可見的根節點其父節點ID皆爲0

MzTreeView 1.0在數據庫庫表裏的字段名稱:
字段名 字段的具體說明
id 節點ID(不可爲0,可以是數字或字符)
parentId 本節點的父節點ID(若本節點已爲根節點,此處填0)
text 節點的顯示文本(一般不允許爲空,不過有一種情況例外,即根節點,若根節點文本爲空,這個根節點將不會在頁面裏顯示)
hint 節點的說明註解
icon 節點的圖標,MzTreeView 1.0允許每個節點擁有不同的圖標(對應的名字在類裏的icons和iconsExpand定義)
data 節點掛的數據,格式是 param=value&param=value&... url裏?後的那串字符串格式,
url 每個節點允許擁有不同的鏈接,它爲空或者爲#時,樹裏這個節點的鏈接,點擊將無反應
target 每個節點的鏈接允許在不同的target裏打開,爲空時取類裏的默認值
method 點擊該鏈接時所想觸發的腳本語句
特注:每個字段值中不可有冒號: 不可以換行 引號酌情考慮應不與節點字符串的引號相沖突
設計模式 爲了達到能夠在瀏覽器中快速打開多節點樹的頁面,我做了很多的優化與創新,下面我將詳細解說幾項最重要的部分:

  • 數據一次性加載 首先我要說的就是數據的一次性加載。在目前的 B/S 架構開發中對於多節點多層次的樹,特別是樹節點量超過兩千的情況下,幾乎都是採取數據異步加載來達到目的,即用戶需要展開某個節點時,再從服務器端加載下級子節點的數據,數據異步加載最爲經典的例子就是 MSDN 網站左邊的目錄樹了。異步加載的優點在於可以擴充到無限級無限節點樹,樹的數據來源可以多樣化(數據庫或XML文件)等,但是它的缺點也是非常多的:設計模式比數據一次性加載要複雜得多,要考慮到 Browser/Server 之間的應答,要判斷子節點是否含有孫節點,後臺數據源的層級關係模型等。對網絡傳輸的信賴性太大,每個節點的展開都需要連一次 Server,只要在取某節點數據時網絡出現問題,就會導致該節點及其以下的子節點加載失敗。而採取數據一次加載的模式只要一次加載成功,服務器就可以不用管它了,服務器壓力減輕,腳本設計則完全獨立,對整棵樹節點的檢索可以在客戶端完成,節點展開響應速度快等等優勢,因此在節點數不多的情況下數據一次性加載更有優勢,那麼這個節點數不多不多到底多少節點量爲平衡點呢?就 ASP.net 裏帶的那個 TreeView 來說,在一兩千節點以下一次性加載比較具有優勢,而 MzTreeView 1.0 在節點量三萬至五萬以非常具有優勢。
  • 節點信息的壓縮傳輸 在瀏覽器裏顯示的樹結構其實都是一個個 HTML 元素組合起來的,在 WEB 頁面裏的樹都是根據樹節點的信息組合成一串的 HTML 元素列來顯示,這一步從節點信息到 HTML 的轉化可以在兩個地方生成:一個是在服務器端,一個是在客戶端。在服務器端生成的優點在於不須考慮客戶端的瀏覽器的兼容性,節點信息的可控性非常強,但是它的缺點也是非常大的:加重服務器的負擔,增加網絡傳輸量。在服務器端直接生成樹節點的 HTML 給服務器帶來的壓力是顯而易見的,也是非常巨大的,估計只要有幾十個併發連接就能讓服務器暫時停止響應了。這種直接在服務器生成樹的做法在實際運用環境中也有人使用,不過本人非常不贊成這種做法。當然也有人直接將樹生成爲一個靜態文件放在服務器端,這種做法對於樹節點相對固定不變的情況還是非常有利的,只需要生成一次,以後就不需要再生成了,服務器的壓力也非常小,但它的弊病在於可變化性太小,比如說不同的權限能看到的樹節點的不同這種情況,用這種生成靜態樹放在服務器端的做就沒有辦法解決,且不管是服務器端動態計算還是直接生成靜態樹文件放在服務器端都有一個避免不了的問題就是網絡傳輸量巨大。可以計算一下,一個樹點所需要的HTML字符量大約300到600字節之間。即含有一千節點的樹的網頁大小就有300K到600K,給網絡造成的壓力是非常巨大的,所以MzTreeView 1.0採用了節點信息的壓縮傳輸,大至一千節點的總傳輸量在30K左右,這可以差了一個數量級呀。本樹將每個節點所必要的信息組合成一個字符串序列,傳遞到客戶端,然後在客戶端再用客戶端腳本對這些信息進行分析,再生成一個個的節點HTML,這也符合了WEB的分散計算的原理,當然服務器端可以有選擇性輸出部分節點,這樣又做到節點的靈活多變性。
  • 傳輸的節點信息的可擴展性 服務器端將節點的必要信息組合成一個字符串序列傳遞到客戶端,然後客戶端再用腳本對這個字符串序列進行分析,生成樹的節點,那麼這個字符串序列對整個樹的生成的效率就有重要的影響了。我也參照過很多組串傳輸的例子,在一般的做法當中大多采用函數的參數方式傳遞。比如說定義一個函數 funName(p1, p2, p3, ...),然後服務器組串的時候就按位置給定數據。這種組串的弊病是非常大的,首先就是位置絕對錯不得,只要有一個位置數據出錯,這個節點的信息就亂了,對於那些在函數裏已經定義的但節點裏沒有的信息也得用空字符串補上,諸如:(p1, "", p3, p4, "", "", ""),且萬一這種組串的對應分析函數發生了變化,那麼這種串就算是廢了,得重新定義服務器端的字符串位置序列了,可以說這種組串的方式可擴展性極差。
    節點信息組串傳輸的另一種常用模式就是XML。XML以它的無限可擴展性已慚有代替HTML稱霸WEB的味道了。XML最大的優點就在於它的無限可擴展性,可以用任意的TagName名,可以有任意的Attribute名,節點與子節點已經有層級的關係,用XML來做WEB樹的數據源其實是最理想的,MSDN的資源目錄樹就是採用XML作爲傳遞的字符串,它唯一的不足之處就是不是所有瀏覽器都能很好地支持它,特別是在一些低版本的瀏覽器中,所以我只好忍痛割愛沒有啓用XML作爲中間的傳媒。那麼是不是有可能結合XML的擴展性對第一種組串的方式進行改進呢?當我愁眉不展的時候,HTML裏的STYLE樣式表寫法跳入我眼,樣式的寫法是"param1: value1; param2: value3; ...",哈哈,這不是現成地給我指明瞭路嗎?這種寫法擁有XML的可擴展性,位置順序的隨意性,又是傳統的長字符串,正合我意也!服務器給定這種數據源字符串,我不光可以在TreeView裏用它,還可以直接做Outlook Bar,下拉式層級菜單,右鍵層級菜單的數據源,豁然開朗也!我寫了一個函數專門解析這種文本:getAttribute()。
  • 客戶端節點數據的存儲方式及快速檢索 現在數據源準備好了,數據傳輸也已做到最大優化了,下面就是客戶端的腳本解析了,而這一步也正是樹生成的效率瓶頸所在。由於我沒有采用XML做爲數據源,所以我這裏就不討論XML+XSL和XMLDOM的模式,而只考慮HTML+DOM模式了。在HTML+DOM模式下客戶端存儲的方式有很多種,我就曾經看到過一種直接將字符串輸出在多行文本框<textarea>裏的,但歸究起來最常用的方式就是用一個數組來存放節點信息,不過就算是用數組也是種類多多,比如說數組裏套數組,節點通過記錄父節點在數組裏的索引號表示層級的等等,說到數組存放樹節點模式,我推薦阿信寫的那棵樹。在服務器端組織好腳本及節點字符串,客戶端瀏覽器加載這個頁面的時候順序執行其裏面的腳本,將一個個節點做最初步的解析,然後再一個個地ADD到數組中去。這種流程看起來似乎沒有什麼問題,在很多的傳統C/S結構編程中也就這種模式,但是隨着節點數的增多,這種流程在B/S裏的缺點就出現了:腳本的執行效率不如強語言高!我做過測試,在5000節點的數據量時這一步操作將耗時1秒鐘,也許你認爲這1秒鐘不算長,哪裏有5000這麼多節點的樹?但對於一個程序員來說,嚴謹是第一位的,若存在優化的可能性,情況出現的可能性,我們就得將它考慮到。那麼有沒有可能再進一步優化呢?當然有,否則我就用不着這麼大費口舌在這裏講了。既然在腳本里執行 for 循環插入數組效率不高那我不用循環,且考慮到在數組裏這麼多節點中搜索我想要的某幾個節點,還得用循環,所以我乾脆就不再用數組來存放樹節點了,那用什麼呢?說到這裏我得插幾句題外話,客戶端腳本也可以寫一些類,雖說寫出來的類沒有強語言那麼好,繼承性不好等,但腳本終究還是可以寫出一些簡單的類的。類可以定義屬性,也可以定義方法,並且訪問屬性的時候可以通過屬性名下標直接訪問到屬性值,關於腳本里如何寫類,大家可以參考我在JavaScript和VBScript裏對這方面的詳細說明。呵呵,說了這麼多的題外話,不過相信大家也猜到了,新的節點存儲方式就是類的屬性自定義擴展方式。這種存儲方式隨着頁面的打開,頁面裏的腳本被執行,類的屬性值不需要做任何添加的動作就直接寫到內存當中,比數組模式少去了ADD操作,這一步從字符串到內存的時間就是頁面打開的時間,我測試了一下,5000節點的樹用這種方式打開,只需0.1秒呀,與數組模式速度整整差了一個數量級呀!且這種模式還有一種好處,在幾千個節點裏要找到目標節點,只需要知道節點對應的屬性名,一步就可以直接找到。
    客戶端樹節點存儲模式說清楚了,那節點的快速檢索也就清楚了。已知節點名即在類裏的屬性名的話,一步就找到了該節點,不過爲了配合下一節的異步展示,我的檢索就沒有這麼簡單了。試想在幾千或者幾萬個節點裏要快速地找出符合條件的幾十個節點,這一步的耗時可想而知了,首先就得排除for循環法,for循環的效率在數組模式裏大家就看到了,這一步操作特別在總節點數多的情況下就能讓使用者等瘋掉,顯然得另想辦法。於是我想到了正則表達式裏的字符串模式匹配match(),我將所有的節點名(即類的屬性名)join()成一個大字符串,然後再用正則表達式匹配,一步就找出了想要的節點了。經測試,在三萬節點裏找30個節點對象耗時小於0.1秒!
  • 異步展示 再來講一下節點字符串被解析之後轉化成HTML元素的這一步操作。上面已經有過一個計算,表達1000節點的樹的HTML字符量就有300K-600K之多,且這一步只能一個節點一個節點慢慢地生成,沒有什麼取巧的辦法,想快點也只能是減小單個節點的HTML元素量罷,不過最快也得1-3秒每千節點呀,這也是沒有法子的事,誰叫DOM的效率不高呢!總得想個什麼法子吧,否則象5000節點量的樹讓使用者等上個半分鐘一分鐘的,誰也受不了呀!因此我想出異步展示這招:頁面加載時並不立即生成所有節點的HTML元素,而是用戶展開多少節點就生成多少節點,節點的生成發生在用戶展開這個節點的時候。使用者在頁面打開的時候並不會立即把所有的節點都一次全部展開,而是一級級地往下展開的,就象你查看windows註冊表一樣,想看哪級纔會去展開哪一級,這樣我就只需要在你展開那級的時候把這一級的節點轉化成HTML即可,每次轉化的節點只有幾十分甚至只有幾個而已,消除了使用者的等待時間。經過上面這幾個環節,終於一棵實用性,效率,擴展性俱佳的WEB TreeView出世了。
  • 採用文字豎線 每個瀏覽器隨着使用客戶的不同,而總會產生不同的設置,其中有一項就是客戶端設置每次訪問都檢查網頁新版本,即客戶端不緩存。這個設置對一般的應用來說問題不會很大,但是對於使用圖片作爲豎線的樹來說隨着節點總數的增多,圖片的使用量也就跟着巨量增加,可能會使用幾千甚至幾萬個小圖片,每張圖片是都很小,但量一大的話,將會嚴重影響樹的快速展示,因此針對這種情況就得換一種模式來展現了,那就是文字豎線,用文字加樣式就可以解決這個問題。我加了一個變量:MzTreeView.wordLine(布爾型,默認值爲false),當網速過慢或者沒有使用本地緩存時這個屬性會自動設置成 true 而以文字代替圖片完成豎線(注:Opera 瀏覽器不支持文字豎線模式),當然你也可以一開始就強行設置值爲 true,這樣樹就會始終用文字豎線了。
屬性 MzTreeView 類的一些屬性:
屬性名 類型 屬性的具體說明
MzTreeView.nodes 集合 服務器端給樹指定數據源時數據存放的對象,具體存放格式如:
MzTreeViewHandle.nodes["parentId_nodeId"] = "text: nodeText; icon: nodeIcon; url: nodeURL; ...";
MzTreeView.url 地址字符串 可讀寫,樹缺省的URL,默認值是 #
MzTreeView.target 目標框架名 可讀寫,樹缺省的鏈接target,默認值是 _self
MzTreeView.name 字符 只讀,樹的實例名,同樹實例化時作爲參數傳入(大小寫敏感):
var Tree = new MzTreeView("Tree");
MzTreeView.currentNode 樹節點 只讀,樹當前得到焦點的節點對象
MzTreeView.icons 集合 樹所使用的所有圖標存放
MzTreeView.iconsExpand 集合 樹裏展開狀態的圖標存放
MzTreeView.colors 集合 樹裏使用到的幾個顏色存放

MzTreeView 在客戶端的節點所擁有的屬性:
屬性名 屬性的具體說明
node.id 數字文本,節點的ID
node.parentId 數字文本,節點對應的父節點ID
node.text 文本,節點的顯示文本
node.hint 文本,節點的註釋說明
node.icon 文本,節點對應的圖標
node.path 文本,節點在樹裏的絕對路徑:0_1_10_34
node.url 文本,該節點的 URL
node.target 文本,該節點鏈接的目標框架名
node.data 文本,該節點所掛載的數據
node.method 文本,該節點的點擊對應處理語句
node.parentNode 對象,節點的父節點對象
node.childNodes 數組,包含節點下所有子節點的數組
node.sourceIndex 文本,服務器給予的數據裏對象的 parentId_nodeId 的組合字符串
node.hasChild 布爾值,指該節點是否有子節點
node.isLoad 布爾值,本節點的子節點數據是否已經在客戶端初始化
node.isExpand 布爾值,節點的展開狀態
方法 MzTreeView 類的一些方法:
方法名 方法的具體說明
MzTreeView.toString() 類的默認初始運行
MzTreeView.buildNode(id) 將該節點的所有下級子節點轉換成 HTML 並在網頁上體現出來
MzTreeView.nodeToHTML(node, AtEnd) 將 node 轉換成 HTML
MzTreeView.load(id) 從數據源里加載當前節點下的所有子節點
MzTreeView.nodeInit(sourceIndex, parentId) 節點的信息初始,從數據源到客戶端完整節點的轉化
MzTreeView.focus(id) 聚集到某個節點上
MzTreeView.expand(id[, sureExpand]) 展開節點(包含下級子節點數據的加載初始化)
MzTreeView.setIconPath(path) 給節點圖片設置正確的路徑
MzTreeView.nodeClick(id) 節點鏈接點擊時同時被觸發的點擊事件處理方法
MzTreeView.upperNode() 跳轉到當前聚集節點的父級節點
MzTreeView.lowerNode() 跳轉到當前聚集節點的子級節點
MzTreeView.pervNode() 跳轉到當前聚集節點的上一節點
MzTreeView.nextNode() 跳轉到當前聚集節點的下一節點
MzTreeView.expandAll() 展開所有的樹點,在總節點量大於500時這步操作將會比較耗時
示例
<script language="JavaScript"
  src="http://www.meizz.com/Web/Plugs/MzTreeView10.js"></script>
<base href="http://www.meizz.com/Web/">
<style>
A.MzTreeview
{
  font-size: 9pt;
  padding-left: 3px;
}
</style>
<script language="JavaScript">
  var tree = new MzTreeView("tree");

  tree.icons["property"] = "property.gif";
  tree.icons["css"] = "collection.gif";
  tree.icons["book"]  = "book.gif";
  tree.iconsExpand["book"] = "bookopen.gif"; //展開時對應的圖片

  tree.setIconPath("http://www.meizz.com/Icons/TreeView/"); //可用相對路徑

  tree.nodes["0_1"] = "text:WEB 編程";
  tree.nodes["1_100"] = "text:代碼示例; data:id=100"; 
  tree.nodes["1_200"] = "text:梅花雪腳本控件集; data:id=200";
  tree.nodes["1_310"] = "text:CSS; icon:css; data:id=310"; 
  tree.nodes["1_320"] = "text:DHTML; data:id=320"; 
  tree.nodes["1_300"] = "text:HTML; data:id=300"; 
  tree.nodes["1_400"] = "text:JavaScript; icon:book; data:id=400";
  tree.nodes["320_322"] = "text:屬性; icon: property; data:id=322"; 
  tree.nodes["320_323"] = "text:方法; data:id=323"; 
  tree.nodes["320_324"] = "text:事件; icon:event; data:id=324"; 
  tree.nodes["320_325"] = "text:集合; data:id=325"; 
  tree.nodes["400_407"] = "text:對象; data:id=407"; 
  tree.nodes["400_406"] = "text:方法; data:id=406"; 
  tree.nodes["400_408"] = "text:運算符; data:id=408"; 
  tree.nodes["400_409"] = "text:屬性; data:id=409"; 
  tree.nodes["407_1140"] = "text:Date; url:Article.asp; data:id=140";
  tree.nodes["406_1127"] = "text:toString; url:Article.asp; data:id=127";
  tree.nodes["408_1239"] = "text:||; url:Article.asp; data:id=239";
  tree.nodes["409_1163"] = "text:E;  url:Article.asp; data:id=163";

  tree.setURL("Catalog.asp");
  tree.setTarget("MzMain");
  document.write(tree.toString());    //亦可用 obj.innerHTML = tree.toString();
</script>

//MzTreeView1.0網頁樹類, 在實例化的時候請把實例名作參數傳遞進來
function MzTreeView(Tname)
{
if(typeof(Tname) != "string" || Tname == "")
throw(new Error(-1, '創建類實例的時候請把類實例的引用變量名傳遞進來!'));

//【property】
this.url = "#";
this.target = "_self";
this.name = Tname;
this.wordLine = false;
this.currentNode = null;
this.useArrow = true;
this.nodes = {};
this.node = {};
this.names = "";
this._d = "x0f";
this.index = 0;
this.divider = "_";
this.node["0"] =
{
"id": "0",
"path": "0",
"isLoad": false,
"childNodes": [],
"childAppend": "",
"sourceIndex": "0"
};

this.colors =
{
"highLight" : "#0A246A",
"highLightText" : "#FFFFFF",
"mouseOverBgColor" : "#D4D0C8"
};
this.icons = {
L0 : 'L0.gif', //┏
L1 : 'L1.gif', //┣
L2 : 'L2.gif', //┗
L3 : 'L3.gif', //━
L4 : 'L4.gif', //┃
PM0 : 'P0.gif', //+┏
PM1 : 'P1.gif', //+┣
PM2 : 'P2.gif', //+┗
PM3 : 'P3.gif', //+━
empty : 'L5.gif', //空白圖
root : 'root.gif', //缺省的根節點圖標
folder : 'folder.gif', //缺省的文件夾圖標
file : 'file.gif', //缺省的文件圖標
exit : 'exit.gif'
};
this.iconsExpand = { //存放節點圖片在展開時的對應圖片
PM0 : 'M0.gif', //-┏
PM1 : 'M1.gif', //-┣
PM2 : 'M2.gif', //-┗
PM3 : 'M3.gif', //-━
folder : 'folderopen.gif',

exit : 'exit.gif'
};

//擴展 document.getElementById(id) 多瀏覽器兼容性
//id 要查找的對象 id
this.getElementById = function(id)
{
if (typeof(id) != "string" || id == "") return null;
if (document.getElementById) return document.getElementById(id);
if (document.all) return document.all(id);
try {return eval(id);} catch(e){ return null;}
}

//MzTreeView 初始化入口函數
this.toString = function()
{
this.browserCheck();
this.dataFormat();
this.setStyle();
this.load("0");
var rootCN = this.node["0"].childNodes;
var str = "<A id='"+ this.name +"_RootLink' href='#' style='DISPLAY: none'></A>";

if(rootCN.length>0)
{
this.node["0"].hasChild = true;
for(var i=0; i<rootCN.length; i++)
str += this.nodeToHTML(rootCN[i], i==rootCN.length-1);
setTimeout(this.name +".expand('"+ rootCN[0].id +"', true); "+
this.name +".focusClientNode('"+ rootCN[0].id +"'); "+ this.name +".atRootIsEmpty()",10);
}

if (this.useArrow) //使用方向鍵控制跳轉到上級下級父級子級節點
{
if (document.attachEvent)
document.attachEvent("onkeydown", this.onkeydown);
else if (document.addEventListener)
document.addEventListener('keydown', this.onkeydown, false);
}
return "<DIV class='MzTreeView' "+
"onclick='"+ this.name +".clickHandle(event)' "+
"ondblclick='"+ this.name +".dblClickHandle(event)' "+
">"+ str +"</DIV>";
};

this.onkeydown= function(e)
{
e = window.event || e; var key = e.keyCode || e.which;
switch(key)
{
case 37 : eval(Tname).upperNode(); break; //Arrow left, shrink child node
case 38 : eval(Tname).pervNode(); break; //Arrow up
case 39 : eval(Tname).lowerNode(); break; //Arrow right, expand child node
case 40 : eval(Tname).nextNode(); break; //Arrow down
}
};
}

//瀏覽器類型及版本檢測
MzTreeView.prototype.browserCheck = function()
{
var ua = window.navigator.userAgent.toLowerCase(), bname;
if(/msie/i.test(ua))
{
this.navigator = /opera/i.test(ua) ? "opera" : "";
if(!this.navigator) this.navigator = "msie";
}
else if(/gecko/i.test(ua))
{
var vendor = window.navigator.vendor.toLowerCase();
if(vendor == "firefox") this.navigator = "firefox";
else if(vendor == "netscape") this.navigator = "netscape";
else if(vendor == "") this.navigator = "mozilla";
}
else this.navigator = "msie";
if(window.opera) this.wordLine = false;
};

//給 TreeView 樹加上樣式設置
MzTreeView.prototype.setStyle = function()
{
/*
width: 16px;
height: 16px;
width: 20px;
height: 20px;
*/
var style = "<style>"+
"DIV.MzTreeView DIV IMG{border: 0px solid #FFFFFF;}"+
"DIV.MzTreeView DIV SPAN IMG{border: 0px solid #FFFFFF;}";
if(this.wordLine)
{
style +="
DIV.MzTreeView DIV
{
height: 20px;"+
(this.navigator=="firefox" ? "line-height: 20px;" : "" ) +
(this.navigator=="netscape" ? "" : "overflow: hidden;" ) +"
}
DIV.MzTreeView DIV SPAN
{
vertical-align: middle; font-size: 21px; height: 20px; color: #D4D0C8; cursor: default;
}
DIV.MzTreeView DIV SPAN.pm
{
width: "+ (this.navigator=="msie"||this.navigator=="opera" ? "11" : "9") +"px;
height: "+ (this.navigator=="netscape"?"9":(this.navigator=="firefox"?"10":"11")) +"px;
font-size: 7pt;
overflow: hidden;
margin-left: -16px;
margin-right: 5px;
color: #000080;
vertical-align: middle;
border: 1px solid #D4D0C8;
cursor: "+ (this.navigator=="msie" ? "hand" : "pointer") +";
padding: 0 2px 0 2px;
text-align: center;
background-color: #F0F0F0;
}";
}
style += "</style>";
document.write(style);
};

//當根節點爲空的時候做的處理
MzTreeView.prototype.atRootIsEmpty = function()
{
var RCN = this.node["0"].childNodes;
for(var i=0; i<RCN.length; i++)
{
if(!RCN[i].isLoad) this.expand(RCN[i].id);
if (RCN[i].text=="")
{
var node = RCN[i].childNodes[0], HCN = node.hasChild;
if(this.wordLine)
{
var span = this.getElementById(this.name +"_tree_"+ node.id);
span = span.childNodes[0].childNodes[0].childNodes[0];
span.innerHTML = RCN[i].childNodes.length>1 ? "┌" : "─";
}
else
{
node.iconExpand = RCN[i].childNodes.length>1 ? HCN ? "PM0" : "L0" : HCN ? "PM3" : "L3"
this.getElementById(this.name +"_expand_"+ node.id).src = this.icons[node.iconExpand].src;
}
}
}
};
//初始化數據源裏的數據以便後面的快速檢索
MzTreeView.prototype.dataFormat = function()
{
var a = new Array();
for (var id in this.nodes) a[a.length] = id;
this.names = a.join(this._d + this._d);
this.totalNode = a.length; a = null;
};

//在數據源檢索所需的數據節點
//id 客戶端節點對應的id
MzTreeView.prototype.load = function(id)
{
var node = this.node[id], d = this.divider, _d = this._d;
var sid = node.sourceIndex.substr(node.sourceIndex.indexOf(d) + d.length);
var reg = new RegExp("(^|"+_d+")"+ sid +d+"[^"+_d+d +"]+("+_d+"|$)", "g");
var cns = this.names.match(reg), tcn = this.node[id].childNodes; if (cns){
reg = new RegExp(_d, "g"); for (var i=0; i<cns.length; i++)
tcn[tcn.length] = this.nodeInit(cns[i].replace(reg, ""), id); }
node.isLoad = true;
};

//初始化節點信息, 根據 this.nodes 數據源生成節點的詳細信息
//sourceIndex 數據源中的父子節點組合的字符串 0_1
//parentId 當前樹節點在客戶端的父節點的 id
MzTreeView.prototype.nodeInit = function(sourceIndex, parentId)
{
this.index++;
var source= this.nodes[sourceIndex], d = this.divider;
var text = this.getAttribute(source, "text");
var hint = this.getAttribute(source, "hint");
var sid = sourceIndex.substr(sourceIndex.indexOf(d) + d.length);
this.node[this.index] =
{
"id" : this.index,
"text" : text,
"hint" : hint ? hint : text,
"icon" : this.getAttribute(source, "icon"),
"path" : this.node[parentId].path + d + this.index,
"isLoad": false,
"isExpand": false,
"parentId": parentId,
"parentNode": this.node[parentId],
"sourceIndex" : sourceIndex,
"childAppend" : ""
};
this.nodes[sourceIndex] = "index:"+ this.index +";"+ source;
this.node[this.index].hasChild = this.names.indexOf(this._d + sid + d)>-1;
if(this.node[this.index].hasChild) this.node[this.index].childNodes = [];
return this.node[this.index];
};

//從XML格式字符串裏提取信息
//source 數據源裏的節點信息字符串(以後可以擴展對XML的支持)
//name 要提取的屬性名
MzTreeView.prototype.getAttribute = function(source, name)
{
var reg = new RegExp("(^|;|s)"+ name +"s*:s*([^;]*)(s|;|$)", "i");
if (reg.test(source)) return RegExp.$2.replace(/[x0f]/g, ";"); return "";
};

//根據節點的詳細信息生成HTML
//node 樹在客戶端的節點對象
//AtEnd 布爾值 當前要轉換的這個節點是否爲父節點的子節點集中的最後一項
MzTreeView.prototype.nodeToHTML = function(node, AtEnd)
{
var source = this.nodes[node.sourceIndex];
var target = this.getAttribute(source, "target");
var data = this.getAttribute(source, "data");
var url = this.getAttribute(source, "url");
if(!url) url = this.url;
if(data) url += (url.indexOf("?")==-1?"?":"&") + data;
if(!target) target = this.target;

var id = node.id;
var HCN = node.hasChild, isRoot = node.parentId=="0";
if(isRoot && node.icon=="") node.icon = "root";
if(node.icon=="" || typeof(this.icons[node.icon])=="undefined")
node.icon = HCN ? "folder" : "file";
node.iconExpand = AtEnd ? "└" : "├";

var HTML = "<DIV noWrap='True'><NOBR>";
if(!isRoot)
{
node.childAppend = node.parentNode.childAppend + (AtEnd ? " " : "│");
if(this.wordLine)
{
HTML += "<SPAN>"+ node.parentNode.childAppend + (AtEnd ? "└" : "├") +"</SPAN>";
if(HCN) HTML += "<SPAN class='pm' id='"+ this.name +"_expand_"+ id +"'>+</SPAN>";
}
else
{
node.iconExpand = HCN ? AtEnd ? "PM2" : "PM1" : AtEnd ? "L2" : "L1";
HTML += "<SPAN>"+ this.word2image(node.parentNode.childAppend) +"<IMG "+
"align='absmiddle' id='"+ this.name +"_expand_"+ id +"' "+
"src='"+ this.icons[node.iconExpand].src +"' style='cursor: "+ (!node.hasChild ? "":
(this.navigator=="msie"||this.navigator=="opera"? "hand" : "pointer")) +"'></SPAN>";
}
}
HTML += "<IMG "+
"align='absMiddle' "+
"id='"+ this.name +"_icon_"+ id +"' "+
"src='"+ this.icons[node.icon].src +"'><A "+
"class='MzTreeview' hideFocus "+
"id='"+ this.name +"_link_"+ id +"' "+
"href='"+ url +"' "+
"target='"+ target +"' "+
"title='"+ node.hint +"' "+
"onfocus=""+ this.name +".focusLink('"+ id +"')" "+
"onclick="return "+ this.name +".nodeClick('"+ id +"')">"+ node.text +
"</A></NOBR></DIV>";
if(isRoot && node.text=="") HTML = "";

HTML = "rn<SPAN id='"+ this.name +"_tree_"+ id +"'>"+ HTML
HTML +="<SPAN style='DISPLAY: none'></SPAN></SPAN>";
return HTML;
};

//在使用圖片的時候對 node.childAppend 的轉換
MzTreeView.prototype.word2image = function(word)
{
var str = "";
for(var i=0; i<word.length; i++)
{
var img = "";
switch (word.charAt(i))
{
case "│" : img = "L4"; break;
case "└" : img = "L2"; break;
case " " : img = "empty"; break;
case "├" : img = "L1"; break;
case "─" : img = "L3"; break;
case "┌" : img = "L0"; break;
}
if(img!="")
str += "<IMG align='absMiddle' src='"+ this.icons[img].src +"' height='20'>";
}
return str;
}


//將某個節點下的所有子節點轉化成詳細的<HTML>元素表達
//id 樹的客戶端節點 id
MzTreeView.prototype.buildNode = function(id)
{
if(this.node[id].hasChild)
{
var tcn = this.node[id].childNodes, str = "";
for (var i=0; i<tcn.length; i++)
str += this.nodeToHTML(tcn[i], i==tcn.length-1);
var temp = this.getElementById(this.name +"_tree_"+ id).childNodes;
temp[temp.length-1].innerHTML = str;
}
};

//聚集到客戶端生成的某個節點上
//id 客戶端樹節點的id
MzTreeView.prototype.focusClientNode = function(id)
{
if(!this.currentNode) this.currentNode=this.node["0"];

var a = this.getElementById(this.name +"_link_"+ id); if(a){ a.focus();
var link = this.getElementById(this.name +"_link_"+ this.currentNode.id);
if(link)with(link.style){color=""; backgroundColor="";}
with(a.style){color = this.colors.highLightText;
backgroundColor = this.colors.highLight;}
this.currentNode= this.node[id];}
};

//焦點聚集到樹裏的節點鏈接時的處理
//id 客戶端節點 id
MzTreeView.prototype.focusLink= function(id)
{
if(this.currentNode && this.currentNode.id==id) return;
this.focusClientNode(id);
};

//點擊展開樹節點的對應方法
MzTreeView.prototype.expand = function(id, sureExpand)
{
var node = this.node[id];
if (sureExpand && node.isExpand) return;
if (!node.hasChild) return;
var area = this.getElementById(this.name +"_tree_"+ id);
if (area) area = area.childNodes[area.childNodes.length-1];
if (area)
{
var icon = this.icons[node.icon];
var iconE = this.iconsExpand[node.icon];
var Bool = node.isExpand = sureExpand || area.style.display == "none";
var img = this.getElementById(this.name +"_icon_"+ id);
if (img) img.src = !Bool ? icon.src :typeof(iconE)=="undefined" ? icon.src : iconE.src;
var exp = this.icons[node.iconExpand];
var expE = this.iconsExpand[node.iconExpand];
var expand= this.getElementById(this.name +"_expand_"+ id);
if (expand)
{
if(this.wordLine) expand.innerHTML = !Bool ? "+" : "-";
else expand.src = !Bool ? exp.src : typeof(expE) =="undefined" ? exp.src : expE.src;
}
if(!Bool && this.currentNode.path.indexOf(node.path)==0 && this.currentNode.id!=id)
{
try{this.getElementById(this.name +"_link_"+ id).click();}
catch(e){this.focusClientNode(id);}
}
area.style.display = !Bool ? "none" : "block";//(this.navigator=="netscape" ? "block" : "");
if(!node.isLoad)
{
this.load(id);
if(node.id=="0") return;
//當子節點過多時, 給用戶一個正在加載的提示語句
if(node.hasChild && node.childNodes.length>200)
{
setTimeout(this.name +".buildNode('"+ id +"')", 1);
var temp = this.getElementById(this.name +"_tree_"+ id).childNodes;
temp[temp.length-1].innerHTML = "<DIV noWrap><NOBR><SPAN>"+ (this.wordLine ?
node.childAppend +"└" : this.word2image(node.childAppend +"└")) +"</SPAN>"+
"<IMG border='0' height='16' align='absmiddle' src='"+this.icons["file"].src+"'>"+
"<A style='background-Color: "+ this.colors.highLight +"; color: "+
this.colors.highLightText +"; font-size: 9pt'>請稍候...</A></NOBR></DIV>";
}
else this.buildNode(id);
}
}
};

//節點鏈接單擊事件處理方法
//id 客戶端樹節點的 id
MzTreeView.prototype.nodeClick = function(id)
{
var source = this.nodes[this.node[id].sourceIndex];
eval(this.getAttribute(source, "method"));
return !(!this.getAttribute(source, "url") && this.url=="#");
};

//爲配合系統初始聚集某節點而寫的函數, 得到某節點在數據源裏的路徑
//sourceId 數據源裏的節點 id
MzTreeView.prototype.getPath= function(sourceId)
{

Array.prototype.indexOf = function(item)
{
for(var i=0; i<this.length; i++)
{
if(this[i]==item) return i;
}
return -1;
};
var _d = this._d, d = this.divider;
var A = new Array(), id=sourceId; A[0] = id;
while(id!="0" && id!="")
{
var str = "(^|"+_d+")([^"+_d+d+"]+"+d+ id +")("+_d+"|$)";
if (new RegExp(str).test(this.names))
{
id = RegExp.$2.substring(0, RegExp.$2.indexOf(d));
if(A.indexOf(id)>-1) break;
A[A.length] = id;
}
else break;
}
return A.reverse();
};

//在源代碼裏指定 MzTreeView 初始聚集到某個節點
//sourceId 節點在數據源裏的 id
MzTreeView.prototype.focus = function(sourceId, defer)
{
if (!defer)
{
setTimeout(this.name +".focus('"+ sourceId +"', true)", 100);
return;
}
var path = this.getPath(sourceId);
if(path[0]!="0")
{
alert("節點 "+ sourceId +" 沒有正確的掛靠有效樹節點上!rn"+
"節點 id 序列 = "+ path.join(this.divider));
return;
}
var root = this.node["0"], len = path.length;
for(var i=1; i<len; i++)
{
if(root.hasChild)
{
var sourceIndex= path[i-1] + this.divider + path[i];
for (var k=0; k<root.childNodes.length; k++)
{
if (root.childNodes[k].sourceIndex == sourceIndex)
{
root = root.childNodes[k];
if(i<len - 1) this.expand(root.id, true);
else this.focusClientNode(root.id);
break;
}
}
}
}
};

//樹的單擊事件處理函數
MzTreeView.prototype.clickHandle = function(e)
{
e = window.event || e; e = e.srcElement || e.target;
//alert(e.tagName)
switch(e.tagName)
{
case "IMG" :
if(e.id)
{
if(e.id.indexOf(this.name +"_icon_")==0)
this.focusClientNode(e.id.substr(e.id.lastIndexOf("_") + 1));
else if (e.id.indexOf(this.name +"_expand_")==0)
this.expand(e.id.substr(e.id.lastIndexOf("_") + 1));
}
break;
case "A" :
if(e.id) this.focusClientNode(e.id.substr(e.id.lastIndexOf("_") + 1));
break;
case "SPAN" :
if(e.className=="pm")
this.expand(e.id.substr(e.id.lastIndexOf("_") + 1));
break;
default :
if(this.navigator=="netscape") e = e.parentNode;
if(e.tagName=="SPAN" && e.className=="pm")
this.expand(e.id.substr(e.id.lastIndexOf("_") + 1));
break;
}
};

//MzTreeView 雙擊事件的處理函數
MzTreeView.prototype.dblClickHandle = function(e)
{
e = window.event || e; e = e.srcElement || e.target;
if((e.tagName=="A" || e.tagName=="IMG")&& e.id)
{
var id = e.id.substr(e.id.lastIndexOf("_") + 1);
if(this.node[id].hasChild) this.expand(id);
}
};

//回到樹當前節點的父層節點
MzTreeView.prototype.upperNode = function()
{
if(!this.currentNode) return;
if(this.currentNode.id=="0" || this.currentNode.parentId=="0") return;
if (this.currentNode.hasChild && this.currentNode.isExpand)
this.expand(this.currentNode.id, false);
else this.focusClientNode(this.currentNode.parentId);
};

//展開當前節點並
MzTreeView.prototype.lowerNode = function()
{
if (!this.currentNode) this.currentNode = this.node["0"];
if (this.currentNode.hasChild)
{
if (this.currentNode.isExpand)
this.focusClientNode(this.currentNode.childNodes[0].id);
else this.expand(this.currentNode.id, true);
}
}

//聚集到樹當前節點的上一節點
MzTreeView.prototype.pervNode = function()
{
if(!this.currentNode) return; var e = this.currentNode;
if(e.id=="0") return; var a = this.node[e.parentId].childNodes;
for(var i=0; i<a.length; i++){if(a[i].id==e.id){if(i>0){e=a[i-1];
while(e.hasChild){this.expand(e.id, true);
e = e.childNodes[e.childNodes.length - 1];}
this.focusClientNode(e.id); return;} else {
this.focusClientNode(e.parentId); return;}}}
};

//聚集到樹當前節點的下一節點
MzTreeView.prototype.nextNode = function()
{
var e = this.currentNode; if(!e) e = this.node["0"];
if (e.hasChild){this.expand(e.id, true);
this.focusClientNode(e.childNodes[0].id); return;}
while(typeof(e.parentId)!="undefined"){
var a = this.node[e.parentId].childNodes;
for(var i=0; i<a.length; i++){ if(a[i].id==e.id){
if(i<a.length-1){this.focusClientNode(a[i+1].id); return;}
else e = this.node[e.parentId];}}}
};

//展開樹的所有節點
MzTreeView.prototype.expandAll = function()
{
if(this.totalNode>500) if(
confirm("您是否要停止展開全部節點?rnrn節點過多!展開很耗時")) return;
if(this.node["0"].childNodes.length==0) return;
var e = this.node["0"].childNodes[0];
var isdo = t = false;
while(e.id != "0")
{
var p = this.node[e.parentId].childNodes, pn = p.length;
if(p[pn-1].id==e.id && (isdo || !e.hasChild)){e=this.node[e.parentId]; isdo = true;}
else
{
if(e.hasChild && !isdo)
{
this.expand(e.id, true), t = false;
for(var i=0; i<e.childNodes.length; i++)
{
if(e.childNodes[i].hasChild){e = e.childNodes[i]; t = true; break;}
}
if(!t) isdo = true;
}
else
{
isdo = false;
for(var i=0; i<pn; i++)
{
if(p[i].id==e.id) {e = p[i+1]; break;}
}
}
}
}
};

//本樹將要用動的圖片的字義及預載函數
//path 圖片存放的路徑名
MzTreeView.prototype.setIconPath = function(path)
{
var k = 0, d = new Date().getTime();
for(var i in this.icons)
{
var tmp = this.icons[i];
this.icons[i] = new Image();
this.icons[i].src = path + tmp;
if(k==9 && (new Date().getTime()-d)>20)
this.wordLine = true; k++;
}
for(var i in this.iconsExpand)
{
var tmp = this.iconsExpand[i];
this.iconsExpand[i]=new Image();
this.iconsExpand[i].src = path + tmp;
}
};

//設置樹的默認鏈接
//url 默認鏈接 若不設置, 其初始值爲 #
MzTreeView.prototype.setURL = function(url){this.url = url;};

//設置樹的默認的目標框架名 target
//target 目標框架名 若不設置, 其初始值爲 _self
MzTreeView.prototype.setTarget = function(target){this.target = target;};
// -->
一個簡單的示例:

<script language="JavaScript"
src="http://www.meizz.com/Web/Plugs/MzTreeView10.js"></script>
<base href="http://www.meizz.com/Web/">
<style>
A.MzTreeview
{
font-size: 9pt;
padding-left: 3px;
}
</style>
<script language="JavaScript">
var tree = new MzTreeView("tree");

tree.icons["property"] = "property.gif";
tree.icons["css"] = "collection.gif";
tree.icons["book"] = "book.gif";
tree.iconsExpand["book"] = "bookopen.gif"; //展開時對應的圖片

tree.setIconPath("http://www.meizz.com/Icons/TreeView/"); //可用相對路徑

tree.nodes["0_1"] = "text:WEB 編程";
tree.nodes["1_100"] = "text:代碼示例; data:id=100";
tree.nodes["1_200"] = "text:梅花雪腳本控件集; data:id=200";
tree.nodes["1_310"] = "text:CSS; icon:css; data:id=310";
tree.nodes["1_320"] = "text:DHTML; data:id=320";
tree.nodes["1_300"] = "text:HTML; data:id=300";
tree.nodes["1_400"] = "text:JavaScript; icon:book; data:id=400";
tree.nodes["320_322"] = "text:屬性; icon: property; data:id=322";
tree.nodes["320_323"] = "text:方法; data:id=323";
tree.nodes["320_324"] = "text:事件; icon:event; data:id=324";
tree.nodes["320_325"] = "text:集合; data:id=325";
tree.nodes["400_407"] = "text:對象; data:id=407";
tree.nodes["400_406"] = "text:方法; data:id=406";
tree.nodes["400_408"] = "text:運算符; data:id=408";
tree.nodes["400_409"] = "text:屬性; data:id=409";
tree.nodes["407_1140"] = "text:Date; url:Article.asp; data:id=140";
tree.nodes["406_1127"] = "text:toString; url:Article.asp; data:id=127";
tree.nodes["408_1239"] = "text:||; url:Article.asp; data:id=239";
tree.nodes["409_1163"] = "text:E; url:Article.asp; data:id=163";

tree.setURL("Catalog.asp");
tree.setTarget("MzMain");
document.write(tree.toString()); //亦可用 obj.innerHTML = tree.toString();
</script> 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章