在做tree封裝的時候,困難的地方往往是怎樣顯示樹形數據,總后臺查詢的數據通常是List<Map<K,V>>之類的JSON字符串。形如
[{"NODELEVEL":2,"NODENAME":"測試深度節點20","FLAG":"1","HASCHILD":"1","PARENTNODE":"10002","LEVELCODE":"1000200000"}
,{"NODELEVEL":3,"NODENAME":"測試深度節點200","FLAG":"1","HASCHILD":"1","PARENTNODE":"1000200000","LEVELCODE":"100020000000000"}
,{"NODELEVEL":4,"NODENAME":"測試深度節點2000","FLAG":"1","HASCHILD":"1","PARENTNODE":"100020000000000","LEVELCODE":"10002000000000000000"}
,{"NODELEVEL":5,"NODENAME":"測試深度節點20000","FLAG":"1","HASCHILD":"1","PARENTNODE":"10002000000000000000","LEVELCODE":"1000200000000000000000000"}
,{"NODELEVEL":6,"NODENAME":"qwerfga","FLAG":"1","HASCHILD":"0","PARENTNODE":"1000200000000000000000000","LEVELCODE":"100020000000000000000000000001"}
,{"NODELEVEL":2,"NODENAME":"sdfg","FLAG":"1","HASCHILD":"1","PARENTNODE":"10002","LEVELCODE":"1000200001"}
,{"NODELEVEL":3,"NODENAME":"safsdfsadfaaa","FLAG":"1","HASCHILD":"0","PARENTNODE":"1000200001","LEVELCODE":"100020000100001"}
]
通常樹需要的數據是有結構層次的,形如:
[
{
"NODELEVEL":2,
"NODENAME":"sdfg",
"FLAG":"1",
"HASCHILD":"1",
"PARENTNODE":"10002",
"LEVELCODE":"1000200001",
"children":[
{
"NODELEVEL":3,
"NODENAME":"safsdfsadfaaa",
"FLAG":"1",
"HASCHILD":"0",
"PARENTNODE":"1000200001",
"LEVELCODE":"100020000100001"
}
],
"isFolder":true
},
{
"NODELEVEL":2,
"NODENAME":"測試深度節點20",
"FLAG":"1",
"HASCHILD":"1",
"PARENTNODE":"10002",
"LEVELCODE":"1000200000",
"children":[
{
"NODELEVEL":3,
"NODENAME":"測試深度節點200",
"FLAG":"1",
"HASCHILD":"1",
"PARENTNODE":"1000200000",
"LEVELCODE":"100020000000000",
"children":[
{
"NODELEVEL":4,
"NODENAME":"測試深度節點2000",
"FLAG":"1",
"HASCHILD":"1",
"PARENTNODE":"100020000000000",
"LEVELCODE":"10002000000000000000",
"children":[
{
"NODELEVEL":5,
"NODENAME":"測試深度節點20000",
"FLAG":"1",
"HASCHILD":"1",
"PARENTNODE":"10002000000000000000",
"LEVELCODE":"1000200000000000000000000",
"children":[
{
"NODELEVEL":6,
"NODENAME":"qwerfga",
"FLAG":"1",
"HASCHILD":"0",
"PARENTNODE":"1000200000000000000000000",
"LEVELCODE":"100020000000000000000000000001"
}
],
"isFolder":true
}
],
"isFolder":true
}
],
"isFolder":true
}
],
"isFolder":true
}
]
//ps:感謝
http://jsonformatter.curiousconcept.com/ 在線format json
從平鋪的格式轉換到層次結構的json我經過多次實踐,得出了下面的方法:
最開始,我約定ajax請求能得到樹深度遍歷結果,推薦之前一篇文件:
一種能跨數據庫的樹形數據表格設計 上面的示例數據就是一個深度遍歷結果。深度遍歷結果的特點是每條記錄的上一條記錄要么是兄弟節點,要么是父節點,每條記錄的下一條記錄要么是兄弟節點,要么是子節點。根據這個特點,可以用如下方法解析:
function parse(arr, option) {
var treenodes = [];//存放解析結果
var stack = [];//存放當前節點路徑 如果A->B->C-D 遍歷到D節點時stack應該是[A,B,C]
for ( var i = 0; i < arr.length; i++) {
var mapednode = arr[i];
if (i == 0) {// 第一個節點
treenodes.push(mapednode);
stack.push(mapednode);
} else {
var previousnode = arr[i - 1];
if (parseInt(previousnode.level, 10) + 1 == parseInt(mapednode.level, 10)) {// 深度增加(深度增加1才視為深度增加)
var parentnode = stack[stack.length - 1];//棧的最后一個節點是父節點
parentnode.isFolder = true;
if (!parentnode.children) parentnode.children = [];
parentnode.children.push(mapednode);
stack.push(mapednode);
}
if (previousnode.level == mapednode.level) {// 與之前節點深度相同
// 棧中最后一個節點此時是同級節點previousnode
// 所以此處去父節點時需要取到previousnode的父節點
// 如果是一級節點,此時父節點應該是根節點
if (stack.length > 1) {
var parentnode = stack[stack.length - 2];
parentnode.isFolder = true;
if (!parentnode.children)
parentnode.children = [];
parentnode.children.push(mapednode);
} else {
treenodes.push(mapednode);
}
stack[stack.length - 1] = mapednode;// 保證棧中是同級節點的最后一個
}
if (previousnode.level > mapednode.level) {// 深度減少 出棧
for ( var j = 0; j < (previousnode.level - mapednode.level); j++) {
stack.pop();
}
// 如果回到了一級節點,此時父節點應該是根節點
if (stack.length > 1) {
var parentnode = stack[stack.length - 2];
parentnode.isFolder = true;
if (!parentnode.children) {
parentnode.children = [];
}
parentnode.children.push(mapednode);
} else {
treenodes.push(mapednode);
}
stack[stack.length - 1] = mapednode;// 保證棧中是同級節點的最后一個
}
}
}
delete stack;
delete arr;
return treenodes;
}
var parseoption={
titlefield:"NODENAME",//顯示名稱字段
keyfield:"LEVELCODE",//節點唯一標識字段
levelfield:"NODELEVEL",//深度字段
parentfield:"PARENTNODE",//父節點標識字段
customsiblingsort:"NODENAME"//同級節點排序字段
};
var parsedTreeData = parse(arr,parseoption);
基本思路是將arr循環,然后將節點層級的“塞”到對應的地方。因為總是要跟前一個節點數據比較,所以需要訪問到上一個節點。而且總需要獲得當前節點的父節點,而且需要判斷是不是回到了一級節點,所以需要記錄當前節點的訪問路徑,從整個深度遍歷arr的過程看,當前節點的路徑剛好是一個先進先出的關系,所以將stack聲明為一個堆棧使用。
上面的方法沒有實現同級節點排序,使用push方法能保證樹的同級顯示順序與深度遍歷結果中的出現順序(這樣可以利用ajax數據本身的順序)。其實可以用
插入排序的方法來替換上面的多處push。
//插入排序法
var cl= parentnode.children.length;
var insertIndex = 0;
for ( var j = 0; j < cl; j++) {
var targetSortValue = arr[parentnode.children[j]][fSIBLINGSORT]||"";
//字符串比較
if(isNaN(targetSortValue) && isNaN(sortValue) && targetSortValue.localeCompare(sortValue)>0 ){
insertIndex= j;
break;
}else{
insertIndex=j+1;
}
//數字比較
if((!isNaN(targetSortValue)) && (!isNaN(sortValue)) && parseFloat(targetSortValue)>parseFloat(sortValue) ){
insertIndex= j;
break;
}else{
insertIndex=j+1;
}
}
parentnode.children.splice(insertIndex, 0, i);
但是事實上用戶老會抱怨為什么一定要深度遍歷的結果,雖然,在之前提到的
一種跨數據庫的數據表設計中比較容易獲取到深度遍歷結果。
所以我又開始想怎么將無序的平鋪樹數據解析成層級結構。
function parse(arr,option){
//有同級排序 拼接同級節點排序函數
if(option.customsiblingsort){
if(typeof(window[option.customsiblingsort])!="function"){//不是函數,視為字段名
var sortfield = option.customsiblingsort;
option.customsiblingsort= function(node1,node2){
var v1 =node1[sortfield]||"";
var v2 =node2[sortfield]||"";
if((!isNaN(v1)) && (!isNaN(v2))){//數字比較
return parseFloat(v1)- parseFloat(v2);
}else{
return v1.localeCompare(v2);//字符串比較
}
};
}else{
option.customsiblingsort=window[option.customsiblingsort];
}
}
//step1:排序,按照節點深度排序,同深度節點按照customsiblingsort排序
arr.sort(function (node1,node2){
if((node1[option.levelfield]-node2[option.levelfield])>0){
return 1;
}else if ((node1[option.levelfield]-node2[option.levelfield])==0 && node1[option.parentfield].localeCompare(node2[option.parentfield])>0){
return 1;
}else if ( option.customsiblingsort && (node1[option.levelfield]-node2[option.levelfield])==0 && node1[option.parentfield].localeCompare(node2[option.parentfield])==0 && option.customsiblingsort(node1,node2)>0 ){
return 1;
}else{
return 0;
}
});
var result =[];
var mapnode ={};//key :node key,value:node object reference
//step2:排序后節點解析成樹
for(var i= 0;i<arr.length;i++){
var mapednode =arr[i];
var n = mapednode;
//深度為1或者深度與排序后第一個節點的深度一致視為一級節點
if(n[option.levelfield]==1 || n[option.levelfield]==arr[0][option.levelfield]){
result.push(n);
mapnode[n[option.keyfield]]= n;
continue;
}
if(n[option.levelfield]>=1){
//找到父節點
mapnode[n[option.keyfield]]= n;
var p= n[option.parentfield];
var parentNode = mapnode[p];
//arr是按照深度排序,任何一個節點的父節點都會在子節點之前,所以parentNode!=null
if(typeof(parentNode.children)=="undefined"){
parentNode.children=[];
}
parentNode.isFolder=true;
parentNode.children.push(n);
}
}
delete arr,mapnode;
return result;
}
var parseoption ={titlefield:"NODENAME",keyfield:"LEVELCODE",levelfield:"NODELEVEL",parentfield:"PARENTNODE",customsiblingsort:"NODENAME"};
var parsedTreeData = parse(arr,parseoption);
這種方法的循環次數自然比第一種方法要長,先根據深度將數組排序,這樣做的目的一個是保證在遍歷數組的任何一個節點是能在之前的節點中找到其父節點,第二也附帶著將同級節點的排序也實現了。這種方法代碼可讀性比一種直接,性能慢在第一階段的排序上。
樹的前端顯示總是能對前端開發者造成很大的挑戰,光解析數據就會費勁心思。為了不至于要使用xtree,dtree那樣的傳統的tree控件,還是找找jQuery插件。目前發現了兩個不錯的jQuery tree插件
jsTree 和
dynatree 。不過都版本還是比較低,都不是很穩定,經常會有bug。jsTree就支持無序的數組數據源,dynatree必須是層次數據源。dynatree的性能顯然要比jsTree要好,支持lazyLoad。所以選定dynatree。只是需要自己解析數據。
大家在樹方面有什么建議和經驗不妨分享分享。
相關的代碼下載