Using dijit.Tree with the new Dojo Object Store (dojo.store)

A colleague asked me today how a dijit.Tree can be used with the new dojo.store architecture, which will supersede the legacy dojo.data architecture. Every tutorial he found on the web used ItemFileReadStore, which implements the old standard. Later that day I stumbled upon a similar question on StackOverflow and realized that a short write-up might benefit others as well.

A dijit.Tree

First things first. We need an html-file that pulls in dojo and the css-files for a theme – in this example we’ll use the claro theme. Nothing fancy here.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
		<title>Tree structure using the new Dojo Object Store</title>
 
		<link type="text/css" rel="stylesheet" media="screen" 
			href="http://ajax.googleapis.com/ajax/libs/dojo/1.6.1/dijit/themes/claro/claro.css" />
 
		<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.6.1/dojo/dojo.xd.js" type="text/javascript"></script>
		<script src="js/index.js" type="text/javascript"></script>
	</head>
 
	<body class="claro">
		<h1>Tree structure using the new Dojo Object Store</h1>
		<div id="treeNode"></div>
	</body>
</html>

As we’re (always) coding unobtrusively, all JavaScript/Dojo functionality is contained within an external file – in this case js/index.js.

First, we require all the packages we need. In this example, we’ll be using the Memory Store, as it doesn’t require any server side code. We could of course have used any other dojo.store, e.g. the RESTful dojo.store.JsonRest and the example would still be valid. The dojo.data.ObjectStore will act as a wrapper – you give it an object store, and it returns, wrapped as a legacy data store.

dojo.require("dijit.Tree");
dojo.require("dojo.store.Memory");
dojo.require("dojo.data.ObjectStore");

 

We define the Object Store to a simple tree structure:

var objectStore = new dojo.store.Memory({data: [
	{id:"A", label: "Root", children:[
		{id:"1", label:"First"},
		{id:"2", label:"Second", children:[
			{id:"2a", label:"First child"},
			{id:"2b", label:"Second child"},
		]},
		{id:"3", label:"Third"},
		{id:"4", label:"Fourth"},
	]}
]});

 

dijit.Tree expects a TreeModel, which in turn expects a Data Store.

var dataStore = new dojo.data.ObjectStore({objectStore: objectStore});
var treeModel = new dijit.tree.TreeStoreModel({store: dataStore});
 
var tree = new dijit.Tree({
	model: treeModel
}, 'treeNode');
tree.startup();

 

And that’s basically it. You’d of course want to wrap the code in a dojo.addOnLoad, which would make the entire index.js look like this:

dojo.require("dijit.Tree");
dojo.require("dojo.store.Memory");
dojo.require("dojo.data.ObjectStore");
 
dojo.addOnLoad(function() {
 
	var objectStore = new dojo.store.Memory({
		data: [
			{id:"A", label: "Root", children:[
				{id:"1", label:"First"},
				{id:"2", label:"Second", children:[
					{id:"2a", label:"First child"},
					{id:"2b", label:"Second child"},
				]},
				{id:"3", label:"Third"},
				{id:"4", label:"Fourth"},
			]}
		]
	});
 
	var dataStore = new dojo.data.ObjectStore({objectStore: objectStore});
	var treeModel = new dijit.tree.TreeStoreModel({store: dataStore});
 
	var tree = new dijit.Tree({
		model: treeModel
	}, 'treeNode');
	tree.startup();
});

 

If you point your browser to the index.html file, this is what you’d see:
The browser's view of a dijit.Tree using the new dojo.store architecture
You could also take a look at this live demo.

Using a server to host the data – the JsonRest object store

So, now we’ve setup a dijit.Tree using dojo.store.Memory as the data source. While this is great for a quick mock-up Tree, you’d have to generate the JS-files dynamically if the data changes over time. And should you ever want to let your users manipulate the data through the tree – e.g. drag’n’drop or rename nodes – the Memory Store simply will not suffice.

Fortunately, changing the Memory Store to a dojo.store.JsonRest and instead using a server to host the data is quite trivial. Instead of supplying the data directly – as was the case with the Memory Store – we just supply a URL, from which we’ll get the data (in a RESTful way).

dojo.require("dijit.Tree");
dojo.require("dojo.store.JsonRest");
dojo.require("dojo.data.ObjectStore");
 
dojo.addOnLoad(function() {
	var objectStore = new dojo.store.JsonRest({target: "<URL-to-JSON-data>"});
 
	var dataStore = new dojo.data.ObjectStore({objectStore: objectStore});
	var treeModel = new dijit.tree.TreeStoreModel({store: dataStore});
 
	var tree = new dijit.Tree({
		model: treeModel
	}, 'treeNode');
	tree.startup();
});

Which URL should we be using? The simplest case, would of course be just giving it a URL to a static json-file on the server. Even though this would work quite nicely to just show the Tree, it’s hardly an improvement to the Memory Store. Like stated above – we want to generate the JSON-data dynamically. And to allow users to modify the data, we need full REST-support – i.e. not only GET a file, but also POST, PUT and DELETE support. Any server that can send JSON-encoded objects in a RESTful way would do.

Next, we’ll take a look at how to accomplish this using PHP with ZendFramework.

Closing remarks

It should be noted that this solution is “the easy way” to do it, meaning least time to implement. Since the TreeModel expects a Data Store, we used a wrapper to morph our Object Store into a Data Store. This step naturally adds some overhead to our solution. For performance reasons, you might want to create your own TreeModel, which takes an object store directly and thus, eliminates the need for a wrapper. Or you could always wait for the Dojo Community to add one to the toolkit. If there’s an interest I’ll put together another write-up on how to create your own TreeModel that eliminates the need for a wrapper.

This entry was posted in JavaScript and tagged , , , . Bookmark the permalink.
  • http://www.yahoo.com/ Rope

    Boy that ralely helps me the heck out.

  • sou

    thanks for the blog. Dojo has so little doc that they replicate the same example from the website a 1000 times which directly uses the jsonrest store and does not go step by step like this. thanks much.

  • Taneeda

    Very good example, thank you very much :)

  • Taneeda

    Would be great, if you write more articles about dijit.tree with json and/or usage without the wrapper…

  • Taneeda

    Could you give an example how to load data from REST into this tree, by using the DataController from you other tutorial (http://blog.respondify.se/2011/09/creating-a-dojo-store-jsonrest-backend-server-using-zendframework-php)

  • Taneeda

    Hi guys,

    I have done some experiments and would share my

    I have a working tree which gets his data over REST from a database. The following code is at [project]/public/js/tree.js (the file is included in layout.phtml):


    require(['dojo/store/JsonRest', 'dijit/Tree', 'dojo/data/ObjectStore',
    'dijit/tree/TreeStoreModel', 'dojo/store/Memory', 'dojo/domReady!'],
    function(JsonRest, Tree, ObjectStore, TreeStoreModel, Memory, dom) {
    /**
    * TdvTree loads data from REST DataController
    */
    // tdvProjects = new JsonRest({
    // target: "http://localhost/QSTemplateDojo/public/data.json"
    // });
    tdvProjects = JsonRest({
    target:"data/",
    mayHaveChildren: function(object){
    // console.log(object.children);
    // see if it has a children property
    return 'children' in object;
    },
    getChildren: function(object, onComplete, onError){
    // retrieve the full copy of the object
    this.get(object.id).then(function(fullObject){
    // copy to the original object so it has the children array as well.
    object.children = fullObject.children;
    // now that full object, we should have an array of children
    onComplete(fullObject.children);
    }, function(onError){
    // an error occurred, log it, and indicate no children
    onComplete([]);
    });
    },
    getRoot: function(onItem, onError){
    // get the root object, we will do a get() and callback the result
    this.get('root').then(onItem, onError);
    },
    getLabel: function(object){
    return object.label;
    }
    });
    tdvTree = new Tree({ // create a tree
    model: tdvProjects // give it the model
    }, "tdvTree"); // target HTML element's id
    tdvTree.startup();
    tree.on("dblclick", function(object){
    object.name = prompt("Enter a new name for the object");
    usGov.put(object);
    }, true);
    });

    I implemented a different DataController, based on the example at http://blog.respondify.se/2011/09/creating-a-dojo-store-jsonrest-backend-server-using-zendframework-php/#comment-32:

    class DataController extends Zend_Rest_Controller {

    public function _init() {
    $this->_helper->viewRenderer->setNoRender(true);
    }

    public function getAction() {
    $this->_forward('index');
    }

    public function indexAction() {
    /**
    * Request all childrens of the delivered id and return them to the
    * caller...
    */

    $parentId = $this->_getParam('id');
    $saveParentId = $parentId;
    if($parentId == 'root') {
    $parentId = Application_Model_Tdv::getDb()->fetchRow(
    Application_Model_Tdv::getSelectAll()->where('n.lft = ?', 1));
    $parentId = $parentId['id'];
    }

    $parent = Application_Model_Tdv::getItemById($parentId);
    $childs = Application_Model_Tdv::getChilds($parentId);

    $data = array('id' => $saveParentId, 'label' => $parent['name']);
    foreach ($childs as $child) {
    if ($child['offspring'] > 0) {
    $data['children'][] = array(
    'id' => $child['id'],
    'label' => $child['name'],
    'children' => true
    );
    } else {
    $data['children'][] = array(
    'id' => $child['id'],
    'label' => $child['name']
    );
    }
    }

    $this->getHelper('json')->sendJson($data);
    }

    }

    Maybe, this helps somebody…

  • Taneeda
  • http://blog.respondify.se/author/coa/ Christian O. Andersson

    Closed the tag for you :)

    I looked through your code, and I really like that you’re using the new AMD require style! I really should update the blog to reflect the new recommended way to require dojo classes, since Dojo 1.7.

    But wouldn’t this code be better suited as a comment to http://blog.respondify.se/2011/09/creating-a-dojo-store-jsonrest-backend-server-using-zendframework-php/ where I discuss how to implement a RESTful client-server setup?

    Also, why create the JSON-data structure in the controller? I would think the model be better suited for that job?

  • Sp3igel

    I am just starting with Dojo and this really helped clarify what the official tutorials left out. Thanks!