Struts2 – Dojo Dynamic Tree

September 19th, 2007 by Manu Mahajan

In my previous attempt at creating a tree using struts2 and dojo, I stumbled upon a roadblock. As the size of the tree grew larger the performance started deteriorating. Our tree had 500 nodes and it took about 1min 20sec for the tree to render in internet explorer 6!

So the need was to create a dynamic tree which would load nodes as and when required.

I managed to create a simple application based on the struts-blank application which demonstrates how this can be achieved. I am pasting the code here so that it can help other people who want to achieve this using struts 2.0.9.

The JSP Code

I start by creating a dynamicTree.jsp file which will paint the tree.

I will create a tree with one root node in my html page and when that node is expanded its sub nodes will be loaded. It is possible to load a tree with any number of nodes in the beginning but I am going to keep this example simple so I am starting with a single node.

For painting the tree tag and the root node I could not use the struts2 taglib as I need to use attributes for dojo widgets that are not supported by struts2. Like the attribute ‘controller’ is not supported by the tree tag and ‘isFolder’ is not supported by the treeNode tag. The next version (2.1.0) has this fixed but the latest stable version at the moment is 2.0.9.

Here is what my jsp looks like

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
  <s:head theme="ajax" debug="true"/>
<script>
 
   //Load the tree controller and extensions
  dojo.require("dojo.widget.TreeLoadingController");
  dojo.require("dojo.widget.TreeControllerExtension");
 
  dojo.addOnLoad(function() {
    //Add the extensions to the controller
    dojo.lang.mixin(dojo.widget.byId('treeController'), dojo.widget.TreeControllerExtension.prototype);
  });
 
}
</script>
 
</head>
<body>
 
<div dojoType="TreeLoadingController"
  widgetId="treeController"
  RPCUrl="tree_gettreenodes.action"></div>
 
<dojo:TreeSelector widgetId="treeSelector_contentTree"
  eventNames="select:treeSelected;"></dojo:TreeSelector>
 
<div dojoType="Tree" 
  id="contentTree" selector="treeSelector_contentTree"
  controller="treeController" toggle="fade">
 
  <div dojoType="TreeNode" title="<s:property value='#request.rootNode.name'/>"
    id="<s:property value='#request.rootNode.id'/>" isFolder="true"></div>
 
</div>
 
</body>
</html>

struts.xml

My struts.xml is pretty simple. There is one mapping to an action class which will expose methods for painting the tree and a default result for loading the jsp.

<struts>
   <constant name="struts.enable.DynamicMethodInvocation" value="false" />
   <constant name="struts.devMode" value="false" />
   <package name="default" extends="struts-default">
	<action name="tree_*" method="{1}" 
            class="com.codepencil.sandbox.tree.web.action.DynamicTreeAction">
    	    <result>/tree/dynamicTree.jsp</result>
        </action>
   </package>
</struts>

The Action Class

In the action class I need to implement a method that will be called when child nodes of a tree are requested. When the request is made dojo passes a parameter name data which contains the JSON objects of the tree and the tree node that was clicked. The parameter looks something like this.

{"node":{"widgetId":"1" isFolder=\"true","objectId":"","index":1,"isFolder":true}, "tree":{"widgetId":"contentTree","objectId":""}}

The treeLoadingController expects the server response to be a JSON array of node objects. For dealing with JSON I am using the set of Java classes available at json.org Since my needs are very basic I can make do with these simple classes. You can use any JSON library of your choice.

After this I write an action class and create a function named gettreenodes which does this job for me.

This is my action code.

package com.codepencil.sandbox.tree.web.action;
 
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.interceptor.ServletResponseAware;
import org.apache.struts2.showcase.ajax.tree.Category;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONStringer;
import com.opensymphony.xwork2.ActionSupport;
 
public class DynamicTreeAction extends ActionSupport implements ServletResponseAware,ServletRequestAware {
 
    private static final long serialVersionUID = -9131739831820245692L;
    private String data;
    private HttpServletResponse response;
    private HttpServletRequest request;
 
    public String getData() {
        return data;
    }
 
    public void setData(String data) {
        this.data = data;
    }
 
    public void setServletResponse(HttpServletResponse response) {
        this.response = response;
    }
 
    public void setServletRequest(HttpServletRequest request){
        this.request = request;
    }
 
    public String gettree(){
        Category rootNode = Category.getById(1);
        request.setAttribute("rootNode", rootNode);
        return SUCCESS;
    }
 
    public String gettreenodes(){
        JSONObject jsonData,node;
        PrintWriter writer;
        try {
            jsonData = new JSONObject(data);
            node = jsonData.getJSONObject("node");
            String nodeId = node.get("widgetId").toString();
            Category category = Category.getById(Long.parseLong(nodeId));
            JSONStringer stringer = new JSONStringer();
            stringer.array();
            List<Category> children = category.getChildren();  
            for (int i=0;i<children.size();i++){
                Category childCategory = children.get(i);
                stringer.object();
                stringer.key("id");
                stringer.value(childCategory.getId());
                stringer.key("title");
                stringer.value(childCategory.getName());
                stringer.key("isFolder");
                stringer.value(childCategory.getChildren().size()>0 ? true : false);
                stringer.endObject();
            }
            stringer.endArray();
            writer = response.getWriter();
            writer.write(stringer.toString());
        } catch (JSONException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

For creating the tree I am using the Category class from the struts showcase application as I was feeling too lazy to write a brand new class for this purpose. In my actual application I have some complex hibernate mappings and I load the tree node objects from the database.

Another important thing that caused me a lot of frustration is that you should always return null from an action method that handles ajax calls otherwise you wont get the desired result.

The Sample Application

The code of the full application can be downloaded from the following link. You can simply deploy this as it is in a container. (I have tested it with Tomcat5.5 and jdk6)

Download the Dynamic Tree Sample Application

You can use the tree loading controller for saving and restoring the state of the tree in a similar way as in the previous example.

39 Responses

  1. Session

    Manu, thanks once more for your hard work and generosity in sharing this code; I’ve been searching so long for working tutorials that would meet my needs, and finally I was able to do what I needed done with your help. Let me know once you have a more active blog up and running on the site, and I’ll gladly contribute to it.

    Thanks again,
    Session

  2. Visitor

    I have question. I was trying to add javascript which would let me to see which node is selected but I cannot make it work. Would you please show me how to capture node ID or some data which are inside node.
    I am trying to load based on node id some additional details into page but cannot cross this obstacle.

  3. Manu Mahajan

    If you want to capture the event when a user clicks on a node then you will have to do some tweaking as the struts 2.0.9 distribution has a bug. I’ve explained this in my previous post. Check it out here

    http://www.codepencil.com/index.php/creating-a-tree-widget-using-struts2/#capture-node-select

  4. Visitor

    I have read and followed instructions but still it doesnt work. I guess I am not getting exactly right part about getting tree.flt file and where it should be placed.
    I got it from repository and put it as you suggested under “WEB_APPLICATION_ROOT/templates/ajax”. But same file is inside strut20core.jar so should I replace that file inside jar as well? Next part I might not be doing correctly (or it is not needed) – do I have to do something to Tree.java, TreeNode.java and struts tld files?

  5. Visitor

    I guess I would be asking too much but would it be possible if you make change in your src code distribution are offering here and make simple

    function treeNodeSelected(message) {
    alert(message.node.widgetId);
    };

    work?…..

  6. Visitor

    Ok I have figured it out

    I must say you made good job with your research!

  7. Manu Mahajan

    I’m glad that you figured it out :)

    I’m sorry I messed up my laptop and had to reinstall operating systems and so I was offline for a day.

  8. Peter

    Hi Manu,
    this is great stuff you came up with!
    I have a couple questions.
    I am using 2.0.11 struts and you are using in your example 2.0.9. If I am to integrate it into 2.0.11 version what steps do I have to take?…and one more you tailored some JSON file to achieve you goals. If there are included into 2.0.11 build are they going to clash with any other JSON files?…I am definitively not expert on DOJO and JSON so please excuse me if those are trivial questions.

  9. Manu Mahajan

    Hi Peter
    This code is a little old if you are using struts 2.0.11. I think the same can be achieved in the new version of struts relatively easily. I have not used it yet but I think I saw a similar example somewhere. I will look it up and post it here soon.

  10. Peter

    Hi Manu,
    done some digging and found out why your code cannot work in current 2.0.11 Struts version( but of course it works as advertised in 2.0.9).
    between those 2 versions dojo lib must have changed:
    I have captured JSON requests and they are different:
    2.0.9:
    action getChildren
    data {”node”:{”widgetId”:”1″,”objectId”:”",”index”:0,”isFolder”:true},”tree”:{”widgetId”:”contentTree”,”objectId”:”"}}

    2.0.11:
    action getChildren
    data {”node”:{”widgetId”:”TreeNode_0″,”objectId”:”",”index”:0,”isFolder”:true},”tree”:{”widgetId”:”contentTree”,”objectId”:”"}}

    so obviously in your code when one asks for widgetId

    String nodeId = node.get(”widgetId”).toString();

    in 2.0.11 you get “TreeNode_0″ and not the mumber.

    Not sure if it is the only diff since I still didnt kame it work but I shall play with it a bit more and let you know.

  11. Struts2 Tree标签的使用 | 出家如初,成佛有余

    [...] http://www.codepencil.com/index.php/struts2-dojo-dynamic-tree/ [...]

  12. Xiangjun

    Hi Manu,

    After two painful days, I still can not figure out how to make the

    function treeNodeSelected(message) {
    alert(message.node.widgetId);
    };

    work. If I code like this

    Do I still need to change the tree.ftl, Tree.class ,TreeTag.class, and struts-tags.tld files?

    Hope you can see my comment. :(

  13. Xiangjun

    sorry. Dont know why I cant post the code. What i mean is i use the method in this post instead of your previous post.

  14. Manu Mahajan

    You haven’t mentioned the version of struts that you are using, I assume it’s that latest (2.0.11)

    You do not need to change the .ftl template or the class files for the latest version the code should work as it is. You need to provide more information like what is the exact error that you are getting. Are you getting a javascript error?

  15. Narayana

    Hi,

    i am using struts 2.1.2, want to create a tree, which can retail its previous state! can you please tell me what changes i need to do to your previous part of creating a tree?

  16. vasya_zyh

    [url=][/url]

  17. vasya_jzy

    [url=][/url]

  18. vasya_dpq

    [url=][/url]

  19. vasya_sqe

    [url=][/url]

  20. vasya_uni

    [url=][/url]

  21. vasya_uev

    [url=][/url]

  22. marusya_jlm

    [url=][/url]

  23. marusya_ont

    [url=][/url]

  24. marusya_wqm

    [url=][/url]

  25. marusya_yga

    [url=][/url]

  26. marusya_ewq

    [url=][/url]

  27. marusya_lnn

    [url=][/url]

  28. marusya_ayb

    [url=][/url]

  29. marusya_wrz

    [url=][/url]

  30. marusya_kek

    [url=][/url]

  31. marusya_sco

    [url=][/url]

  32. Manas

    hi, I am getting the following error

    org.apache.jasper.JasperException: java.lang.NullPointerException

    pls help

  33. Marty J

    Hiya

    Not what I was looking for, but very cool stuff. Thank you.

  34. Krishna

    Hi,
    Please help…
    I am getting the whole string “”
    in action class(nodid) insteadof it’s value while using the following statement.


    id=”" isFolder=”true”>
    What i did wrong? Please very urgent.

  35. Krishna

    Hi,
    Sorry…
    I am getting the whole string
    in action class(nodid) insteadof it’s value while using the following statement.
    [
    "
    id="" isFolder="true">]

  36. Krishna

    s:property value=’#request.rootNode.id’

  37. Krishna

    div dojoType=”TreeNode” title=”"
    id=”" isFolder=”true”> div

  38. govind

    hi ,
    i am getting problems with dynamic tree view please help me out. i am using strust2.0.11 and i dont know how to use dojo plugin. i have done displaying tree structure with map .its working fine but i am not getting how to include link for the treenodes .here i am sending my code please help thanks in advance.

  39. omar

    hi,
    please help me I have to show in the dynamic tree view a selected node tree when the page is loaded and i am using this tree, so i dont know how to do it? , thanks for all.