I have a set of SQL data which will change frequently and I need to create a nested, unordered list from that on a regular basis. I have converted the SQL data into an object array, but I'm stuck on the logic for creating the unordered list from that.
I tried making a recursive function, but I'm not experienced enough with those, so
The data contains these fields:
ID Category Name ParentID
So, each category has an ID and a ParentID (which matches the ID of another category). Root categories all have the same dummy ParentID.
The javascript object has the same properties. Here's an example:
var Categories = [
new Category(1, 'Root', 0),
new Category(2, 'Cat1', 1),
new Category(3, 'Cat2', 2),
new Category(4, 'Cat3', 5),
new Category(5, 'Cat4', 1),
new Category(6, 'Cat5', 5),
new Category(7, 'Cat6', 5),
new Category(8, 'Cat7', 1),
new Category(9, 'Cat8', 2),
new Category(10, 'Cat9', 1),
new Category(11, 'Cat10', 10),
new Category(12, 'Cat11', 1),
new Category(13, 'Cat12', 8)
]
I need to use that object array to make an unordered list that would look like this:
<ul>
<li>Cat1
<ul>
<li>Cat2</li>
<li>Cat8</li>
</ul>
<li>Cat4
<ul>
<li>Cat3</li>
<li>Cat5</li>
<li>Cat6</li>
</ul>
</li>
<li>Cat7
<ul>
<li>Cat12</li>
</ul>
</li>
<li>Cat8</li>
<li>Cat9
<ul>
<li>Cat10</li>
</ul>
</li>
<li>Cat11</li>
</ul>
Currently, the deepest my data goes is 3 tiers, but I would like to be able to have the script do any number of tiers.
jQuery is OK for this.
Here is a class based approach. The depth is unlimited. The only requirement is that a parent must exist before a child is added.
// create class
// parent is optional Category
var Category = function (id, name, parent) {
this.id = id;
this.name = name;
this.parent = null;
this.children = [];
if (parent) {
parent.add(this);
}
};
Category.prototype.root = function() {
if (this.parent) return this.parent.root();
return this;
}
// find by id
Category.prototype.find = function (id) {
if (this.id == id) return this;
var found;
for (var i = 0, il = this.children.length; i < il; i++) {
if (found = this.children[i].find(id)) return found;
}
return null;
};
// create relationship
Category.prototype.add = function (cat) {
cat.parent = this;
this.children.push(cat);
};
// render list for item
Category.prototype.renderList = function ($parentElem) {
var $nameElem = $('<li>' + this.name + '</li>').appendTo($parentElem);
if (this.children.length) {
this.renderChildren($('<ul />').appendTo($nameElem))
}
}
// create child elements and add them to the parent
Category.prototype.renderChildren = function ($parentElem) {
for (var i = 0, il = this.children.length; i < il; i++) {
this.children[i].renderList($parentElem);
}
}
function createCategory(id, name, parentId) {
rootCat.find(parentId).add(new Category(id, name));
}
// add items
var rootCat = new Category(1, 'root');
createCategory(2, 'Cat1', 1);
createCategory(3, 'Cat2', 2);
createCategory(4, 'Cat3', 3);
createCategory(14, 'Cat3.5', 4);
createCategory(5, 'Cat4', 1);
createCategory(6, 'Cat5', 5);
createCategory(7, 'Cat6', 5);
createCategory(8, 'Cat7', 1);
createCategory(9, 'Cat8', 2);
createCategory(10, 'Cat9', 1);
createCategory(11, 'Cat10', 10);
createCategory(12, 'Cat11', 1);
createCategory(13, 'Cat12', 8);
// render
rootCat.renderChildren($('#cats'));
Demo: http://jsfiddle.net/4JpsW/
function Category(id, categoryName, parentId){
this.id = id;
this.categoryName = categoryName;
this.parentId = parentId;
}
var categories = [
new Category(1, 'Root', 0),
new Category(2, 'Cat1', 1),
new Category(3, 'Cat2', 2),
new Category(4, 'Cat3', 5),
new Category(5, 'Cat4', 1),
new Category(6, 'Cat5', 5),
new Category(7, 'Cat6', 5),
new Category(8, 'Cat7', 1),
new Category(9, 'Cat8', 2),
new Category(10, 'Cat9', 1),
new Category(11, 'Cat10', 10),
new Category(12, 'Cat11', 1),
new Category(13, 'Cat12', 8)
];
categories.sort(function(a,b){
return a.parentId < b.parentId ? -1 : (a.parentId > b.parentId ? 1 : 0);
});
var root = document.createElement('ul'),
currentParentId=1,
currentParentUl=root;
for(var i=1; i < categories.length; ++i){
if(categories[i].parentId !== currentParentId){
currentParentId = categories[i].parentId;
currentParentUl = document.createElement('ul');
root.getElementsByClassName('category_'+currentParentId)[0]
.appendChild(currentParentUl);
}
currentParentUl.innerHTML += '<li class="category_'+categories[i].id+'">'+categories[i].categoryName+'</li>';
}
document.body.appendChild(root);
Note 1: Like @DanielGimenez's code, the depth is unlimited and a parent must exist before a child is added.
Note 2: My code uses the native functions sort
and getElementsByClassName
to do the most expensive part (sorting and finding) to have better performance.
Note 3: I have used classes instead of ids to avoid conflicts with other parts of your page. But if that's not a problem, using ids will increase the speed.
You can compare performance here: http://jsperf.com/creating-a-nested-list-with-an-object-array
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With