[ACCEPTED]-How can I recursively create a UL/LI's from JSON data - multiple layers deep-menu

Accepted answer
Score: 16

You can try this recursive function I've 7 just coded:

function buildList(data, isSub){
    var html = (isSub)?'<div>':''; // Wrap with div if true
    html += '<ul>';
    for(item in data){
        html += '<li>';
        if(typeof(data[item].sub) === 'object'){ // An array will return 'object'
            if(isSub){
                html += '<a href="' + data[item].link + '">' + data[item].name + '</a>';
            } else {
                html += data[item].id; // Submenu found, but top level list item.
            }
            html += buildList(data[item].sub, true); // Submenu found. Calling recursively same method (and wrapping it in a div)
        } else {
            html += data[item].id // No submenu
        }
        html += '</li>';
    }
    html += '</ul>';
    html += (isSub)?'</div>':'';
    return html;
}

It returns the html for the menu, so 6 use it like that: var html = buildList(JSON.menu, false);

I believe it is faster 5 because it's in pure JavaScript, and it 4 doesn't create text nodes or DOM elements 3 for every iteration. Just call .innerHTML or $('...').html() at the 2 end when you're done instead of adding HTML 1 immediately for every menu.

JSFiddled: http://jsfiddle.net/remibreton/csQL8/

Score: 6

Make two functions makeUL and makeLI. makeUL calls makeLI on each 3 element, and makeLI calls makeUL if there's sub elements:

function makeUL(lst) {
    ...
    $(lst).each(function() { html.push(makeLI(this)) });
    ...
    return html.join("\n");
}

function makeLI(elem) {
    ...
    if (elem.sub)
        html.push('<div>' + makeUL(elem.sub) + '</div>');
    ...
    return html.join("\n");
}

http://jsfiddle.net/BvDW3/

Needs 2 to be adapted to your needs, but you got 1 the idea.

Score: 3

Pure ES6

var foo=(arg)=>
`<ul>
${arg.map(elem=>
    elem.sub?
        `<li>${foo(elem.sub)}</li>`
        :`<li>${elem.name}</li>`
    ).join('')}
</ul>`

JSON example

   var bar = [
  {
    name: 'Home'
  }, {
    name: 'About'
  }, {
    name: 'Portfolio'
  }, {
    name: 'Blog'
  }, {
    name: 'Contacts'
  }, {
    name: 'Features',
    sub: [
      {
        name: 'Multipage'
      }, {
        name: 'Options',
        sub: [
          {
            name: 'General'
          }, {
            name: 'Sidebars'
          }, {
            name: 'Fonts'
          }, {
            name: 'Socials'
          }
        ]
      }, {
        name: 'Page'
      }, {
        name: 'FAQ'
      }
    ]
  }
]
var result=foo(bar)

Your 'result' will be 1 valid HTML

Score: 2

This solution uses a single recursive function. I 8 simplified logic by using Array's map() prototype function.

$(function () {
    $("body").html(makeUnorderedList(getData().menu));
});

function makeUnorderedList(data, li) {
    return $('<ul>').append(data.map(function (el) {
        var li = li || $('<li>');
        if (el.id || el.link) li.append($('<a>', {
            text : el.id || el.link,
            href : '#' + (el.id || el.link),
            name : el.name
        }));
        if (el.sub) li.append(makeUnorderedList(el.sub, li));
        return li;
    }));
}

function getData() {
    return {
        menu: [{
            id: '0',
            sub: [{
                name: 'lorem ipsum 0-0',
                link: '0-0',
                sub: null
            }, {
                name: 'lorem ipsum 0-1',
                link: '0-1',
                sub: null
            }, {
                name: 'lorem ipsum 0-2',
                link: '0-2',
                sub: null
            }]
        }, {
            id: '1',
            sub: null
        }, {
            id: '2',
            sub: [{
                name: 'lorem ipsum 2-0',
                link: '2-0',
                sub: null
            }, {
                name: 'lorem ipsum 2-1',
                link: '2-1',
                sub: null
            }, {
                name: 'lorem ipsum 2-2',
                link: '2-2',
                sub: [{
                    name: 'lorem ipsum 2-2-0',
                    link: '2-2-0',
                    sub: null
                }, {
                    name: 'lorem ipsum 2-2-1',
                    link: '2-2-1',
                    sub: null
                }, {
                    name: 'lorem ipsum 2-2-2',
                    link: '2-2-2',
                    sub: null
                }, {
                    name: 'lorem ipsum 2-2-3',
                    link: '2-2-3',
                    sub: null
                }, {
                    name: 'lorem ipsum 2-2-4',
                    link: '2-2-4',
                    sub: null
                }, {
                    name: 'lorem ipsum 2-2-5',
                    link: '2-2-5',
                    sub: null
                }, {
                    name: 'lorem ipsum 2-2-6',
                    link: '2-2-6',
                    sub: null
                }]
            }, {
                name: 'lorem ipsum 2-3',
                link: '2-3',
                sub: null
            }, {
                name: 'lorem ipsum 2-4',
                link: '2-4',
                sub: null
            }, {
                name: 'lorem ipsum 2-5',
                link: '2-5',
                sub: null
            }]
        }, {
            id: '3',
            sub: null
        }]
    };
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Extension

Here 7 is a more dynamic approach. You get to choose 6 how your list items are rendered and what 5 the child property is. The mapFunc paramater is 4 a callback that gives you access to the 3 current child node and its parent.

The scope 2 of the mapFunc is the item. So you could use item as 1 well as this to refer to said item.

$(function () {
    $("body").html(makeUnorderedList(getData().menu, function(item, index, parent) {
      // `item` and `this` are the same.
      return $('<a>', {
            text : (item.id || item.link),
            href : '#' + (item.id || item.link),
            name : item.name,
            'data-index' : index
        });
    }, 'sub'));
});

function makeUnorderedList(data, mapFunc, childProp, li, parent) {
    return $('<ul>').append(data.map(function (el, index) {
        var li = li || $('<li>');
        li.append(mapFunc.call(el, el, index, parent));
        if (el[childProp]) {
            li.append(makeUnorderedList(el[childProp], mapFunc, childProp, li, data));
        }
        return li;
    }));
}
Score: 1

Fiddle

Code:

JS

var jsonstring = [{
  "id": '1',
  "children": [{
    "id": '2'
  }, {
    "id": '3',
    "children": [{
      "id": '4'
    }]
  }]
}, {
  "id": '5'
}];

 var htmlStr= recurse( jsonstring );
$('#test').append(htmlStr);


function recurse( data ) {
  var htmlRetStr = "<ul>"; 
  for (var key in data) { 
        if (typeof(data[key])== 'object' && data[key] != null) {
        var x=key*1;        
        if(isNaN(x)){
            htmlRetStr += "<li>" + key + ":<ul>";
            }
            htmlRetStr += recurse( data[key] );
            htmlRetStr += '</ul></li>';
        } else {
            htmlRetStr += ("<li>" + key + ': &quot;' + data[key] + '&quot;</li  >' );
        }
  };
  htmlRetStr += '</ul >';    
  return( htmlRetStr );
}

HtML

<div id="test"></div>

CSS

li ul ul li {
    padding-left: 10px;
}

li ul ul ul {
    padding: 0px;
}

0

Score: 1

I was searching for general parent child 7 element function and I saw these answers, and 6 I took some pieces of code from here and 5 there and made this function. I decided 4 to share my code as an answer, in case 3 someone like me will find this post when 2 he is searching for a general parent child 1 html element draw function:

function drawRecElements(arr, html, elements) {
    if (typeof (html) === 'undefined') {
        var html = '';
    }
    if (typeof (elements) === 'undefined') {
        var elements = {child: '<li>', childClose: '</li>', parent: '<ul>', parentClose: '</ul>'};
    }
    if (typeof (arr) === 'string') {
        return elements.child + arr + elements.childClose;
    } else if (typeof (arr) === 'object') {
        for (i in arr) {
            if (typeof (arr[i]) === 'string') {
                html += elements.parent + elements.child + i + elements.childClose + elements.child + arr[i] + elements.childClose + elements.parentClose;
            } else if(typeof (i) === 'string' && (isNaN(i))){
                html += elements.parent + elements.child + i + elements.childClose + elements.child + drawRecElements(arr[i],'',elements) + elements.childClose + elements.parentClose;
            } else if (typeof (arr[i]) === 'object') {
               html = drawRecElements(arr[i], html,elements);
            }
        }
    }
    return html;
}

https://jsfiddle.net/kxn442z5/1/

Score: 1

This is like a complete solution for generating 9 UL/LI recursively from JSON config, which 8 has customizable classes for each node and 7 support of expand and collapse events for 6 each node. This provides just a basic working 5 model, from which you ll be able to expand 4 and customize to your needs.

I found this 3 answer from https://techmeals.com/fe/questions/javascript/6/How-can-I-create-a-dynamic-tree-of-UL-and-LI-from-JSON-config

Example JSON config file:

var config = {
    "Menu-1-Level-1": {
        "label": "Menu-1-Level-1",
        "type": "treeView",
        "class": "Menu-1-Level-1",
        "children": [
            {
                label: "Menu-1-Level-2",
                type: "treeView",
                "class": "Menu-1-Level-2",
                children: [
                    {
                        label: "Menu-1-Level-3",
                        class: "Menu-1-Level-3"
                    }
                ]
            },
            {
                label : "Menu-2-Level-2",
                class: "Menu-2-Level-2"
            }
        ]
    },
    "Menu-2-Level-1": {
        "label": "Menu-2-Level-1",
        "type": "treeView",
        "class": "Menu-2-Level-1",
        "children": [
            {
                label: "Menu-1-Level-2",
                class: "Menu-1-Level-2",
                type: "treeView",
                children: [
                    {
                        label: "Menu-1-Level-3",
                        class: "Menu-1-Level-3"
                    }
                ]
            },
            {
                label : "Menu-2-Level-2",
                class : "Menu-2-Level-2"
            }
        ]
    }
};

HTML 2 Code:

<!DOCTYPE html>
<html>

<head>
    <title>Tree Menu</title>
    <script src="http://code.jquery.com/jquery-1.11.2.min.js" type="text/javascript"></script>
    <script src="tree.js" type="text/javascript"></script>
    <link href="tree.css" rel="stylesheet">
</head>

<body>
    <div class="treeContainer">
        <div class="tree"></div>
    </div>
    <script src="testPage.js" type="text/javascript"></script>
</body>

</html>

Tree.js

var tree;

tree = function (treeNodeParent, dataObj) {
    this.dataObj = dataObj;
    this.treeNodeParent = treeNodeParent;
    this.treeNode = $(document.createElement("ul")).addClass("treeNode");
};

tree.prototype.expandCollapse = function (e) {
    var target = $(e.currentTarget), parentLabel = target.parent();

    if (parentLabel.hasClass("collapsed")) {
        parentLabel.removeClass("collapsed").addClass("expanded");
    } else {
        parentLabel.addClass("collapsed").removeClass("expanded");
    }
};

tree.prototype.attachEvents = function () {
    var me = this;
    me.treeNodeParent.delegate(".collapsed label, .expanded label", "click", me.expandCollapse);
};

tree.prototype.attachMarkUp = function () {
    var me = this;
    me.treeNodeParent.append(me.treeNode);
};

tree.prototype.getEachNodeMarkup = function (nodeObj, rootNode, selector) {
    var selectedNode, i, me = this;

    if (nodeObj.children) {

        if (!selector) {
            selectedNode = rootNode;
        } else {
            selectedNode = rootNode.find(selector);
        }

        nodeObj.class = nodeObj.class ? nodeObj.class : "";
        selectedNode.append($.parseHTML("<li name=" + nodeObj.label + " class='collapsed " + nodeObj.class + "'>" + "<label>" + nodeObj.label + "</label>" + "<ul></ul></li>"));
        selector = selector + " li[name=" + nodeObj.label + "] > ul";

        for (i = 0; i < nodeObj.children.length; i = i + 1) {
            me.getEachNodeMarkup(nodeObj.children[i], rootNode, selector);
        }

    } else {
        nodeObj.class = nodeObj.class ? nodeObj.class : "";
        rootNode.find(selector).append($.parseHTML("<li name=" + nodeObj.label + " class='" + nodeObj.class + "'>" + "<label>" + nodeObj.label + "</label>" + "</li>"));
    }
};

tree.prototype.getTree = function () {
    var component, me = this;

    for (component in me.dataObj) {
        if (me.dataObj.hasOwnProperty(component)) {
            me.getEachNodeMarkup(me.dataObj[component], me.treeNode, "");
        }
    }
    me.attachMarkUp();
    me.attachEvents();
    return me.treeNode;
};

Tree.css

.treeNode .collapsed > ul, .collapsed > li {
    display: none;
}

.treeNode .expanded > ul, .expanded > li {
    display: block;
}

testPage.js

// the variable "config" is nothing but the config JSON defined initially. 
treeNode = new tree($('.treeContainer .tree'), config); 
treeNodeObj = treeNode.getTree();

Look at the 1 example provided at https://jsfiddle.net/3s3k3zLL/

Score: 0

An edit to an old answer brought this question 15 back up, and I think in modern JS this becomes 14 easier than many answers above.

Here is one 13 solution, using template strings and parameter 12 destructuring:

const buildMenu = (nodes) =>
  `<ul>${nodes.map(
    ({id, name, link, sub}) => sub
      ? `<li>${name ? `<a href="${link}">${name}</a>` : id}<div>${buildMenu(sub)}</div></li>`
      : `<li>${id || `<a href="${link}">${name}</a>`}</li>`
  ).join('')}</ul>`

var JSON = {menu: [{id: '0',sub: [{name: 'lorem ipsum 0-0',link: '0-0', sub: null}, {name: 'lorem ipsum 0-1',link: '0-1', sub: null}, {name: 'lorem ipsum 0-2',link: '0-2', sub: null}]}, {id: '1',sub: null}, {id: '2',sub: [{name: 'lorem ipsum 2-0',link: '2-0', sub: null}, {name: 'lorem ipsum 2-1',link: '2-1', sub: null}, {name: 'lorem ipsum 2-2',link: '2-2', sub: [{name: 'lorem ipsum 2-2-0',link: '2-2-0', sub: null}, {name: 'lorem ipsum 2-2-1',link: '2-2-1', sub: null}, {name: 'lorem ipsum 2-2-2',link: '2-2-2', sub: null}, {name: 'lorem ipsum 2-2-3',link: '2-2-3', sub: null}, {name: 'lorem ipsum 2-2-4',link: '2-2-4', sub: null}, {name: 'lorem ipsum 2-2-5',link: '2-2-5', sub: null}, {name: 'lorem ipsum 2-2-6',link: '2-2-6', sub: null}]}, {name: 'lorem ipsum 2-3',link: '2-3', sub: null}, {name: 'lorem ipsum 2-4',link: '2-4', sub: null}, {name: 'lorem ipsum 2-5',link: '2-5', sub: null}]}, {id: '3',sub: null}]}

const tree = buildMenu(JSON.menu);

document.getElementById('output').innerHTML = tree
<div id="output"></div>

Most of the complexity here 11 is simply in handling different formats 10 for nodes that have sub-arrays and those that 9 don't and for those with name/link properties and 8 those with just an id property. If the input 7 was a little more consistent, this would 6 be simpler still. Nonetheless, this is 5 not a horrible version. If you like to 4 work with string-based DOM solutions, it's 3 fairly clean.

Note that while modern features 2 make the solution cleaner, we could do the 1 same thing in ES5:

const buildMenu = (nodes) =>
  '<ul>' + nodes.map (
    (node) => node.sub
      ? '<li>' + (node.name ? '<a href="' + node.link + '">' + node.name + '</a>' : node.id) + '<div>' + buildMenu(node.sub) + '</div></li>'
      : '<li>' + (node.id || '<a href="' + node.link + '">' + node.name + '</a>') + '</li>'
  ) .join ('') + '</ul>'

More Related questions