Friday, May 29, 2009

RichFaces tree - dynamic update of tree model

The previous RidhFaces simple tree example allows you to render tree data which are output at one render time. But what if your tree data model is so large, so that you can't render it all at once. Instead you have to update your tree model at run time on user request?

Rich faces provides a listener called change expand listener. In case the user selects certain node in your tree, this listener will be called and here you can update your tree model. In the following section I will show how this is done. Let's first see how this listener is bound to a tree component:








Please notice that I left all other tree property's intentionally. As you can see, this property will call the assigned method where you can handle your tree model. Let us look into the method closely and see how we can update our tree model.


import org.richfaces.event.NodeExpandedEvent;

public class MyBackingBean{

/**
* The tree expand expand listener method.
*/
public void processExpansion(NodeExpandedEvent nodeExpandedEvent){
// get the source or the component who fired this event.
Object source = nodeExpandedEvent.getSource();
if (source instanceof HtmlTreeNode) {
// It should be a html tree node, if yes get
// the ui tree which contains this node.
UITree tree = ((HtmlTreeNode) source).getUITree();
// avoid null pointer exceptions even though not needed. but safety first ;-)
if (tree == null) {
return;
}
// get the row key i.e. id of the given node.
Object rowKey = tree.getRowKey();
// get the model node of this node.
TreeNode selectedTreeModelNode = tree.getModelTreeNode(rowKey);
if(null != selectedTreeModelNode){
// add the children nodes.
addChildrenNodes(selectedTreeModelNode);
}
}
}
/**
* Method for adding children nodes to a given tree model node.
*/
public void addChildrenNodes(TreeNode node){
//process your node somehow. For this example I add only one node.
TreeNodeImpl newNode = new TreeNodeImpl();
newNode.setData("My New Node");
newNode.setParent(node);
node.addChild("rowKey", newNode);
}
}


So every time user clicks the icon for expanding nodes, this listener will be called and the tree model will be updated. When the response will be rendered, the updated tree model will be rendered out, including your newly added node(s).

The same process can be done while the user selects particular node of the tree. When the user selects particular node, a RichFaces will look for queued Select node listeners to be called. Such listener can be registered is a Select Node Listener.







To queue select node listener to the tree, you can use the nodeSelectListener property. Than you can create new public listener method in your backing bean as follow:


import org.richfaces.event.NodeSelectedEvent;

public class MyBackingBean{

/**
* The select node listener method.
*/
public void selectNode(NodeSelectedEvent event){
// get the component that fired this event.
UIComponent component = event.getComponent();
if (component instanceof HtmlTree) {
// if a rich faces html tree (it is sometimes also a ui tree)
//get the row key of selected node.
Object rowKey = ((HtmlTree) component).getRowKey();
//pull the model tree node out of ther.
TreeNode selectedTreeModelNode = ((HtmlTree) component).getModelTreeNode(rowKey);
if(null != selectedTreeModelNode){
// add the children to the model.
addChildrenNodes(selectedTreeModelNode);
}
}
}
}


You should notice that the same method for adding children to the model is called as in previous example for expanding the node. Now that we covered how we can update our tree model, we could decide that after selecting a node and updating it's model we would like to expand the node. To do this, there are few way's. The safest and preferable way would be through the tree state saving property. Let's look into it closer:








In the backing bean, you should create new property called tree state. This should look something like this:



/**
* Getters and Setters added.
*/
public class MyBackingBean{

private org.richfaces.component.state.TreeState treeState;

public void setTreeState(org.richfaces.component.state.TreeState treeState) {
this.treeState = treeState;
}

public org.richfaces.component.state.TreeState getTreeState() {
return treeState;
}

}


Now that we added the Getters and Setters for this property, we could add this line to our select node listener method:



import org.richfaces.event.NodeSelectedEvent;

public class MyBackingBean{

public void selectNode(NodeSelectedEvent event) throws Exception {

// ... same as in the previous section
if(null != selectedTreeModelNode){
//.... as in previous section
// queue the node to be expanded.
getTreeState().expandNode((UITree) tree,
(TreeRowKey) rowKey);
}
}
}
}


The tree state instance will be consulted at render time of the tree. It contains map's where the state of the nodes is saved. Expanded nodes have own map where the row key's are queued for expanded rendering. The method expand node of the tree state class queue's the row key of the given node to be rendered as expanded.

As you see, RichFaces is very flexible and provides methods for dynamic extending of the tree structure, avoiding to render the whole content of the tree at the beginning.

Pity that they don't have good tutorials on their components.

30 comments:

  1. Very nice and detailed info. Best I found in a while. This helped me a lot, thanks.

    ReplyDelete
  2. I am having problems getting this to run. Can you post the full xhtml and backing bean? I think I missed something.

    ReplyDelete
  3. Hi ed,

    this example is held generic, cause I can't post my source code out of company policy reasons. I think you will understand that.

    My first question would be, do you have a simple RichFaces tree running, similar to the example here: http://random-thoughts-vortex.blogspot.com/2009/05/richfaces-tree-dynamic-loading.html


    If so, than you should try to add the Expand Listener attributes to your tree, and the appropriate expand method in your backing bean. That's it.

    Now go in debug Mode and put an break point there. After clicking on the node, you will enter the expand method.That is what it does.

    Now you have to read the node-id in there and get the node and fill it with new sub brunches.

    All in all, not that hard.

    Hope it helps you.

    ReplyDelete
  4. Great Post in explaining how to do this. However I do have one Problem.
    I load the children of a node dynamicaly when it is expanded or clicked upon. However a node is not expandable(the little downarrow in the rich:tree) until its children are loaded. So In order to see if a node is expandable I have to click on it first.

    I would prefer if I could see the arrow in advance without first clicking on it. I guess for this to work that way I will have to load the childrens children of every node that is currently visible?

    Still many thanks for your post.

    ReplyDelete
  5. Hi Boldoran, first you are welcome.

    you asked: I would prefer if I could see the arrow in advance without first clicking on it. I guess for this to work that way I will have to load the childrens children of every node that is currently visible?


    I guess this is what you need to do in order to see the arrow on the side.

    I did not need such thing yet, thus don't really know how to do it. If I find out, I will post it here.

    Kind Regards.

    ReplyDelete
  6. Nice post.
    I have a problem here

    rich:tree nodeSelectListener="#{discovery.selectnode}" switchtype="client" value="#{discovery.nodes}" ajaxSubmitSelection="true"

    But, when i select a node, the listener method is not called. Any thoughts on this?

    Kind Regards,
    Vikas

    ReplyDelete
  7. Hi Vikas,

    I would try changing switchtype="client" in switchtype="ajax".

    The switchtype="client" renders the whole tree in browser, and I thing (not sure there) that no calls will be made on the server.

    Tell me if that worked out.

    Kind regards.

    ReplyDelete
  8. switchtype of ajax didnt work either. But, I found the issue. Since I was running this on WAS in RAD, it had reference implementation of core jsf from Sun. But, I also added myfaces implementation in my WEB-INF/lib. So, there was a warning in my console logs that the class loader found two libraries conflicting, I removed my myfaces impl and it worked fine. Strangely, none of the events were working - action event, node select event etc,.

    Thanks anyway.

    Kindest Regards,
    Vikas

    ReplyDelete
  9. Hi Vikas,

    I have to integrate one framework that was built using Richfaces and Myfaces. Until now I'm facing several problems and I was yet not able to do one example with WebSPhere Portal+Richfaces+Myfaces.

    Did you tried something like this?

    Thanks in Advance

    ReplyDelete
  10. Hello AGEMA-MAKEDONIN,
    thanks for the post.
    I have nearly the same case, but my tree can not reRender its self.
    I've tried many combinations, when I debug the code it is running and the proper methods are getting called, the point is that nothing is rerendered on the screen.
    Do you have any idea?
    Thank you in advance!
    Netnget

    ReplyDelete
  11. Hallo netnget,
    it is really strange that nothing is rendered on the screen. Have you checked the rendered property of the tree or any given container or panel that may hold the tree instance as it's child?

    As for rerendering, you can put the tree in say

    h:panelGrid and tell the tree to rerender the panel grid that has it as child component, similar to this:

    h:panelGrid id="rerenderId
    rich:tree id="treeId" reRender="rerenderId"
    rich:tree
    h:panelGrid

    I have it similar to this in my project and it works fine for me.

    Hope I could help you.

    Kind regards.

    ReplyDelete
  12. Thanks for that post, really helpful.

    ReplyDelete
  13. There is one additional problem i've noticed.

    if we want to expand/collapse the node when it was selected the listener will be invoked only once.
    This caused due to the selected state of the current node.

    my solution was to add "treeState.setSelected(null);" to the end of the "selectNode" listener.

    Thanks again for this greate tip.

    ReplyDelete
  14. hello agema-makedonin,
    thanks for your helpful support. I guess I am somewhere wrong but do not know where.
    I have something like that.




    it behaves like I have never called expand node.
    Actually I tryed to expand it in both way: With
    getTreeState().expandNode( (UITree)component, (TreeRowKey) rowKey);
    and as well as
    ((UITree)component).queueNodeExpand((TreeRowKey)rowKey);


    like this

    public void selectNode(NodeSelectedEvent event) {
    // get the component that fired this event.
    UIComponent component = event.getComponent();
    if (component instanceof HtmlTree) {
    Object rowKey = ((HtmlTree) component).getRowKey();
    TreeNode selectedTreeModelNode = ((HtmlTree) component).getModelTreeNode(rowKey);
    if(null != selectedTreeModelNode){
    // add the children to the model.
    addChildrenNodes(selectedTreeModelNode);
    //getTreeState().expandNode((UITree) component, (TreeRowKey) rowKey);

    try {

    //on this bellow one of them is commented getTreeState().expandNode( (UITree)component, (TreeRowKey) rowKey);
    //((UITree)component).queueNodeExpand((TreeRowKey)rowKey);
    } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }


    }
    }
    }


    Thank you for your time and support.

    kind regards
    netnget

    ReplyDelete
  15. hello agema-makedonin
    thanks for your answer.
    I still have the same problem.
    The tree is rendered but when I click on a node the children are not not rendered but the proper methods are called. It behaves the same way as I have never called

    `getTreeState().expandNode( (UITree)component, (TreeRowKey) rowKey);`

    but I actually have called it.

    I have something like that

    `









    `
    and the methods in the bean are quite similar to yours.
    I still have the feeling that I am missing something.
    Any idea will be highly appreciated.
    Best regards
    netnget

    ReplyDelete
  16. Halo netnget,

    I would check if the childrenof the TreeNode are added to the parent node with the help of the TreeNode.addNode Method. The new children should be added to it's parent in the select node listener.

    Second, I would put the Tree to rerender it self. The Tree has the reRender attribute, so do something like:

    rich:tree id="treeId" reRender="treeId"

    Other wise, I can't think of another reason why this would not work for you.

    I wish you luck.

    Kind regards.

    ReplyDelete
  17. Thank you for the article.
    I am getting an error if I try to expand a node before the entire tree is rendered. Is there any way to restrict the user to prevent clicking on the tree before it is fully loaded/rendered.

    ReplyDelete
  18. I would try preventing the user clicking on the tree by putting a a4j status tag in a modal panel. Check this out, it is a example on how it can be done: http://community.jboss.org/wiki/RichFacesPleaseWaitBox

    I hope that helps.

    Kind regards

    ReplyDelete
  19. Thank you for the prompt reply. The code you have mentioned is working fine for us when user expands a node but not when the tree is initially rendered.
    We display the tree when user clicks on a link. Is there any way to prevent the user from clicking on the tree before the entire tree is displayed for the first time.

    ReplyDelete
  20. I would not render the whole tree at a time, but rather the first nodes level. I would than add the next level of nodes when user is expanding certain nodes. In the expand node listener, I would dinamically add the sub-nodes of the node that the user is trying to expand. Other than that, I don't have an idea of how the user is to be prevented while a large tree is rendered out.

    I hope it hellps.

    ReplyDelete
  21. My tree has got a context menu. Can I get a row key of a selected node in a contextmenu event? I use tag for a contextmenu event and I generate a context menu programmaticaly(dinamically) in the event handler method.

    ReplyDelete
  22. I don't know if that is possible. For me, it ain't possible. I would try to go over the a4j:actionparam assignTo="#{bean.nodeId}" to assign the id of certain node to a backing bean method, so I would get the node id on any ajax event on selected node.

    It is a worth trying.

    regards

    ReplyDelete
  23. hi i have to add nodes dynamically.. i can add child to the particular node.. but it is performed while clicking the node.. but am not getting the folder expand collapse view since it is leaf.. from the begining i want the folder view so that while clicking that i shud get the child nodes... any suggestions or help


    regards

    ReplyDelete
  24. you could add a dummy node to all the nodes so that they render like a folder. On clicking each node, you could populate the node hierarchy and remove the dummy node. Hope it helps.

    ReplyDelete
  25. I don't know if I understand your question. I wonder why your folder is a leaf. I would suggest two separate TreeNodeImpl classes for documents and folders. The one should be leaf the other not.

    Try it. Regards.

    ReplyDelete
  26. Thanks for your reply.

    First time i will be adding nodes then while expanding only i have display the child.
    On clicking the expand button only it has to retrieve all the childs.im not getting the expand button to click

    please help

    Thanks & Regards

    ReplyDelete
  27. Thanks for ur reply Acharya.. i could proceed in that way but at the end that dummy leaf is getting displayed..how to remove that..kindly help

    ReplyDelete
  28. Thank you so much. This post was immensly helpful in our current project. This just made my day!

    ReplyDelete
  29. AnonymousMay 21, 2012

    Thanks Sir.....
    Its Really Helpful
    ....

    ReplyDelete