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.