Adding Dojo Tree Nodes with Ajax

Update: I've added a new page with information about how to customize the menu.

This will demonstrate adding child nodes to a tree from a server, on demand, using ajax. This demonstration assumes that the reader has read the introduction to the dojo tree. I also want to note, this article is the direct result of a request/suggestion from dzone user bloid.

Build a Tree Programmatically

To continue in the theme of where the introduction left off, lets build a tree widget programmatically. We can use the same technique of creating a container and adding a place holder that provides a loading message. I am going to var from the original code just a little. Fisrt, I am going to move the dojo.require lines into the dojo.onLoad function. This will allow the page to display sooner while, waiting for the dojo libraries to load. Next, the tree nodes used, will only be for the top level, and they will all be set as folders.

  1. <script type="text/javascript" src="scripts/dojo.js">/*_*/</script>
  2.  
  3. <script type="text/javascript">
  4. /* Initial Nodes */
  5. var treeDat = {
  6.   treeNodes: [
  7.     { title:"World" },
  8.     { title:"Business" },
  9.     { title:"Sports" }
  10.   ]
  11. };
  12. </script>
  13.  
  14. <script type="text/javascript">
  15. var TreeBuilder = {
  16.   buildTreeNodes:function (dataObjs, treeParentNode){
  17.     for(var i=0; i<dataObjs.length;i++){
  18.       var node = dojo.widget.createWidget("TreeNode",{
  19.         title:dataObjs[i].title,
  20.         isFolder: true,
  21.         widgetId:(((treeParentNode)?treeParentNode.widgetId:"root_")+"_"+i)
  22.       });
  23.       treeParentNode.addChild(node);
  24.       treeParentNode.registerChild(node,i);
  25.     }
  26.   },
  27.   buildTree:function (){
  28.     var myTreeWidget = dojo.widget.createWidget("Tree",{
  29.       widgetId:"myTreeWidget"
  30.     });
  31.     this.buildTreeNodes(treeDat.treeNodes,myTreeWidget);
  32.     var treeContainer = document.getElementById("myWidgetContainer");
  33.     var placeHolder = document.getElementById("treePlaceHolder");
  34.     treeContainer.replaceChild(myTreeWidget.domNode,placeHolder);
  35.   }
  36. }
  37. dojo.addOnLoad(function(){
  38.   dojo.require("dojo.lang.*");
  39.   dojo.require("dojo.widget.Tree");
  40.   dojo.require("dojo.widget.TreeNode");
  41.   dojo.require("dojo.widget.TreeRPCController");
  42.   TreeBuilder.buildTree()
  43.   });
  44. </script>
  45.  
  46. </head>
  47. <body>
  48. <h1>RPC Dojo Tree Demo</h1>
  49. <hr />
  50.  
  51. <div id="myWidgetContainer"
  52.    style="width: 17em; border: solid #888 1px; height:300px;">
  53.   <span id="treePlaceHolder"
  54.      style="background-color:#F00; color:#FFF;">
  55.     Loading tree widget...
  56.   </span>
  57. </div>

Ok, this desn't do much but, give the base nodes for the tree widget. (The file is available for download) Next, we will want to make it call the server when expanding a node to retrieve the child elements. Lets start with a static model and then move to a dynamic one.

Add Child Tree Nodes with Ajax

From the dojo tree widget demo, copy the file getChildren int the same folder as the rpcTree.html file. The contents of the file is a simple JSON string.

[{title:"test",isFolder:true,objectId:"myobj"},{title:"test2"}]

When called, this creates two nodes with the titles "test" and "test2". The first node is a folder (expandable) and the second is not. When we create the dynamic version, we will create this file dynamically.

Next up, we need to add a controller to the tree. Modify the dojo.addOnLoad as follows.

  1. dojo.addOnLoad(function(){
  2.   dojo.require("dojo.lang.*");
  3.   dojo.require("dojo.widget.Tree");
  4.   dojo.require("dojo.widget.TreeNode");
  5.   dojo.require("dojo.widget.TreeRPCController");
  6.   TreeBuilder.buildTree();
  7.   var myRpcController = dojo.widget.createWidget("TreeRPCController",{
  8.       widgetId:"treeController",
  9.       RPCUrl:"getChildren"
  10.     });
  11.   myRpcController.onTreeClick = function(message){
  12.     var node = message.source;
  13.     if (node.isExpanded){
  14.       this.expand(node);
  15.     } else {
  16.       this.collapse(node);
  17.     }
  18.   };
  19.   var treeContainer = document.getElementById("myWidgetContainer");
  20.   treeContainer.appendChild(myRpcController.domNode);
  21.   myRpcController.listenTree(dojo.widget.manager.getWidgetById("myTreeWidget"));
  22.   });

Ok, let me explain this. Lines 55-58 show the declaration for the controller. Notice line 57, the RPCUrl. This is the relative url to the resource that will return a JSON string representing the children of a given node. Now, lines 59-66, these in effect flip the logic of the TreeBasicController. We have to do this because the controller reverses the expand/collapse logic of the TreeNodes. Without overriding the function, when you click on a node to expand it, nothing will happen (yea, I know, don't get me started). On line 68, the controller is added to the DOM, and on line 69, we instruct the controller to listen to the tree. Now if you expand the nodes on the tree, you will see the same effect as when expanding the 2nd tree in the Dojo demo.

The file is available in this state to view, test, and/or download.

Dynamically Creating Children

To create dynamic children, we will replace the static file with a reference to a servlet. By default, the TreeRPCController will call the url specified in the RPCUrl adding a query string parameter action with the value getChildren. (Note: It can send other values for other functions but, that is a topic for another day.) It will also send a parameter, data, that contains a JSON string containing two objects, the node being expnded, and some of its properties, and the tree that contains the node. When creating children dynamically, the important thing being sent is the widgetId of the node.

Here is a little servlet that returns the same nodes as the static data, just for demostration. It also outputs the querystring to the console, just so you can see what the tree controller sends. If you map it in the web.xml and place the servlet mapping in the RPCUrl of the controller above, you can see it in action.

  1. public class DojoDemoServlet extends HttpServlet {
  2.  
  3.   private static final long serialVersionUID = (-1L);
  4.  
  5.   public void doGet(HttpServletRequest req, HttpServletResponse resp)
  6.       throws ServletException, IOException {
  7.     doPost(req, resp);
  8.   }
  9.  
  10.   public void doPost(HttpServletRequest req, HttpServletResponse resp)
  11.       throws ServletException, IOException {
  12.     resp.setContentType("text/javascript");
  13.     PrintWriter writer = resp.getWriter();
  14.     writer.println(
  15.         "[{title:\"test\",isFolder:true,objectId:\"myobj\"},{title:\"test2\"}]"
  16.     );
  17.     writer.close();
  18.     System.out.println(req.getQueryString());
  19.   }
  20. }

Follow-up

Demos

The following demos demonstrate the code explained above. View source to see the code in context.


Feel free to send comments to: feedback@willcode4beer.com or leave a reply on my blog. thanks


Sponsors:

About willCode4Beer