Displaying and Styling a Read-only Dojo Tree Control

In this tutorial, we are going to be using the Dojo toolkit – a popular JavaScript tookit shipped with the IBM Feature Pack for Web 2.0 and Mobile. Specifically, we are going to be using the dijit.Tree class from the Dojo toolkit to display a simple Dojo read-only tree that can display information from a hierarchy of data. We’ll also style the tree, showing how to style different levels of the tree differently.

We’ll start out with a basic JavaScript object that contains some data we want to display. It’s hierarchical in nature – arrays inside arrays. In this example, we assume we are running an online retailer, and we want to display all the customers we have, and for each, all the orders they have made in the last month, then within those, each of the items in the order. Our object looks like this:

var customerOrderHistory = [
    {
        "name": "Fred Bloggs",
        "customerNum": 23,
        "orders": [
            {
                "orderNum": "0001",
                "items": [
                    {
                        "description": "Gone with the Wind (DVD)",
                        "itemNum": 56
                    },
                    {
                        "description": "Oxford English Dictionary",
                        "itemNum": 78
                    }
                ]
            },
            {
                "orderNum": "0002",
                "items": [
                    {
                        "description": "Whittaker's Alamanac",
                        "itemNum": 90
                    }
                ]
            }
        ]
    },
    {
        "name": "David Smith",
        "customerNum": 45,
        "orders": [
            {
                "orderNum": "0003",
                "items": [
                    {
                        "description": "King James Bible",
                        "itemNum": 112
                    }
                ]
            }
        ]
    }
];

Before we can create a Dojo Tree to display this data, we need to create a Tree Model. In our case, we’re going to use the dijit.Tree.ForestStoreModel to do this, as our tree has multiple roots (customers).

In turn, in order to create this tree model, we in turn need to get place our items into a Dojo data store (we’re going to use the dojo.data.ItemFileReadStore, as it’s well placed for handling in-memory data). To create this data store, we first need a data structure where each node and leaf in the tree has a consistent field name for its identifier (the internal representation of that node or leaf) – which must be unique across the entire tree – and a consistent field name for its label (what will be displayed on the screen for that node or leaf). Each node also needs to have a consistently named array that contains its child elements. Therefore, we need to write a data restructuring function. Our looks like this:

_restructureCustomerData = function(data) {
        var newData = dojo.map(data, dojo.hitch(this, function(customer) {
            var newCustomer = {};

            newCustomer.id = customer.customerNum;
            newCustomer.name = customer.name;

            newCustomer.children = dojo.map(customer.orders, dojo.hitch(this, function(idPrefix, order) {
		var newOrder = {};

		newOrder.id = idPrefix + "_" + order.orderNum;
		newOrder.name = order.orderNum;

		newOrder.children = dojo.map(order.items, dojo.hitch(this, function(idPrefix, item) {
		    var newItem = {};

		    newItem.id = idPrefix + "_" + item.itemNum;
		    newItem.name = item.description;

		    return newItem;
		    }, newOrder.id));

		return newOrder;
		}, newCustomer.id));

            return newCustomer;
        }));

        return newData;
    }

This will walk through the object tree of our initial data, and at each level will use the dojo.map function to create an entirely new resultant object tree, that consistently at each level uses the field names id (for the identifier) and name (for the label). Each non-leaf node also has a field called children that contains the child elements. For illustration, the resultant object looks like this:

[
    {
        "id": 23,
        "name": "Fred Bloggs",
        "children": [
            {
                "id": "23_0001",
                "name": "0001",
                "children": [
                    {
                        "id": "23_0001_56",
                        "name": "Gone with the Wind (DVD)"
                    },
                    {
                        "id": "23_0001_78",
                        "name": "Oxford English Dictionary"
                    }
                ]
            },
            {
                "id": "23_0002",
                "name": "0002",
                "children": [
                    {
                        "id": "23_0002_90",
                        "name": "Whittaker's Alamanac"
                    }
                ]
            }
        ]
    },
    {
        "id": 45,
        "name": "David Smith",
        "children": [
            {
                "id": "45_0003",
                "name": "0003",
                "children": [
                    {
                        "id": "45_0003_112",
                        "name": "King James Bible"
                    }
                ]
            }
        ]
    }
]

We can now use this object as the input to the creation of a data store:

var store = new dojo.data.ItemFileReadStore({
        data: {
            "identifier": "id",
            "label": "name",
            "items": restructuredData
        }
    });

And then in turn a store model:

var treeModel = new dijit.tree.ForestStoreModel({
        store: store,
        rootId: "root",
        rootLabel: "Customers",
        childrenAttrs: ["children"]
    });

Assuming that have an HTML div on our page with an id of “_treeHolder”, we can now create the tree and attach it to that empty div:

this._tree = new dijit.Tree({model: treeModel, showRoot: false}, dojo.byId("_treeHolder"));

The tree will now display successfully. With some items expanded, it looks like this:

Applying styles to the tree

There are many ways we can apply CSS styling to the tree. However, a category of advanced techniques involves overriding the tree class itself to add different CSS classes to different levels of the tree, therefore enabling different styles at different depths. We start out by altering the _restructureCustomerData function slightly to insert metadata about each object, specifically which CSS class we want to apply to each level in the tree:

 _restructureCustomerData = function(data) {
        var newData = dojo.map(data, dojo.hitch(this, function(customer) {
            var newCustomer = {};

            newCustomer.id = customer.customerNum;
            newCustomer.name = customer.name;
	    newCustomer.cssClass = "customer";

            newCustomer.children = dojo.map(customer.orders, dojo.hitch(this, function(idPrefix, order) {
		var newOrder = {};

		newOrder.id = idPrefix + "_" + order.orderNum;
		newOrder.name = order.orderNum;
	        newOrder.cssClass = "order";

		newOrder.children = dojo.map(order.items, dojo.hitch(this, function(idPrefix, item) {
		    var newItem = {};

		    newItem.id = idPrefix + "_" + item.itemNum;
		    newItem.name = item.description;
                    newItem.cssClass = "description";

		    return newItem;
		    }, newOrder.id));

		return newOrder;
		}, newCustomer.id));

            return newCustomer;
        }));

        return newData;
    }

We now extend the dijit.Tree class itself to create our own tree class, overriding the getRowClass() function. This allows us to assign a custom CSS class to each row, based on the data we’ve just inserted into the data store:

    dojo.declare("ibm.dijit.examples.LevelSensitiveTree", [ dijit.Tree ], {
	getRowClass: function(item, opened) {
	    if(item.id !== "root") {
		return this.model.store.getValue(item, "cssClass");
            }
	}
    });

We can then use this new tree class in the same way as the regular dijit.Tree class. The resultant tree looks like this:

In this article you have hopefully learnt some practical lessons on how to use and style Dojo trees based on raw data.