Create a dynamic treeview with json data - dynatree in jsp

Lithium picture Lithium · Sep 29, 2013 · Viewed 10.5k times · Source

I have a table in database like this:

id   -   title   -  parentID
-----------------------------
1        Root          null
2        item 1        1
3        item 2        1
4        item 3        1
5        item 3.1      4

this should create something like this:

--Root
----item 1
----item 2
----item 3
-------item 3.1

here's my controller to fetch the root node and its children:

@RequestMapping(value="/CourtBranch/LoadTreeView", method = RequestMethod.GET)
    public void LoadList(@RequestParam("ParentId") String parentID,HttpServletRequest req, HttpServletResponse resp) {
        List lst;
        if (parentID.equals("Root"))
        {
            lst = _COURTBRANCH.LoadTreeChildren(null, "Root");   // selects records which have parent=null
        }
        else
        {
            lst = _COURTBRANCH.LoadTreeChildren(parentID, "TreeNode");   // selects records which have parent=parentID
        }

        resp.setContentType("application/json");
        resp.setCharacterEncoding("utf-8");
        try {
            resp.getWriter().print(_gson.toJson(lst));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

and this script loads my root:

<script type="text/javascript">
  $(function(){
    $("#tree").dynatree({
          initAjax: {
              url: "/CourtBranch/LoadTreeView.json?ParentId=Root",
              data: { mode: "funnyMode" }
              },
      onActivate: function(node) {
        $("#echoActive").text(node.data.title);
      },
      onDeactivate: function(node) {
        $("#echoActive").text("-");
      }
    });
  });
</script>

now I need to know how to send the root's id to my controller to fetch root's children and add them to the root node. should I use appendAjax function? how?

Answer

Francois picture Francois · Oct 28, 2013

Basically you don't really want to do that straight into your controller. (loose coupling)

I'd create 2 utils classes Tree<T> and Node<T> to manage your tree and nodes:

Here is the tree class:

public class Tree<T> {

    private Node<T> rootElement;

    public Tree() {
        super();
    }

    public Node<T> getRootElement() {
        return this.rootElement;
    }

    public void setRootElement(final Node<T> rootElement) {
        this.rootElement = rootElement;
    }

    public List<Node<T>> toList() {
        final List<Node<T>> list = new ArrayList<Node<T>>();
        walk(this.rootElement, list);
        return list;
    }

    private void walk(final Node<T> element, final List<Node<T>> list) {
        list.add(element);
        for (final Node<T> data : element.getChildren()) {
            walk(data, list);
        }
    }
}

And the node class with some helper methods

public class Node<T> {

    private T data;
    private List<Node<T>> children;

    public Node() {
        super();
    }

    public Node(final T data) {
        this();
        setData(data);
    }

    public Boolean hasChildren() {
        return this.children.size() != 0;
    }

    public List<Node<T>> getChildren() {
        if (this.children == null) {
            return new ArrayList<Node<T>>();
        }
        return this.children;
    }

    public void setChildren(final List<Node<T>> children) {
        this.children = children;
    }

    public int getNumberOfChildren() {
        return this.children.size();
    }

    public void addChild(final Node<T> child) {
        if (this.children == null) {
            this.children = new ArrayList<Node<T>>();
        }
        this.children.add(child);
    }

    public void insertChildAt(final int index, final Node<T> child) throws IndexOutOfBoundsException {
        if (index == getNumberOfChildren()) {
            addChild(child);
            return;
        } else {
            this.children.get(index);
            this.children.add(index, child);
        }
    }

    public void removeChildAt(final int index) throws IndexOutOfBoundsException {
        this.children.remove(index);
    }

    public T getData() {
        return this.data;
    }

    public void setData(final T data) {
        this.data = data;
    }
}

Then I'd have your type of tree extending the tree. based on your controller CourtBranch

Suppose your CourtBranch model has the following structure (I'm using hibernate + jpa):

@Entity
@Table
public class CourtBranch

private String id;
private String name;
private Long partentId;

//getters setters etc...

Create a class that extends the Tree:

public class CourtBranchTree extends Tree<CourtBranch>
    public CourtBranchTree{
        super();
    }

Now create your TreeGridResponse class:

public class TreeGridResponse {
    //inject the service to get the id of your model
    @Resource
    CourtBranchService cbService;
    //if you are using a repository for the db queries:
    @Resource
    CourtBranchRepository cbRepo;

    public TreeGridResponse(){
    }

    //returning the tree as a JSON to use AJAX
    public String cbBTreeAsJson(final CourtBranchTree tree){
        final StringBuffer sb = new StringBuffer();
        final CourtBranch root = tree.getRootElement().getData();
        sb.append("[\r {\"title\": \"" + root.getName() + "\", \"key\": \"" + root.getId() + "\", \"children\": [\r");
        final List<Node<CourtBranch>> children = tree.getRootElement().getChildren();
        loopforChildre(sb, children);
        sb.append("]");
        return sb.toString();
    }

    private StringBuffer loopForChildren(final StringBuffer sb, final List<Node<UserRight>> children) {
        for (int i = 0; i < children.size(); i++) {
            final Node<courtBranch> childElement = children.get(i);
            if (i == 0) {
                sb.append("{\"title\": \"" + childElement.getData().getName() + "\", \"key\": \"" + childElement.getData().getId() + "\"");
            } else {
                sb.append(", {\"title\": \"" + childElement.getData().getName() + "\", \"key\": \"" + childElement.getData().getId() + "\"");
            }
            if (childElement.hasChildren()) {
                sb.append(", \"children\": [\r");
                loopForChildren(sb, childElement.getChildren());
            } else {
                sb.append("}");
            }
        }
        sb.append("]}");
        return sb;
    }

    public CourtBranchTree get() {
        final CourtBranchTreetree tree = new CourtBranchTree();
        final Node<CourtBranch> root = new Node<CourtBranch> (this.cbRepo.findOne(Long.valueOf(1)));//gets your root
        getRecursive(root, tree);
        tree.setRootElement(root);
        return tree;
    }

    private void getRecursive(final Node<CourtBranch> courtBranch, final CourtBranchTree tree) {
        final List<CourtBranch> children = this.cbService.findCourtBranchByParentId(courtBranch.getData().getId());
        final List<Node<CourtBranch>> childElements = new ArrayList<Node<CourtBranch>>();
        for (final CourtBranch childCourtBranch : children) {
            final Node<CourtBranch> childElement = new Node<CourtBranch>(childUserRight);
            childElements.add(childElement);
            getRecursive(childElement, tree);
        }
        courtBranch.setChildren(childElements);
    }
}

Register the TreeGridResponse in your config to get the bean and inject it into your controller.

@Controller
public class CBController

@Resource
private TreeGridResponse gridResponse;

@RequestMapping(value="/CourtBranch/LoadTreeView", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String cbTree() {
    return this.gridResponse.cbBTreeAsJson(this.gridResponse.get());
}

Note the @ResponseBody annotation that will indicate Spring to parse your string into JSON so that your AJAX can read it.

And your jsp:

<div id ="cbTree"></div>
<script type ="text/javascript">
    $(function(){
    $("#cbTree").dynatree({
        initAjax:{
            url: /CourtBranch/LoadTreeView
            },
        checkbox: true,
        selectMode: 3
    });
    });
</script>

Hope this helped...