Reputation: 439
I am trying to use use the following JSON data to create the following similar structure in a recursive inner function with not much luck, really need some help and so if anyone can assist please do. Thank you in advance.
<ul>
<li></li>
<li>
<a href=""></a>
<div>
<ul>
<li>
<a href=""></a>
<div>
....etc
</div>
</li>
</ul>
</div>
</li>
</ul>
the JSON data I am using is as follows:
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}
]
}
and the code I have created (incomplete, this is the brain teaser I need help on) is:
$(function(){
$.fn.dropdown = function(settings){
var that = this;
var settings = $.extend({}, $.fn.dropdown.defaults, settings);
var methods = {
isArray: function(o){
return Object.prototype.toString.call(o) === '[object Array]';
},
createDropdownCode: function(arr){
var menu = arr.menu;
var html = null;
var menusort = function(menu){
html = that;
that.find("li").each(function(idx){
var menuList = menu[idx].sub;
var baseContainer = $(this);
var count = -1;
var subsort = (function(){
count += 1;
return function(submenu, pb){
var subblock;
subblock = $("<div />").append('<ul />');
if(methods.isArray(submenu)){
for(var i=0;i<submenu.length;i++){
var l = $("<li />").append("<a href='"+ submenu[i].link +"'>"+ submenu[i].name +"</a>");
subblock.find('ul').append(l);
if(pb !== undefined && i == submenu.length-1){
pb.append(subblock)
}
if(methods.isArray(submenu[i].sub)){
subsort(submenu[i].sub, subblock.find('ul li').eq(i));
}
}
}
}
})()
subsort(menuList)
})
}
menusort(menu);
return null; //html !== null ? html.html() : null;
},
init: function(){
// filter through json
// create the div=>ul=>li
if(settings.jsonData === undefined || settings.jsonData === null){
console.warn('No JSON Data passed')
return;
}else{
if(!methods.isArray(settings.jsonData.menu)){
console.warn('No JSON Data passed')
return; // error, no data!
}
}
//var html = methods.createBlock(settings.jsonData.menu[0].sub);
var html = methods.createDropdownCode(settings.jsonData);
//console.log(html)
}
}
methods.init();
return that;
}
$.fn.dropdown.defaults = {
jsonData: null
}
})
$('#menu').dropdown({
jsonData: JSON
});
integrated code used, thanks to the individual that gave a close enough answer - Although will study the others.
$.fn.dropdown = function(settings){
var that = this;
var settings = $.extend({}, $.fn.dropdown.defaults, settings);
var methods = {
createDropDownCode: function(arr){
// loop through li's of primary menu
that.find("li").each(function(idx){
$(this).append( menusort(arr.menu[idx].sub) );
function menusort(data){
if(data !== null)
var html = "<div><ul>";
for(item in data){
html += "<li>";
if(typeof(data[item].sub) === 'object'){
html += "<a href='" + data[item].link + "'>" + data[item].name + "</a>";
if($.isArray(data[item].sub))
html += menusort(data[item].sub);
}
html += "</li>"
}
if(data !== null)
html += "</ul></div>";
return html;
}
})
},
init: function(){
var html = methods.createDropDownCode(settings.jsonData);
}
}
methods.init();
}
Upvotes: 8
Views: 25447
Reputation: 50797
An edit to an old answer brought this question back up, and I think in modern JS this becomes easier than many answers above.
Here is one solution, using template strings and parameter 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 is simply in handling different formats for nodes that have sub
-arrays and those that don't and for those with name
/link
properties and those with just an id
property. If the input was a little more consistent, this would be simpler still. Nonetheless, this is not a horrible version. If you like to work with string-based DOM solutions, it's fairly clean.
Note that while modern features make the solution cleaner, we could do the 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>'
Upvotes: 0
Reputation: 33061
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 valid HTML
Upvotes: 3
Reputation: 51
This is like a complete solution for generating UL/LI recursively from JSON config, which has customizable classes for each node and support of expand and collapse events for each node. This provides just a basic working model, from which you ll be able to expand and customize to your needs.
I found this 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 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 example provided at https://jsfiddle.net/3s3k3zLL/
Upvotes: 1
Reputation: 8756
I was searching for general parent child element function and I saw these answers, and I took some pieces of code from here and there and made this function. I decided to share my code as an answer, in case someone like me will find this post when he is searching for a general parent child 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/
Upvotes: 1
Reputation: 724
Code:
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 + ': "' + data[key] + '"</li >' );
}
};
htmlRetStr += '</ul >';
return( htmlRetStr );
}
<div id="test"></div>
li ul ul li {
padding-left: 10px;
}
li ul ul ul {
padding: 0px;
}
Upvotes: 1
Reputation: 48630
This solution uses a single recursive function. I 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>
Here is a more dynamic approach. You get to choose how your list items are rendered and what the child property is. The mapFunc
paramater is a callback that gives you access to the current child node and its parent.
The scope of the mapFunc
is the item. So you could use item
as 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;
}));
}
Upvotes: 2
Reputation: 4259
You can try this recursive function I've 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 use it like that: var html = buildList(JSON.menu, false);
I believe it is faster because it's in pure JavaScript, and it doesn't create text nodes or DOM elements for every iteration. Just call .innerHTML
or $('...').html()
at the end when you're done instead of adding HTML immediately for every menu.
JSFiddled: http://jsfiddle.net/remibreton/csQL8/
Upvotes: 17
Reputation: 214959
Make two functions makeUL
and makeLI
. makeUL
calls makeLI
on each 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");
}
Needs to be adapted to your needs, but you got the idea.
Upvotes: 6