This is going to be a bit long winded, but here goes:
I am writing two separate web applications: one for generating a JSON object with a bunch of data and formatting information and another for turning that JSON object into a nice formatted HTML page for viewing. The particular issue I am having currently is with nested lists.
For the viewing application, I wrote a recursive function that will parse the supplied javascript object and create the HTML for a list with an arbitrary magnitude of nested elements.
function createUnorderedList(object) {
var ul = $('<ol>');
for (var i = 0; i < object.length; i++) {
if (object[i].children === undefined) {
ul.append($('<li>').text(object[i].value));
} else {
ul.append(
$('<li>').html(
object[i].value + "<ul>" + createUnorderedList(object[i].children) + "</ul>"));
}
}
return ul.html();
}
The jsfiddle for that function can be seen here: jsfiddle: Recursive HTML List Generation
Now, I need to create a function for the JSON creation side of things. Having tried and failed to wrap my brain around how to do this recursively, I settled upon a more linear function, seen here:
function createList( array ) {
var json = { "value": [] } ;
var k = 0 ;
if(array[array.length-1] === "") array.pop() ;
for(var i=0; i<array.length; i++ ){
json.value.push( { "value": array[i] } ) ;
for(var j=i+1; j<array.length; j++) {
if(array[j].charAt(0) === '\t') {
array[j] = array[j].replace( /\t/, '' ) ;
i++;
if(json.value[k].children === undefined)
$.extend( json.value[k], { children : [] } ) ;
json.value[k].children.push( { "value": array[j] } ) ;
} else { k++; j=array.length; }
}
}
return json ;
}
jsfiddle: JSON List Generation.
This function can only handle a nested list of one order of magnitude, which should suffice for now but is not ideal. How can I make this function capable of generating a json object with an arbitrary magnitude of nested elements?
Thank you.
2 Answers 2
Edit:: I misunderstood your question at first.
This is how you can parse a ul hierarchy into an object (view the JS console to see what is parsed out):
function itemsToHierarchy(node) {
return $(node).find('> ul > li').toArray().map(function (item) {
return {
value: item.childNodes[0].textContent,
children: itemsToHierarchy(item)
};
});
}
console.log(itemsToHierarchy($('#list')));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
<!-- leaving the HTML scrunched up to save space -->
<div id="list"><ul><li>Item 1<ul><li>Item 1.1</li></ul></li><li>Item 2</li><li>Item 3<ul><li>Item 3.1</li><li>Item 3.2<ul><li>Item 3.2.1<ul><li>Item 3.2.1.1</li></ul></li></ul></li></ul></li><li>Item 4<ul><li>Item 4.1</li><li>Item 4.2</li></ul></li><li>Item 5<ul><li>Item 5.1</li></ul></li></ul></div>
At first I thought you were asking for help with your createList() function. Here is a recursive version that works for any nesting depth:
function getItems( array, depth ) {
depth = depth || 0;
var items = [],
item = array.shift();
while (item && countTabs(item) === depth) {
items.push({
value: item.replace(/^\t*/, ''),
children: getItems(array, depth + 1)
});
item = array.shift();
}
if (item) { // not at this depth
array.unshift(item);
}
return items;
}
function countTabs(value) {
return value.match(/^\t*/)[0].length;
}
The value returned from that can be used to build the HTML as follows:
function createUnorderedList(items) {
return $('<ul>').append($.map(items, function (item) {
var li = $('<li>').text(item.value);
if (item.children.length) {
li.append(createUnorderedList(item.children));
}
return li;
}));
}
Full solution: https://jsfiddle.net/1930t20f/
4 Comments
console.log(itemsToHierarchy($('#list'))); with $(document).append('<code>'+itemsToHierarchy($('#list'))+'</code>'); (or similar)After some hard thinking I found a solution.
First I changed your code to create a <li> for every value. The children are the a <ul> after the <li>.
function createUnorderedList(object) {
var ul = $('<ul>');
for (var i = 0; i < object.length; i++) {
ul.append($('<li>').text(object[i].value));
if (object[i].children) {
ul.append($('<ul>').html(createUnorderedList(object[i].children)));
}
}
return ul.html();
}
Then I created a function that searches recursively all elements. The <ul> is the child of the <li> before it.
var createJSONRecursive = function(elem, arr) {
var childNodes = elem.childNodes;
var nodeTmp;
for (var i = 0; i<childNodes.length; i++) {
var node = {};
var tagName = childNodes[i].tagName;
if (tagName == 'LI') {
node.value = childNodes[i].innerHTML;
if (i < childNodes.length - 1 && childNodes[i+1].tagName == 'UL') {
nodeTmp = node;
}
else {
arr.push(node);
}
}
else if (tagName == 'UL') {
nodeTmp.children = [];
createJSONRecursive(childNodes[i], nodeTmp.children);
arr.push(nodeTmp);
}
}
}
var createJSON = function(elem) {
var json = {value:[]}
createJSONRecursive(elem, json.value);
return json;
}
var json = createJSON(document.getElementById('div'));
Here is the fiddle: https://jsfiddle.net/8qc2mcyz/2/
2 Comments
<ul>s directly inside other <ul>s, which is not technically valid HTML.
Array.prototype.reduce(). SeemakeOrderedList3() in my example.