Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a Nested List with an Object Array

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.

like image 494
Lailoken Avatar asked Jul 25 '13 23:07

Lailoken


Video Answer


2 Answers

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'));

jsFiddle

like image 117
Daniel Gimenez Avatar answered Oct 07 '22 14:10

Daniel Gimenez


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

like image 44
Oriol Avatar answered Oct 07 '22 14:10

Oriol