从一个对象创建一个嵌套列表

5
我有以下对象。
var json = {
    items:[
        {id:45 , lable:"Test 1.2.1", parent_id: 2},
        {id:12, lable:"Test 1.0", parent_id: 0},
        {id:32, lable:"Test 1.1", parent_id: 12},
        {id:2, lable:"Test 1.2", parent_id: 12}
    ]
}

使用这个对象,我需要创建一个嵌套列表,在浏览器中将会呈现如下:
  • 测试 1.0
    • 测试 1.1
    • 测试 1.2
      • 测试 1.2.1
我的方法是编写一个函数,遍历该对象并将值存储在元素列表的数组中。
function appendItems(json){
    var list = json.items;
    var listelem = [];

    list.forEach(function(i){
        var listelements = document.createElement('li');
        listelements.setAttribute("id",i.id);
        listelements.setAttribute("data-parent-id", i.parent_id);
        listelements.innerText = i.lable;
        listelem.push(listelements);

    })

    console.log(listelem);    
}

但是我不确定如何从这一点上获得嵌套结构


你是否愿意考虑使用像jsTree这样的插件?如果不是,就需要使用递归。 - ndd
不使用插件,通过纯JS实现。 - RRP
那么对于 jQuery 也不行,是吗? - ndd
@ndd 现在不需要使用 jQuery。 - RRP
2个回答

9

首先,我建议将你的数组转换成一个实际的树形结构。你可以按照以下方式进行:

var obj = {
    items:[
        {id:45 , lable:"Test 1.2.1", parent_id: 2},
        {id:12, lable:"Test 1.0", parent_id: 0},
        {id:32, lable:"Test 1.1", parent_id: 12},
        {id:2, lable:"Test 1.2", parent_id: 12}
    ]
}

// first we create a dictionary so we can find elements by id easily
var objDict = obj.items.reduce(function(p,c) {
    p[c.id] = c;
    c.children = [];
    return p;
}, {});

// then we build our tree
var tree = obj.items.reduce(function(p,c) {
    // if the parent_id is zero, we have found the root.
    if (!c.parent_id) {
        p = c;
    }
    // otherwise, figure out who the parent is and add this one as a child
    else {
        objDict[c.parent_id].children.push(c);
    }
    return p;
}, {});

console.log(JSON.stringify(tree));

这将为你提供一个类似于这样的对象:
{
    "id": 12,
    "lable": "Test 1.0",
    "parent_id": 0,
    "children": [
        {
            "id": 32,
            "lable": "Test 1.1",
            "parent_id": 12,
            "children": []
        },
        {
            "id": 2,
            "lable": "Test 1.2",
            "parent_id": 12,
            "children": [
                {
                    "id": 45,
                    "lable": "Test 1.2.1",
                    "parent_id": 2,
                    "children": []
                }
            ]
        }
    ]
}

现在你应该可以很容易地处理它了。从根开始创建一个节点,然后进行深度优先搜索,下降到每个子节点并根据需要创建嵌套节点。

像这样:

processTree(tree, document.getElementById("list"));

function processTree(node, element) {
    var li = document.createElement('li');
    li.innerText = node.lable;
    element.appendChild(li);
    if (node.children.length) {
        var ul = document.createElement('ul');
        li.appendChild(ul);
        // note: you might want / need to actual sort the children first
        // but it's not clear from the OP if sorting by id will always give the right order
        for (var i=0; i < node.children.length; i++) {
           processTree(node.children[i], ul);
        }
    }
}

为了给您提供一个像这样的工作示例:

var obj = {
  items: [{
    id: 45,
    lable: "Test 1.2.1",
    parent_id: 2
  }, {
    id: 12,
    lable: "Test 1.0",
    parent_id: 0
  }, {
    id: 32,
    lable: "Test 1.1",
    parent_id: 12
  }, {
    id: 2,
    lable: "Test 1.2",
    parent_id: 12
  }]
}

var objDict = obj.items.reduce(function(p, c) {
  p[c.id] = c;
  c.children = [];
  return p;
}, {});

var tree = obj.items.reduce(function(p, c) {
  if (!c.parent_id) {
    p = c;
  } else {
    objDict[c.parent_id].children.push(c);
  }
  return p;
}, {});

processTree(tree, document.getElementById("list"));

function processTree(node, element) {
  var li = document.createElement('li');
  li.innerText = node.lable;
  element.appendChild(li);
  if (node.children.length) {
    var ul = document.createElement('ul');
    li.appendChild(ul);
    for (var i = 0; i < node.children.length; i++) {
      processTree(node.children[i], ul);
    }
  }
}
<ul id="list">
</ul>


谢谢,我在确定子父关系或如何实现这一点方面卡住了,我注意到对象中的 "parent_id" 值等于其父级 "id" 值。我甚至没有考虑将数组转换为树形结构。 - RRP

0
这是一个使用递归的简单解决方案。我使用了一个辅助函数,它返回给定parent_id的子元素数组。希望对你有所帮助。
'use strict';

(function() {

    function childrenOf(targetId, items) {
        var children = [];
        items.forEach(function(item) {
            if (item.parent_id === targetId) {
                children.push(item);
            }
        });
        return children;
    }

    function nestedList(id, items) {
        var result = '';
        var children = childrenOf(id, items);
        if (children.length) {
            result = '<ul>';
            children.forEach(function(child) {
                result += '<li>';
                result += child.label;
                result += nestedList(child.id,items);
                result += '</li>';
            });
            result += '</ul>';
        }
        return result;
    }

    var json = {
        items:[
            {id:45 , label:"Test 1.2.1", parent_id: 2},
            {id:12, label:"Test 1.0", parent_id: 0},
            {id:32, label:"Test 1.1", parent_id: 12},
            {id:2, label:"Test 1.2", parent_id: 12}
        ]
    }

    var rootId = 0;
    var output = nestedList(rootId, json.items);
    console.log('<html><body>');
    console.log(output);
    console.log('</body></html>');



})();

它会产生以下输出:
<html>

<body>
    <ul>
        <li>Test 1.0
            <ul>
                <li>Test 1.1</li>
                <li>Test 1.2
                    <ul>
                        <li>Test 1.2.1</li>
                    </ul>
                </li>
            </ul>
        </li>
    </ul>
</body>

</html>

每次调用childrenOf都需要再次遍历整个列表,这并不是很高效。 - Matt Burland
你说得很对。先遍历数组并预先生成一系列子数组会更有效率。 - SteveGreenley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接