可爱又可恨的梅花雪

    最近的一个项目中要对页面上的Filter中的菜单树进行改造,原因是之前所采用的TreeView控件树在页面初次加载的时候耗时过长,效率低下(想必用过微软TreeView树控件的用户都有过这样的感受)。改造的基本想法是用客户端树来代替从服务器端加载的TreeView树,客户端构造树只能用javascript了,要体现树的UI和可操控性,就必须提供动态的HTML用事件来进行加载(DHTML部分),这是一件非常繁琐的事情,于是我就想到了采用网络上非常著名的“梅花雪树控件”,不过期间遇到的问题很多,梅花雪的作者在需求方面考虑的问题还是不够全面,使得我在改造梅花雪树的过程中遇到了很多麻烦。

    首先说明一下,这个项目是建立在Microsoft最新的企业级Portal Sharepoint 2007平台上,并且根据客户的要求,所有的Custom Code只能写在客户端,也就是说整个项目不能在服务器上部署dll(不过前面提到的那个Filter的TreeView是个例外,那个是采用C# Webpart编写的),UI的部分截图在下面。

3-13-2009 11-27-37 AM 在选择Filter之前

3-13-2009 11-31-09 AM

在选择Filter中的Hierarchy之后

     Hierarchy的下拉面板中有三棵树,通过RadioButton进行切换。

    梅花雪网站上只能找到1.0版本的树,经过改良后的2.0版本效果不错,但是在梅花雪的网站上好像还没有提供下载,我在Google上搜索了一下,也还是找到很多可供下载的地址。

3-13-2009 12-35-21 PM

梅花雪树1.0

3-13-2009 12-39-17 PM

梅花雪树2.0

    网络上有很多版本的梅花雪树,不过形态都大同小异,我所需要的是带复选框的树,并且绑定数据简单,最好不要通过递归绑定数据(因为当节点数过多时,通过递归绑定数据效率太低,在浏览器中加载树时通常都会“死机”)。其实需求很简单,于是我选择了上面的梅花雪2.0树。这个版本确实不错,除了可以通过不同的方式绑定数据外,还支持动态加载数据,也就是说在树的节点展开时才去动态加载节点。只要不在页面初始加载的时候全部展开树的节点,页面响应的时间会很短。我按照示例中的代码进行了测试,编写js脚本解析数据并绑定到树上,然后运行看效果,还不错,一切看起来都很正常,于是我很兴奋,看来大功快要告成!

    进一步测试并添加相应的需求,问题马上就来了:

    首先是我如何获取到已选择的节点?后来才知道这个其实是很容易的。设好debugger,调试js,跟一下node对象下有哪些属性或方法,找到一个bool属性checked,就是它了,通过遍历MzTreeView下的nodes,判断节点上的checked属性,然后就可以直接取到节点的id或text(这两个属性也是在调试js的过程中发现的,这比直接去读代码要来得方便)。

3-13-2009 3-16-58 PM

     好,既然可以获取到已选择的节点,那么就允许设置哪些节点被选择了,如何做呢?从上面的过程来看,我们只需要设置这些节点的checked属性为true就行了,太容易了!那么果然如此吗?前面我说了,梅花雪2.0的树是动态加载节点的,也就是说,节点所在的分支如果没有打开,你是获取不到节点对象的,于是也就不能设置节点的checked属性了。这就是我所遇到的其中一个问题,看来梅花雪树的作者忽略了这个问题!问题的关键是我们首先要将那些你想设置属性的节点所在的分支打开,但因为树是层次结构,树可能会有很多层,我们需要逐级将这些分支打开,只要分支打开了,就能获取到节点对象,于是也就可以设置属性的值了。这就要求你在绑定树的数据时将这些节点的父节点从下向上依次保存起来,在设置时先按从上到下的顺序打开分支,然后遍历分支下的节点,最后设置这些节点的属性值。下面是我实现这一过程的部分代码。

  1 var hashArea = new Hashtable();
  2 var hashRegion = new Hashtable();
  3 var hashCountry = new Hashtable();
  4 var hashCompany = new Hashtable();
  5 var hashSelectedCompany = new Hashtable();
  6 var hashSelectedNodes = new Hashtable();
  7 
  8 //该function用于解析绑定MzTreeView的数据
  9 function FillData(arrAreaRegion, arrRegionCountry, arrCountryCompany, arrSelectedCompany) {
10     hashArea.clear();
11     hashRegion.clear();
12     hashCountry.clear();
13     hashCompany.clear();
14     hashSelectedCompany.clear();
15     hashSelectedNodes.clear();
16     
17     var data = {};
18 
19     for (var i = 0; i < arrSelectedCompany.length; i++) {
20         hashSelectedCompany.add(arrSelectedCompany[i], arrSelectedCompany[i]);
21     }
22     
23     data["-1_Root"] = "text: Statuary;";
24 
25     //fill Area and Region
26     for (var i = 0; i < arrAreaRegion.length - 1; i++) {
27         if (arrAreaRegion[i] != "") {
28             arrTmp = arrAreaRegion[i].split("`");
29             if (arrTmp.length > 1) {
30                 var a = arrTmp[0].trim();
31                 var b = arrTmp[1].trim();
32                 if (!hashArea.contains(a)) {
33                     hashArea.add(a, "Root");
34                     data["Root_" + a] = "text:" + a + ";";//Area
35                 }
36                 if (!hashRegion.contains(b + "1")) {
37                     hashRegion.add(b + "1", a);
38                     data[a + "_" + b + "1"] = "text:" + b + ";";//Region
39                 }
40             }
41         }
42     }
43 
44     //fill Country
45     for (var j = 0; j < arrRegionCountry.length - 1; j++) {
46         if (arrRegionCountry[j] != "") {
47             arrTmp = arrRegionCountry[j].split("`");
48             if (arrTmp.length > 1) {
49                 var a = arrTmp[0].trim();
50                 var b = arrTmp[1].trim();
51                 if (!hashCountry.contains(b + "11")) {
52                     hashCountry.add(b + "11", a + "1");
53                     data[a + "1_" + b + "11"] = "text:" + b + ";";//Country
54                 }
55             }
56         }
57     }
58 
59     //fill Company
60     var t;
61     for (var l = 0; l < arrCountryCompany.length - 1; l++) {
62         if (arrCountryCompany[l] != "") {
63             arrTmp = arrCountryCompany[l].split("`");
64             if (arrTmp.length > 1) {
65                 t = arrTmp[1].split(";#");
66                 if (t.length > 1) {
67                     var a = arrTmp[0].trim();
68                     var b = t[1].trim();
69                     if (!hashCompany.contains(b)) {
70                         hashCompany.add(b, a);
71                         data[a + "11_" + t[0]] = "text:" + b + ";";
72                         if (hashSelectedCompany.contains(b.split("-")[0])) {
73                             data[a + "11_" + t[0]] += "checked:true;"; //CompandCode
74                             hashSelectedNodes.add(a + "11", "");
75                         }
76                     }
77                 }
78             }
79         }
80     }
81     
82     return data;
83 }
84 
85 //有选择性地打开树中的分支,只要分支打开了,该分支下设置为checked=true的节点就能自动被选择上
86 function expandNodes(a) {
87     if (hashSelectedNodes.count > hashCompany.count / 2) {
88         a.expandAll("Root");
89         return;
90     }
91     var _nodesCountry = new Hashtable();
92     var _nodesRegion = new Hashtable();
93 
94     for (var i in hashSelectedNodes._hash) {
95         if (hashCountry.contains(i)) {
96             _nodesCountry.add(hashCountry.items(i), "");
97         }
98     }
99     for (var j in _nodesCountry._hash) {
100         if (hashRegion.contains(j)) {
101             _nodesRegion.add(hashRegion.items(j), "");
102         }
103     }
104     //expand region level
105     for (var i in _nodesRegion._hash) {
106         a.expand(i);
107     }
108     //expand country level
109     for (var j in _nodesCountry._hash) {
110         a.expand(j);
111     }
112     //expand company level
113     for (var k in hashSelectedNodes._hash) {
114         a.expand(k);
115     }
116 }

    先说明一下,读者可能看得不是很明白。首先我要构造的树总共有四层,这是我预先知道的,Area-Region-Country-Company,我们要设置的节点位于叶子节点,也就是Company这一级,FillData函数中分三个for循环分别构造了这四层,在最后一层的时候我根据hashSelectedCompany中的值来判断是否要将该节点的checked设置为true,如果设置为true,就需要保存它的父节点的id到hashSelectedNodes中。在expandNodes函数中,我们需要根据这个hashSelectedNodes来找到相应的Country和Region,然后逐级打开Area、Region、Country,设置为checked=true的节点就会自动被选择上。当然,如果你初始设置时被选择的节点数目很多,这个函数执行的效率不会很高,所以我在函数开始的地方添加了一个判断,如果你所要设置的节点的数目超过所有节点数目的一半,那么我干脆就将树全部展开,而不用再去逐级打开节点了。还有一个前提条件,那就是MzTreeView在Render完后,我们要默认expand第一级,否则expandNodes函数在expand Region的时候便会报找不到对象的错误。

    上面的代码中,读者可能会问,FillData的时候是用来给data数组按照MzTreeView所要求的格式填充数据的,为什么我会在里面加上1 和11呢?这也是我在使用MzTreeView时所遇到的另外一个问题,即节点id重复怎么办

    先分析MzTreeView中用于绑定数据时的结构,我用的不一定是数字,而是文本(因为后台传递过来的数据只有这些):

1 data["-1_Root"] = "text: Statutory";
2 data["Root_APAC"] = "text: APAC";
3 data["APAC_Australia"] = "text: Australia";
4 data["Australia_Australia"] = "text: Australia";
5 
     问题是,子节点和父节点的id重复了。在Render树的时候虽然不会报错,但是生成的树节点不全,那些与父节点id相同的子节点不会被Render出来。一个好的解决办法就是在这些文本上添加额外的信息,使它们与父节点的id不重复,这就是我为什么要在Region这一层加1,而在Country这一层加11。代码中使用到了一个Hashtable的对象,这个是自己编写的方便存储数据用的,读者可以参考我之前的一篇日志《在Javascript中实现伪哈希表》

    继续测试,发现有些节点死活都没有出现在树中,为什么?调试,跟踪,再调试,再跟踪…后来终于发现问题所在,原来在我的数据源中含有小括号,又因为我是直接用节点的名称作为id来绑定数据的,MzTreeView的代码中使用了正则表达式来进行节点查找,而我后来在调试中发觉这个正则表达式没有对特殊字符进行特殊处理,例如我这里遇到的小括号(小括号在正则表达式中也是特殊字符),下面是MzTreeView中mzdata.js文件中的原始代码。

1 //public: getNode (has Builded) by sourceId
2 MzData.prototype.getNodeById = function(id)
3 {
4   if(id==this.rootId&&this.rootNode.virgin) return this.rootNode;
5   var _=this.get__(), d = this.dividerEncoding;
6   var reg=new RegExp("([^"+_+ d +"]+"+ d + id +")("+_+"|$)");
7   if(reg.test(this.indexes)){var s=RegExp.$1;
8   if(s=this.dataSource[s].getAttribute("index_"+ this.hashCode))
9     return this.nodes[s];
10   else{System._alert("The node isn't initialized!"); return null;}}
11   alert("sourceId="+ id +" is nonexistent!"); return null;
12 };

    reg.test(this.indexes)判断的结果为false,也就是说reg的正则匹配失败,原因就是id的值中含有了小括号。我的想法是在进行正则表达式之前将id值中的小括号统一替换成别的字符,然后进行正则表达式匹配,匹配完后再替换回小括号,下面是我修改之后的代码。

1 MzData.prototype.getNodeById = function(id)
2 {
3   if(id==this.rootId&&this.rootNode.virgin) return this.rootNode;
4   var _=this.get__(), d = this.dividerEncoding;
5   //----------------
6   var _id = id;
7   _id = _id.replace(/\(/g, "jackyxu").replace(/\)/g, "xujacky");
8   var _indexes = this.indexes;
9   _indexes = _indexes.replace(/\(/g, "jackyxu").replace(/\)/g, "xujacky");
10   var reg=new RegExp("([^"+_+ d +"]+"+ d + _id +")("+_+"|$)");
11   if (reg.test(_indexes)) { var s = RegExp.$1.replace(/(jackyxu)/g, "(").replace(/(xujacky)/g, ")");
12   //----------------
13   if(s=this.dataSource[s].getAttribute("index_"+ this.hashCode))
14     return this.nodes[s];
15   else{System._alert("The node isn't initialized!"); return null;}}
16   alert("sourceId="+ id +" is nonexistent!"); return null;
17 };

    因为实在不知道用什么字符来替换小括号(很多键盘上的字符都被用作正则表达式的特殊字符了),我就用jackyxu来代替“(”,用xujacky来代替“)”,正则表达式匹配后再替换回来。在mzdata.js文件中还有一个地方也需要类似修改,下面是原始代码和修改后的代码。

1 MzDataNode.prototype.loadChildNodes = function(DataNodeClass)
2 {
3   var $=this.$$caller,r=$.dividerEncoding,_=$.get__(), i, cs;
4   var tcn=this.childNodes;tcn.length=0;if(this.sourceIndex){
5   if((i=this.get("JSData"))) $.loadJsData((/^\w+\.js(\s|\?|$)/i.test(i)?$.jsDataPath:"")+i);
6   if((i=this.get("ULData"))) $.loadUlData(i, this.id);
7   if((i=this.get("XMLData")))$.loadXmlData((/^\w+\.xml(\s|\?|$)/i.test(i)?$.xmlDataPath:"")+i,this.id);}
8   var reg=new RegExp(_ + this.id + r +"[^"+ _ + r +"]+", "g"); 
9   if((cs=$.indexes.match(reg))){for(i=0;i<cs.length;i++){
10     tcn[tcn.length]=this.DTO(DataNodeClass, cs[i].substr(_.length));}}
11   this.isLoaded = true;
12 };

 

1 MzDataNode.prototype.loadChildNodes = function(DataNodeClass)
2 {
3   var $=this.$$caller,r=$.dividerEncoding,_=$.get__(), i, cs;
4   var tcn=this.childNodes;tcn.length=0;if(this.sourceIndex){
5   if((i=this.get("JSData"))) $.loadJsData((/^\w+\.js(\s|\?|$)/i.test(i)?$.jsDataPath:"")+i);
6   if((i=this.get("ULData"))) $.loadUlData(i, this.id);
7   if((i=this.get("XMLData")))$.loadXmlData((/^\w+\.xml(\s|\?|$)/i.test(i)?$.xmlDataPath:"")+i,this.id);}
8   //----------------
9   var _id = this.id;
10   _id = _id.replace(/\(/g, "jackyxu").replace(/\)/g, "xujacky");
11   var _indexes = $.indexes;
12   _indexes = _indexes.replace(/\(/g, "jackyxu").replace(/\)/g, "xujacky");
13   var reg=new RegExp(_ + _id + r +"[^"+ _ + r +"]+", "g"); 
14   if((cs=_indexes.match(reg))){for(i=0;i<cs.length;i++){
15     tcn[tcn.length]=this.DTO(DataNodeClass, cs[i].substr(_.length).replace(/(jackyxu)/g, "(").replace(/(xujacky)/g, ")"));}}
16   //----------------
17   this.isLoaded = true;
18 };

    当然,可能需要替换的字符不止是小括号,或者中括号、大括号等,但在我的项目中只会遇到使用小括号的情况,于是我也就只做了这样的修改,如果想一劳永逸,读者可以按照相似的方式专门编写一个function进行正则表达式中特殊字符的处理。经过修改后的代码可以使名称中带有小括号的的节点成功在树中显示,于是这个问题就解决了。

    接下来是不是就没有问题了呢?不然!梅花雪的示例页面中树是在页面load的时候加载上去的,没有通过按钮事件进行加载,就是说在页面上没有反复加载的过程,而在我的需求中是需要这么做的,于是我把树的加载过程放在一个按钮的事件中,这样当我想重新加载树的时候只需要点击按钮即可,不过这个时候问题就来了,每次我点击按钮重新加载树的时候根节点前面都出现了莫名其妙的空白!看看下面的截图。

3-16-2009 9-11-28 AM     Statutory是根节点,可是前面却出现了空白,似乎梅花雪在树加载的时候将它作为一个子节点处理了。我试着点了点这个空白的地方,发现是一张图片,就是子节点前面的虚线。找到问题所在就好办了,看看代码吧!根节点只会在树Render的时候显示出来,那么问题应该出现在MzTreeView.js文件的Render方法中。

1 MzTreeView.prototype.render = function()
2 {
3   function loadImg(C){for(var i in C){if("string"==typeof C[i]){
4   var a=new Image(); a.src=me.iconPath + C[i]; C[i]=a;}}} var me=this;
5   loadImg(MzTreeView.icons.expand);loadImg(MzTreeView.icons.collapse);
6   loadImg(MzTreeView.icons.line); me.firstNode=null;
7   loadCssFile(this.iconPath +"mztreeview.css", "MzTreeView_CSS");
8 
9   this.initialize(); var str="no data", i, root=this.rootNode;
10   if (root.hasChild){var a = [], c = root.childNodes; me.firstNode=c[0];
11   for(i=0;i<c.length;i++)a[i]=c[i].render(i==c.length-1);str=a.join("");}
12   setTimeout(function(){me.afterRender();}, 10);
13   return "<div class='mztreeview' id='MTV_root_"+ this.index +"' "+
14     "onclick='Instance(\""+ this.index +"\").clickHandle(event)' "+
15     "ondblclick='Instance(\""+ this.index +"\").dblClickHandle(event)' "+
16     ">"+ str +"</div>";
17 };
     然而这个方法只是在页面上加载了一些图片和样式表的引用,并画出了一个div并给出了事件句柄,没有什么特别的东西。上下随意看了一下别的部分,发现有一个MzTreeNode对象,其中也有一个Render方法,仔细看了一下,问题就出现在这里。
1 MzTreeNode.prototype.render = function(last)
2 {
3   var $=this.$$caller, s=$.dataSource[this.sourceIndex],target,data,url;
4   var icon=s.getAttribute("icon");
5   if(!(target=s.getAttribute("target")))target=$.getDefaultTarget();
6   var hint=$.showToolTip ? s.getAttribute("hint") || this.text : "";
7   if(!(url=s.getAttribute("url"))) url = $.getDefaultUrl();
8   if(data=s.getAttribute("data"))url+=(url.indexOf("?")==-1?"?":"&")+data;
9 
10   var id=this.index, s="";
11   var isRoot=this.parentNode==$.rootNode;
12   if( isRoot && $.convertRootIcon && !icon) icon = "root";
13   if(!isRoot)this.childPrefix=this.parentNode.childPrefix+(last?",ll":",l4");
14   if(!icon || typeof(MzTreeView.icons.collapse[icon])=="undefined")
15   this.icon = this.hasChild ? "folder" : "file"; else this.icon = icon;
16   this.line = this.hasChild ? (last ? "pm2" : "pm1") : (last ? "l2" : "l1");
17   if(!$.showLines) this.line = this.hasChild ? "pm3" : "ll";
18 
19   s += "<div><table border='0' cellpadding='0' cellspacing='0'>"+
20        "<tr title='"+ hint +"'><td>"; if (MzTreeNode.htmlChildPrefix)
21   s += MzTreeNode.htmlChildPrefix +"</td><td>"; if(!isRoot)
22   s += "<img border='0' id='"+ $.index +"_expand_"+ id +"' src='"+
23        (this.hasChild ? MzTreeView.icons.collapse[this.line].src : 
24        MzTreeView.icons.line[this.line].src)+"'>"; if($.showNodeIcon)
25   s += "<img border='0' id='"+ $.index +"_icon_"+ id +"' src='"+ 
26        MzTreeView.icons.collapse[this.icon].src +"'>"; if($.useCheckbox)
27   s += "<img border='0' id='"+$.index +"_checkbox_"+ id +"' src='"+ 
28        MzTreeView.icons.line["c"+ (this.checked?1:0)].src +"'>";
29   s += "</td><td style='padding-left: 3px' nowrap='true'><a href='"+ url +
30        "' target='"+ target +"' id='"+$.index +"_link_"+ id +
31        "' class='MzTreeView'>"+ this.text +"</a></td></tr></table><div ";
32        if(isRoot&&this.text=="") s="<div><div ";
33   s += "id='"+$.index+"_tree_"+id+"' style='display: none;'></div></div>";
34   return s;
35 };
     if (MzTreeNode.htmlChildPrefix)这句话是用来判断当前节点是否为子节点,如果是的话则会添加图片在前面,莫名其妙的是我的当前节点是根节点,怎么也会符合这个判断条件呢?这个应该还是梅花雪的一个Bug,不管这么多了,直接在if中添加一个判断,即if (MzTreeNode.htmlChildPrefix && !isRoot),如果当前节点是根节点,则不再添加图片了。重新试了一下,果然可以了。接下来就是一些需求方面的小问题了,例如树在初始化完毕之后总是会默认将焦点放在根节点上,这个问题很容易解决,可以修改样式表将节点获取焦点的样式修改掉,或者在MzTreeView.js文件中将节点获取焦点的代码去掉。
1 MzTreeView.prototype.afterRender=function()
2 {
3   var me=this;
4   if(document.getElementById("MTV_root_"+ me.index))
5   {
6     //if(me.firstNode)(me.currentNode=me.firstNode).focus();
7     this.dispatchEvent(new System.Event("onrender"));
8     if(me.useArrow) me.attachEventArrow();
9   }
10   else setTimeout(function(){me.afterRender();}, 100);
11 };
     还有一个问题,梅花雪树在动态加载节点时是异步完成的,代码中有判断,当子节点的数目超过400个时,父节点打开的时候会显示“正在加载...”,其实这个效果还是不错的,给用户的体验不错。但是现在树在默认加载时要将所有节点全部展开,如果节点数目过多,加载时间也是很长的,这个时候并没有显示“正在加载”(梅花雪只对节点加载做了处理,而没有对树加载做处理),页面会一直等待直到整棵树全部加载完。这个时候的用户体验特别不好,如何解决这个问题呢?看来我们只有自己写代码来处理了!js中有一个系统方法setInterval,它可以在指定的时间间隔内执行代码,我们可以规定一个时间段,每隔一个时间段就进行检查,如果树已经加载完毕就显示树,如果树没有加载完毕就给出一个提示(文字或图片都可以),这个时候的用户体验相对会好很多。不过梅花雪树本身并没有提供用于判断树被加载完毕的标识,所以我们需要在MzTreeView.js文件中添加标识。

    isend——判断树是否被加载完毕

    lastNode——最后的节点

    在树Render之前给lastNode赋值(最后一个节点的id),setInterval方法中不断判断树是否已被加载完毕,然后做相应的处理,读者可以参考我附件中的例子。需要修改MzTreeView.js文件中的MzTreeNode.prototype.expandLevel方法如下。

1 MzTreeNode.prototype.expandLevel = function(level) {
2     if (level <= 0) return;
3     level--; var me = this;
4     if (this.hasChild && !this.expanded) this.expand();
5     if (level == 1) {
6         if (this.hasChild) {
7             var $ = this.$$caller;
8             for (i = 0; i < this.childNodes.length; i++)
9             if ($.lastNode != "" && this.childNodes[i].text == $.lastNode) $.isend = true;
10         }
11     }
12     for (var x = this.$$caller.index, i = 0, n = this.childNodes.length; i < n; i++) {
13         var node = this.childNodes[i], d = node.index; if (node.hasChild)
14             setTimeout("Instance('" + x + "').nodes['" + d + "'].expandLevel(" + level + ")", 1);
15     }
16 };

    不过有个小小的缺陷,那就是采用上述方式loading树的时候用于提示用户的gif图片不动了,直到整棵树加载完成。这个问题是由于js本身是单线程处理的语言造成的,当js在采用setInterval方法加载树的过程中一直会占用CPU的处理时间,这时便没有功夫去处理gif图片的动画了。感兴趣的读者可以改造附件中的代码,自己模拟多线程使gif图片动起来。不过我最终还是放弃了使用gif图片进行提示,改用文字了,反正达到预期的效果就好。

    结论:总体来说,梅花雪树的效果还是不错的,尤其在动态加载树的节点,采用不同的方式绑定数据,并且还可以为树的节点提供右键菜单,功能很强大,也为采用客户端树的开发人员提供了很大的便利。个人认为,web开发中采用客户端树总比采用服务器端树效率要高,但复杂度高并且难以控制,适当并合理地借鉴前人的经验,将会省去很多不必要的麻烦。虽然梅花雪树经过了2个版本,但是在实际应用中还是暴露了不少的问题,作者给出的开发文档和注释也不全面,园子里有不少热心的朋友都对梅花雪树改造过,希望大家可以共同努力,打造一个功能更强大、更完美的梅花雪树!!

    顺便给出梅花雪树2.0中重要文件的说明,其中标记为红色的是主要修改的文件或资源。为了适应我的项目,经过修改后的梅花雪树中删除了部分没有用的文件(如采用js和xml文件绑定数据等),修改了部分加载资源的路径等。

scripts\csdn\community\treedata\ 该目录下的文件用于绑定树的数据源,包括js文件和xml文件。
scripts\jsframework.js mztreeview主框架代码文件。
scripts\system\_resource\mztreeview\ 主要资源文件,包括样式表和图片。
scripts\system\data\mzdata.js 用于处理mztreeview节点数据。
scripts\system\net\mzcookie.js mztreeview的状态可以保存到cookie中,该文件用于处理cookie的保存和获取。
scripts\system\plugins\ 支持多浏览器版本数据加载的处理。
scripts\system\web\ui\webcontrols\mztreeview.js 生成mztreeview主结构的代码文件。
scripts\system\web\forms\mzeffect.js mztreeview特殊效果处理文件。
scripts\system\xml\mzxmldocument.js 用于处理xml文档。
scripts\system\global.js mztreeview全局配置文件。

梅花雪树1.0下载
梅花雪树2.0下载
经过修改后的梅花雪树

posted @ 2009-03-16 13:45  Jaxu  阅读(6978)  评论(18编辑  收藏  举报