2014-01-10 110 views
1

我試圖從Javascript中的UL元素構建JSON對象。將無序列表轉換爲JSON

我有這樣的表結構:

<ul id="trainingmenu"> 
    <li class="tmnode"><span id="M5lx9n" class="tmname">menu 1</span> 
     <ul> 
      <li class="tmnode"><span id="M66p48" class="tmname">menu 2</span> 
       <ul> 
        <li class="tmnode"><span class="tmname" id="Mschr">menu 3</span> 
        </li> 
       </ul> 
      </li> 
      <li class="tmnode"><span id="Mu03e0" class="tmname">menu 4</span></li> 
     </ul> 
    </li> 
</ul> 

這是JS:

var treeObject = {}; 

    function treeHTML(element, object) { 
     if (element.tagName=="UL") { element=element.firstChild; } 
     object["id"] = element.firstChild.id; 
     object["name"] = element.firstChild.innerHTML; 
     var nodeList = element.getElementsByTagName('LI'); 
     if (nodeList[0]) { 
      object["nodes"] = []; 
      for (var i = 0; i < nodeList.length; i++) { 
       object["nodes"].push({}); 
       treeHTML(nodeList[i], object["nodes"][object["nodes"].length - 1]); 
      } 
     } 
    } 
    treeHTML(document.getElementById("trainingmenu"), treeObject); 
    alert(JSON.stringify(treeObject)); 

..和這是我得到的輸出:

{"id":"M5lx9n","name":"menu 1","nodes":[{"id":"M66p48","name":"menu 2","nodes":[{"id":"Mschr","name":"menu 3"}]},{"id":"Mschr","name":"menu 3"},{"id":"Mu03e0","name":"menu 4"}]} 

這部分這是我想要的格式 - 從每個LI元素的childNode中挑選id和name字段。但它是重複的節點 - 菜單3出現了兩次

這裏有一個fiddle

我可能搞砸了遞歸,但它已經盯着2天都沒有制定出如何。誰能幫忙?

+1

FWIW,我微微一愣的代碼在所有工作。如果HTML代碼確實如此寫入,則外部'ul'上的'element.firstChild' _should_返回'ul'和'li'之間的空白文本節點。在較新的瀏覽器中,您可以使用'.firstElementChild'來忽略文本節點。 – Alnitak

+0

正確 - 這只是爲了便於閱讀;我刪除了小提琴的中斷。但是,我不知道.firstElementChild(!),所以我在那裏學到了一些東西。感謝那。 – WindsorAndy

+0

@WindsorAndy不要忘記'firstElementChild'是DOM規範中的一個適度增加 - 它可能不存在於舊版瀏覽器中。 – Alnitak

回答

1

由於Alnitak提到你應該使用.children.childNodes屬性而不是.getElementsByTagName()方法。下面的代碼片段使用Array原型的.filter()方法來過濾ul子代和.forEach()進行迭代。

function treeHTML(element) { 
    var o = {}; 
    o.id = element.firstElementChild.id; 
    o.name = element.firstElementChild.innerHTML; 
    o.nodes = []; 
    [].slice.call(element.children).filter(function(e) { 
     return e.tagName.toLowerCase() === 'ul'; 
    }).forEach(function(ul) { 
     [].slice.call(ul.children).forEach(function(li) { 
      o.nodes.push(treeHTML(li)); 
     }); 
    }); 
    return o;  
} 
var treeObj = treeHTML(document.getElementById("trainingmenu") 
           .firstElementChild); 

這裏是一個替代的解決方案也應在舊版本的IE瀏覽器的工作:

function children(node, selector) { 
    var res = [], nodes = node.childNodes, l = nodes.length; 
    for (var i = 0; i < l; i++) { 
     if (nodes[i].nodeType === 1) { 
      if (selector && nodes[i].tagName.toLowerCase() === selector) { 
       res.push(nodes[i]); 
      } else if (!selector) { 
       res.push(nodes[i]); 
      } 
     } 
    } 
    return res; 
} 

function treeHTML(element) { 
    var o = {}; 
    var span = children(element, 'span'); 
    o.id = span[0].id; 
    o.name = span[0].innerHTML; 
    o.nodes = []; 
    var ul = children(element, 'ul'), n = ul.length; 
    for (var i = 0; i < n; i++) { 
     var li = children(ul[i]), l = li.length; 
     for (var j = 0; j < l; j++) { 
      o.nodes.push(treeHTML(li[j])); 
     } 
    } 

    return o;  
} 

var root = document.getElementById("trainingmenu"); 
var treeObj = treeHTML(children(root)[0]); 

http://jsfiddle.net/XZ54G/

+0

一個非常優雅的解決方案,BlackSheep。這個問題正在變成一個JS大師班。 – WindsorAndy

+0

@WindsorAndy謝謝,我很高興它可以幫助。 – undefined

+0

您可以填充ES5陣列功能,但不能填充firstElementChild和children。 – Alnitak

1

它是重複的節點,因爲element.getElementsByTagName發現全部後代,而不僅僅是兒童。你可能想測試一下nodeList[i].parentNode == element

或者只是直接迭代所有元素的子節點(element.childNodeselement.children),然後使用tagName == 'li'來查找那些元素。在格式良好的HTML中,您只能在ul元素的子列表中找到(空白)文本節點和li元素。

+0

感謝那..聽起來很有希望。測試它.. – WindsorAndy

+0

...和評論節點。 –

+0

@JanDvorak是的,這些也是! – Alnitak