dijit.Tree - server fetch on node expansion

Here’s an example of a Tree that pulls data from the server on child node expansion (clicking the “+” next to a node that has children) - dojo 1.1 or better required:

My application for this tree is to list the fields I have in objects I call plugins in my CMS. These fields are similar but not exactly the same as database fields. I’m using a tree because plugins can relate to other plugins and I want to show these relationships in a top-down heirarchical fashion. Eventually users can select these fields to do many interesting things, but that’s for later. This example is for a plugin called a Company that has fields like name, city, state, zip, etc.

First - the JSON that’s returned for the company plugin (id = 5):

{
   label: 'name',
   identifier: 'id',
   items: [
      { id:'1', name:'company_id', type:'item' },
      { id:'2', name:'name', type:'item' },
      { id:'3', name:'phone', type:'item' },
      { id:'4', name:'address', type:'item' },
      { id:'5', name:'city', type:'item' },
      { id:'5', name:'state', type:'item' },
      { id:'6', name:'zip', type:'item' },
      { id:'7', name:'company_contacts', type:'item', plugin_id:'12', children: [
         { id:'7_1', name:'fieldid', type:'sub'}
         ]
      },
      { id:'8', name:'company_products', type:'item', plugin_id:'6', children: [
         { id:'8_1', name:'fieldid', type:'sub'}
         ]
      }
   ]
}

This basically lists the different fields for my Company plugin object in my CMS app. It starts with basic fields such as name, city, state, etc. At the bottom though there are two fields that associate/relate this plugin to other plugins, in this case a company is related to contacts and products.

The fact that the last two fields have a children attribute in the JSON will put the expando +/- node next to these items in the tree. The actual children values in the JSON do not matter, they will be replaced on clicking the expando node. The important part of the JSON for these last two is the piece called “plugin_id”. This tells me which plugin in my CMS these fields relates to (so I have a Contacts and Products plugin in the CMS as well with their own set of fields).

The JSON for plugin_id 6 (Products) looks like this:

{
   label: 'name',
   identifier: 'id',
   items: [
      { id:'1', name:'product_id', type:'item' },
      { id:'2', name:'name', type:'item' },
      { id:'3', name:'description', type:'item' },
      { id:'4', name:'product_company', type:'item', plugin_id:'5', children: [
         { id:'4_1', name:'fieldid', type:'sub'}
         ]
      },
      { id:'5', name:'product_keys', type:'item', plugin_id:'7', children: [
         { id:'5_1', name:'fieldid', type:'sub'}
         ]
      },
      { id:'6', name:'serial_number', type:'item' }
   ]
}

Same exact kind of data as before - I just want these items to appear under the “company_products” node when I expand. One note: I have a link BACK to the original company plugin via product_company field. This is precisely why I cannot build the full JSON for this tree as I would get in a situation where I have an infinite loop. I don’t care if the user wants to expand the products node, then that node’s company node, then that node’s products node, etc etc. I basically have a tree with an infinite possible number of child nodes.

Moving on - how to make this thing work. Let’s just show the code and I’ll explain.

<script type="text/javascript" language="Javascript">
	dojo.provide("my.ForestStoreModel");
	dojo.declare("my.ForestStoreModel", dijit.tree.ForestStoreModel, {
		getChildren: function(parentItem, complete_cb, error_cb) {
			if (parentItem.root == true) {
				// get top level nodes for this plugin id 
				pid = 5;
			}
			else {
				pid = this.store.getValue(parentItem, 'plugin_id');
			}
			this.store.fetch({ query: {plugin_id: pid}, 
							   onComplete: complete_cb, 
							   onError: error_cb});
 
			// Call superclasses' getChildren
			return this.inherited(arguments);
		}
	});
</script>
 
<div dojoType="dojox.data.QueryReadStore"
  	url="/get_plugin_fields.php" 
  	jsId="treeStore"></div>
 
 
<div dojoType="my.ForestStoreModel"
	store="treeStore"
	rootId="company"
	rootLabel="Company"
	query="{ id: '*' }"
	childrenAttrs="children"
	jsId="treeModel"></div>
 
 
<div dojoType="dijit.Tree" 
	  model="treeModel" 
	  jsId="itemTree" 
	  persist="false">
	<script type="dojo/method" event="onClick" args="item">
		alert('you clicked: ' + treeStore.getLabel(item));
	</script>
</div>

The first thing I’m doing is creating a customized ForestStoreModel which is provided by the Tree class. There is also a TreeStoreModel but my JSON essentially has no root data in the items array. ForestStoreModel is made for this situation where there is no apparent root or there are multiple root nodes. Bill has a nice description of these store models in this blog entry. Using ForestStoreModel means that I basically need to provide a fake root node and treat all the items in my JSON as children of this psuedo-root. Other than disliking this on principle it has no adverse affect.

Within my custom ForestStoreModel I override the getChildren method so I can define how my child data is fetched from the server. By checking if parentNode.root is null I can determine that this is the top-level fetch, in which case I use 5 as my hard-coded plugin id for the company plugin object. Otherwise this is an expansion of a child node and I pull the value of the plugin_id for the node being expanded. This plugin_id is grabbed from this piece of JSON:

      { id:'8', name:'company_products', type:'item', plugin_id:'6', children: [
         { id:'8_1', name:'fieldid', type:'sub'}
         ]
      }

So, if I click the node labeled “company_products” it will recognize that this node has children (because of the “children” in the JSON) which will call my custom getChildren method. This is not the root node so it will query this node’s value for plugin_id, which will be 6 for this item. Then it will call a new fetch from the server with plugin_id=6 on the query string. This will get me the JSON for this part of the tree which will magically be inserted under the node I expanded. In actuality, it will replace the current children I have from my JSON (the fieldid row above). Pretty cool.

The rest of the code is pretty straight-forward. I need to use a QueryReadStore as my backend as ItemFileReadStore will not re-fetch from the server unless I create a new store on each fetch. My URL doesn’t have a plugin_id param because getChildren is called to show the root items as well.

The definition for the tree store uses my custom forest model, the values for rootId, rootLabel are arbitrary, rootLabel will be the label for the top-level node. The rootId and rootLabel setup that psuedo-root node I referred to earlier.

The tree itself is pretty basic. I have a connect script that alerts the value of the node that was clicked for testing purposes.

Hope this helps.