You are on page 1of 6

ReadySET Pro is a set of well thought-out templates for the documents that help keep your team on track.

ReadySET Pro Core Templates Inception: Elaboration: Construction: Transition: Project proposals, User needs, Target market identification, Project plans Requirements specifications, Use cases, Feature specs, Design documents Implementation notes, Status reports, Test cases, Review meeting checklists Release notes, Installation guide, User guide, FAQ, Product demo script

ReadySET Pro is based on the experience of hundreds of development projects. Each template includes a high-quality outline that asks the right questions, and reusable sample answers that make it easy for developers to express themselves. Built-in checklists help all team members catch mistakes and oversights early. For example, our use case suite helps you visually recognize missing requirements, and our use case templates include detailed reusable sample use cases for user registration and login. Our test planning template and test templates give you a head start by helping you prioritize quality goals, set test coverage criteria, and quickly write system test cases.

Multi select in RichFaces trees


In the past few months I have been involved in a development project where we are using Hibernate, Seam and RichFaces. One of the requirements of our customer is to have a hierarchical data structure represented in a tree structure. We found that the RichFaces rich:tree component meets all the requirements we have. Well, all but one: rich:tree is single select only, not multi select. Or is it? Some theory behind the rich:tree component The RichFaces rich:tree component is a component that can display hierachical data in two ways. The first way is to display a org.richfaces.model.TreeNode with its children. The RichFaces API also provides a default implementation of the org.richfaces.model.TreeNode interface, which is the org.richfaces.model.TreeNodeImpl class. The second way is to use a RichFaces rich:recursiveTreeNodesAdaptor to display a java.util.List or array of any kind of object, as long as it has some member that holds a java.util.List or array of child objects. Due to some heavy preprocessing of the data that is displayed in the tree, along with us feeling more comfortable with java.util.List we choose this approach in our project. In this article Ill use the first approach to show that our solution also works in this case.

Building a simple rich:tree To be able to display a rich:tree in a JSF page, you need few simple classes. The first class I used is called SelectionBean and it looks like this
import org.richfaces.event.NodeSelectedEvent; import org.richfaces.model.TreeNode; import org.richfaces.model.TreeNodeImpl; public class SelectionBean { private TreeNode rootNode = new TreeNodeImpl(); public SelectionBean() { TreeNodeImpl childNode = new TreeNodeImpl(); childNode.setData("childNode"); childNode.setParent(rootNode); rootNode.addChild("1", childNode); TreeNodeImpl childChildNode1 = new TreeNodeImpl(); childChildNode1.setData("childChildNode1"); childChildNode1.setParent(childNode); childNode.addChild("1.1", childChildNode1); TreeNodeImpl childChildNode2 = new TreeNodeImpl(); childChildNode2.setData("childChildNode2"); childChildNode2.setParent(childNode); childNode.addChild("1.2", childChildNode2); } public void processTreeNodeImplSelection(final NodeSelectedEvent event) { System.out.println("Node selected : " + event); } public TreeNode getRootNode() { return rootNode; } }

It creates a simple TreeNode hierarchy that can be displayed in a tree with the following Facelets JSF page:
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:a4j="http://richfaces.org/a4j" xmlns:rich="http://richfaces.org/rich"> <body> <h:form id="main"> <a4j:outputPanel ajaxRendered="true"> <rich:panel id="treePanel">

<f:facet name="header">Tree</f:facet> <rich:tree id="tree" ajaxSubmitSelection="true" switchType="ajax" value="#{selectionBean.rootNode}" var="node"> <rich:treeNode> <h:outputText value="#{node}"/> </rich:treeNode> </rich:tree> </rich:panel> </a4j:outputPanel> </h:form> </body> </html>

The result looks like this:

Quite simple, like I said. Multi select in the rich:tree The SelectionBean class has been prepared to catch node selection events. If you modify the rich:tree line in the Facelets JSF page to read like this
<rich:tree id="tree" ajaxSubmitSelection="true" switchType="ajax" value="#{selectionBean.rootNode}" var="node" nodeSelectListener="#{selectionBean.processTreeNodeImplSelection}">

you should see selection events being registered in the log of your application server:
Node selected : org.richfaces.event.AjaxSelectedEvent

Not very helpful information, but at least we know that the node selection events are registered. Now, if you look at the taglib doc for rich:tree youll notice that there is no way to configure the tree to accept multiple selections. So lets modify the SelectionBean to keep track of node selections itself. Well need some Set to hold the selected tree nodes in. We could use a List, but that will create doubles if we click a node more than one time. Remember, rich:tree has no way of knowing if a node recently was selected or not. So everytime we click a node that rich:tree thinks not to be selected, it raises a selection event again! We only want to know which nodes are clicked and keep track of that. we dont want to know how many times a node is selected. So therefore a Set will do nicely.

Theres one more thing to a rich:tree. Its backing UIComponent is a org.richfaces.component.html.HtmlTree and in the TreeModel of that tree, each node is uniquely identified by a RowKey Object. So, Ill use a
private Map<Object, TreeNode> selectedNodes = new HashMap<Object, TreeNode>();

Now, everytime a node is selected Ill add its TreeNode to the Map under the RowKey key. Assuming we have a global Map member as defined above, the processTreeNodeImplSelection method can now be modified to
public void processNodeSelection(final NodeSelectedEvent event) { HtmlTree tree = (HtmlTree)event.getComponent(); Object rowKey = tree.getRowKey(); TreeNode selectedNode = tree.getModelTreeNode(rowKey); selectedNodes.put(rowKey, selectedNode); for (Object curRowKey : selectedNodes.keySet()) { System.out.println("Selected node : " + selectedNodes.get(curRowKey).getData()); } }

If you click the three nodes in the tree in some random order, youll get this output:
Selected node : childChildNode1 Selected node : childNode Selected node : childChildNode2

So, we are keeping track of all selected nodes! Making the selected nodes visible in the tree Now our bean knows that we have selected multiple nodes, but the tree still displays only one selected node at a time. Using e.g. FireBug its easy to determine the difference is CSS class between a selected node and a non-selected node. Using the default Blue skin for RichFaces, a non-selected node has style class
dr-tree-h-text rich-tree-node-text

while a selected node has style class


dr-tree-h-text rich-tree-node-text dr-tree-i-sel rich-tree-node-selected

The border around a selected node is there because of the dr-tree-i-sel style. We only need a way to make all selected nodes (that is, the ones that are stored in the Map in our bean) use that style. One way is to tell each TreeNode that is has been selected. But how can we do that? Well, for instance by introducing a class that holds both the text that will be displayed in the tree as well as a Boolean that holds the selection state of the node. Such a class could be like this

public class NodeData { private String nodeText; private Boolean selected = Boolean.FALSE; public NodeData(String nodeText) { this.nodeText = nodeText; } [getters and setters] }

With this class we need to make a few changes to our SelectionBean. First of all, when building the node hierarchy we need to use the NodeData class instead of a simple String. This means well have to modify the constructor method so it looks like this
public SelectionBean() { TreeNodeImpl childNode = new TreeNodeImpl(); childNode.setData(new NodeData("childNode")); childNode.setParent(rootNode); rootNode.addChild("1", childNode); TreeNodeImpl childChildNode1 = new TreeNodeImpl(); childChildNode1.setData(new NodeData("childChildNode1")); childChildNode1.setParent(childNode); childNode.addChild("1.1", childChildNode1); TreeNodeImpl childChildNode2 = new TreeNodeImpl(); childChildNode2.setData(new NodeData("childChildNode2")); childChildNode2.setParent(childNode); childNode.addChild("1.2", childChildNode2); }

Next, the processNodeSelection method needs to tell a node that it is selected by setting the selected Boolean in NodeData to true. The method becomes
public void processNodeSelection(final NodeSelectedEvent event) { HtmlTree tree = (HtmlTree)event.getComponent(); Object rowKey = tree.getRowKey(); TreeNode selectedNode = tree.getModelTreeNode(rowKey); ((NodeData)selectedNode.getData()).setSelected(Boolean.TRUE); selectedNodes.put(rowKey, selectedNode); for (Object curRowKey : selectedNodes.keySet()) { System.out.println("Selected node : " + ((NodeData)selectedNodes.get(curRowKey).getData()).getNodeText()); } }

Finally, we need to modify our Facelets JSF page in two ways. The first one is to make sure the h:outputText element displays the nodeText of the NodeData. The second modification is to have the rich:treeNode set its nodeClass accordingly to the selected NodeData Boolean. The Facelets JSF page lines look like this
<rich:treeNode nodeClass="#{node.selected?'dr-tree-i-sel':''}"> <h:outputText value="#{node.nodeText}"/>

</rich:treeNode>

Now, if you reload the application in your browser, all of a sudden you can "select" multiple nodes in the tree.

Future enhancements The above scenario isnt ideal. First of all, now single selection of nodes doesnt work anymore. To fix this, you may want to add a checkbox that toggles the selection state from single to multiple and back. Another issue is that accidentically selected nodes cannot be deselected anymore. The selection state checkbox may partially solve that, however. Once you select a node that you didnt want to select, toggle the checkbox, select a single node, then toggle the checkbox again and start selecting multiple nodes once more. Another way would be to have another checkbox that allows you to deselect any selected node. Finally, users may want to hold a key, e.g. the CTRL key, and then start selecting multiple nodes. I havent got a clue how to do that, so if you know please drop me an email Ideally the RichFaces rich:tree would have native multiple selection support. Perhaps this post will actually make that possible.