轉自博客園: 現在有一個數據,需要你渲染出對應的列表出來: var data = [ {"id":1}, {"id":2}, {"id":3}, {"id":4}, ]; var str="<ul>"; data.forEach(function(v,i){ str+="<li><span>"+v. ...
轉自博客園:
現在有一個數據,需要你渲染出對應的列表出來:
var data = [
{"id":1},
{"id":2},
{"id":3},
{"id":4},
];
var str="<ul>";
data.forEach(function(v,i){
str+="<li><span>"+v.id+"</span></li>"
})
str="</ul>"
$(doucment).append(str);
哼,easy!
語罷,又是一道題飛來!
哦,還帶了兒子來當幫手。我一個迴圈再一個迴圈,輕鬆帶走你們
var data2 = [
{"id":1,children:[{"id":"child11"},{"id":"child12"}]},
{"id":2},
{"id":3children:[{"id":"child31"},{"id":"child32"}]},
{"id":4},
];
var str="<ul>";
data2.forEach(function(v,i){
if(v.children&&v.children.length>0){
str+="<li><span>"+v.id+"</span>";
str+="<ul>";
v.children.forEach(function(value,index){
str+="<li><span>"+value.id+"</span>";
})
str="</ul>";
str="</li>";
}else{
str+="<li><span>"+v.id+"</span></li>"
}
})
str="</ul>"
$(doucment).append(str);
還有誰?
var json=[
{
name:123,id:1
children:[
{
name:453,id:456,children:[{name:789,id:777,children:[{name:"hahahqqq---qq",id:3232,children:[name:'son',id:"13132123211"]}]}]
},
{
name:"Cessihshis" , id:12121
}
]
},
{
name:"啊啊啊11", id:12
},
];
竟然把全家都帶來了,看我迴圈迴圈再迴圈
大法。
嗯,不知道他家幾代同堂,我該迴圈幾次?突然間你感覺遇到對手了。
正納悶著,突然有人拍了一下你的肩膀,兄弟,我這裡有一本遞歸
秘籍,我看你骨骼驚奇,是個練武奇才,10塊錢賣你了。
function render(treeJson){
if(!Array.isArray(treeJson)||treeJson.length<=0){return ""}
var ul=$("<ul>");
treeJson.forEach(function(item,i){
var li=$("<li><span class='treeName'>"+item.name+"</span></li>");
if(Array.isArray(item.children)&&item.children.length>0){
li.append(render(item.children))
}
ul.append(li);
})
return ul
}
$(document).append(render(json));
好了不扯了,通過遞歸,無需再判斷數據有多少層級,只有當前數組有children並且長度大於0,函數就會遞歸調用自身,並且返回一個ul。
這樣一來,一顆非常簡陋的樹就生成了,不過通常樹都帶有radio或者checkbox選擇框,而且很多時候都需要對樹的右側進行拓展,比如加一些新增,編輯等按鈕什麼的,可以考慮多傳一個對象作為參數。
var checkbox={
radio:"<label class='myTreeIcon'><input type='radio' name='selectTreeRedio'><span></span></label>",
multi:"<input type='checkbox' name='selectTreeRedio'>"
}
function render(treeJson,option={type:0,expandDom:function(){}}){
if(!Array.isArray(treeJson)||treeJson.length<=0){return ""}
var {type,expandDom}=option;
var ul=$("<ul>");
treeJson.forEach(function(item,i){
var str="";
if(type==1){
str+=checkbox.multi
}else if(type==2){
str+=checkbox.radio
}
var li=$("<li data-id='"+item+"'>"+str+"<span class='treeName'>"+item.name+"</span></li>");
expandDom&&expandDom(li,item);
if(item.children&&item.children.length>0){
li.append(render(item.children,option))
}
ul.append(li);
})
return ul
}
//option使用了一個預設對象,預設為不需要選擇框和不需要拓展, 如果傳入的type為1或者2,則生成checkbox或者radio,由於radio樣式比較醜,用label包起來自己模擬選中的效果;如果傳入拓展參數,則把當前的父級li以及當前的參數傳入,以便進行拓展。
$("#tree").append(render(json,{
type:1,
expandDom:function(el,data){
el.append("<button>編輯</button><button>測試</button><a data-msg='"+JSON.stringify(data)+"'></a>")
}
}))
有時候後臺返回的可能不是拼裝好層級的數組,而是帶有pid標識的所有數組的集合,比如:
var data = [
{"id":2,"name":"第一級1","pid":0},
{"id":3,"name":"第二級1","pid":2},
{"id":5,"name":"第三級1","pid":4},
{"id":100,"name":"第三級2","pid":3},
{"id":6,"name":"第三級2","pid":3},
{"id":601,"name":"第三級2","pid":6},
{"id":602,"name":"第三級2","pid":6},
{"id":603,"name":"第三級2","pid":6}
];
為了用遞歸來渲染出樹來,這時,就需要我們手動來將層級裝好了:
function arrayToJson(treeArray){
var r = [];
var tmpMap ={};
for (var i=0, l=treeArray.length; i<l; i++) {
// 以每條數據的id作為obj的key值,數據作為value值存入到一個臨時對象裡面
tmpMap[treeArray[i]["id"]]= treeArray[i];
}
for (i=0, l=treeArray.length; i<l; i++) {
var key=tmpMap[treeArray[i]["pid"]];
//迴圈每一條數據的pid,假如這個臨時對象有這個key值,就代表這個key對應的數據有children,需要Push進去
if (key) {
if (!key["children"]){
key["children"] = [];
key["children"].push(treeArray[i]);
}else{
key["children"].push(treeArray[i]);
}
} else {
//如果沒有這個Key值,那就代表沒有父級,直接放在最外層
r.push(treeArray[i]);
}
}
return r
}
現在我們已經實現了將沒有層級結構的數組轉化為帶有層級的數組,那麼問題來了,樹形圖還常常會需要帶搜索功能,有沒有辦法把帶層級結構的數組轉化為不帶層級結構的一個數組呢?因為如果不帶層級的話,進行搜索等操作時就非常方便,一個filter基本就可以搞定了。
var jsonToArray=function (nodes) {
var r=[];
if (Array.isArray(nodes)) {
for (var i=0, l=nodes.length; i<l; i++) {
r.push(nodes[i]);
if (Array.isArray(nodes[i]["children"])&&nodes[i]["children"].length>0)
//將children遞歸的push到最外層的數組r裡面
r = r.concat(jsonToArray(nodes[i]["children"]));
delete nodes[i]["children"]
}
}
return r;
}
這樣,不管後臺返回什麼格式給我們,我們都可以自由的互轉了,不管是帶層級的轉不帶層級的,還是把不帶層級的轉化為帶有層級的,都只需要調用一個函數就可以輕鬆解決。
不過這裡有一個隱患,就是由於對象的引用關係,操作後雖然返回了我們需要東西,但是會改變原來的數據。
為了不影響到原來的數據,我們需要複製一份數據,需要進行一次深拷貝。
為什麼是深拷貝而不是淺拷貝?因為淺拷貝只會複製最外面的一層,假入某一個key值裡面又是一個對象,那對複製後的對象的這個key的值進行操作通用會影響到原來的對象。淺拷貝的方法有很多,ES6的assign,jq第一個參數不為true的 $.extend(),數組的slice(0),還有很多很多。
對於標準的json格式的對象,可以用JSON.parse(JSON.stringify(obj))來實現。當然,本文寫的是遞歸,所以還是來手寫一個
function deepCopy(obj){
var object;
if(Object.prototype.toString.call(obj)=="[object Array]"){
object=[];
for(var i=0;i<obj.length;i++){
object.push(deepCopy(obj[i]))
}
return object
}
if(Object.prototype.toString.call(obj)=="[object Object]"){
object={};
for(var p in obj){
object[p]=obj[p]
}
return object
}
}
其實有點類似於淺拷貝,淺拷貝會複製一層,那麼我們判斷某個值是對象,通過遞歸再來一次(好比飲料中獎再來一瓶一樣,如果中獎了,就遞歸再來一瓶,又中獎就又遞歸再來一瓶,直到不再中獎),也就是說我們通過無盡的淺拷貝來達到複製一個完全的新的對象的效果。
這樣,對樹結構操作時,只需要傳入深拷貝後新對象,就不會影響原來的對象了;
jsonToArray(deepCopy(data));
亦或是
arrayToJson(deepCopy(data)):