ICE Tree Tutorial

Table of Contents

How to Use the ICEfaces Tree Component

The tree component can be used to display hierarchical data. The tree component renders its view using a javax.swing.tree.DefaultTreeModel object as a model. Client side Swing developers who are familiar with the DefaultTreeModel and the DefaultMutableTreeNode class will find an easy migration path to the ice:tree component. The following screen shot is of the ice:tree component using the CSS XP theme.

The tree displays its data vertically where every node of the tree is represented by a DefaultMutableTreeNode. A node can either be a leaf or branch depending if it has zero or more child nodes. A user can click on the branch expand/contract icon to toggle the visibility of child nodes. The root tree node visibility can be toggled by setting the tree hideRootNode attribute to false. Each DefaultMutableTreeNode wraps an IceUserObject which contains instance variables for common tree node properties such as icon paths, tool tips, display text and expanded state.





Creating a Tree


Creating a basic tree component is a pretty standard process comprising of two parts. The first part consists of building a backing bean which will contain the DefaultTreeModel. The second part is to add the tree component to your ICEfaces application. Here is a picture of the tree we will create:

The following code is taken from TreeBean.java which builds a simple DefaultTreeModel, one root node with three child nodes. The TreeBean must expose the DefaultTreeModel instance variable to bind it to the tree components' value attribute.

TreeBean.java
// create root node with its children expanded
DefaultMutableTreeNode rootTreeNode = new DefaultMutableTreeNode();
IceUserObject rootObject = new IceUserObject(rootTreeNode);
rootObject.setText("Root Node");
rootObject.setExpanded(true);
rootTreeNode.setUserObject(rootObject);

// model is accessed by by the ice:tree component via a getter method
model = new DefaultTreeModel(rootTreeNode);

// add some child nodes
for (int i = 0; i <3; i++) {
    DefaultMutableTreeNode branchNode = new DefaultMutableTreeNode();
    IceUserObject branchObject = new IceUserObject(branchNode);
    branchObject.setText("node-" + i);
    branchNode.setUserObject(branchObject);
    rootTreeNode.add(branchNode);
}

The TreeBean must be instantiated in faces-config.xml so it can be used in our JSF application. Here is the JSF code that is needed to render the DefaultTreeModel defined in TreeBean:

<ice:tree id="tree"
          value="#{tree.model}"
          var="item"
          hideRootNode="false"
          hideNavigation="false"
          imageDir="./xmlhttp/css/xp/css-images/" >
    <ice:treeNode>
        <f:facet name="content">
            <ice:panelGroup style="display: inline">
                <ice:outputText value="#{item.userObject.text}" />
            </ice:panelGroup>
        </f:facet>
    </ice:treeNode>
</ice:tree>

Notice how the tree component has a treeNode child component. This child component is sub-child further with a facet named content. The facet name is self descriptive and allows for any type of JSF component to be added. There is also an icon facet which should be used to hold components related to a tree node icon. The content facet should be used to hold components related to a tree nodes text label. The tree tag is iterative and will apply its child component hierarchy to all of the DefaultMutableTreeNodes found in the DefaultTreeModel.

To summarize, the tree component can be created with a simple binding to a backing bean which contains a DefaultTreeModel. You do not have to write any code to make the tree nodes expand and contract.


Customising the IceUserObject


The IceUserObject object was designed to have a bare minimum of instance variables needed to display a basic tree node. In this next example the IceUserObject will be extended so that it can store a String representing a URL. The following code is taken from UrlNodeUserObject.java:

UrlNodeUserObject.java
private String url;

public UrlNodeUserObject(DefaultMutableTreeNode wrapper) {
    super(wrapper);
}

public String getUrl() {
    return url;
}

Now that every node can have a unique URL we need to update the JSF code in linkTree.xhtml. The following code shows how to add a commandLink to a treeNode which takes advantage of our new UrlNodeUserObject object.

<ice:treeNode>
    <f:facet name="content">
        <ice:panelGroup style="display: inline">
            <ice:outputLink value="#{item.userObject.url}" target="_blank">
                <ice:outputText value="#{item.userObject.text}" />
            </ice:outputLink>
        </ice:panelGroup>
    </f:facet>
</ice:treeNode>

Customising the IceUserObject is quite simple and when combined with the iterative nature of the tree component, it can be quite powerful. The next section will show how to respond to node selection.


Responding to Node Selection


In this next example, the IceUserObject will be extended so that it can respond to node selection and change the selected panel in a panelStack component. The tree component does not have a selected node event listener, instead the commandLink component will be use to respond to a user's mouse click. The following code shows how the PanelSelectUserObject default constructor has been modified to get a reference to the PanelStackBean responsible for selecting the PanelStack component's selected panel:

PanelSelectUserObject.java
public PanelSelectUserObject(DefaultMutableTreeNode wrapper) {
    super(wrapper);
    // get a reference to the PanelStackBean from the faces context
    FacesContext facesContext = FacesContext.getCurrentInstance();
    Object panelStackObject =
            facesContext.getApplication()
                    .createValueBinding("#{panelStack}")
                    .getValue(facesContext);
    if (panelStackObject instanceof PanelStackBean){
        panelStack = (PanelStackBean)panelStackObject;
    }
}

The PanelSelectUserObject needs to be able to respond to a user click and update the internal state of the backing beans. The user clicks are handled by binding a method to the tree nodes action listener. Here is the code needed to respond to a user's click.

PanelSelectUserObject.java
public void selectPanelStackPanel(ActionEvent action){
    if (panelStack != null){
        panelStack.setSelectedPanel(displayPanel);
    }
}

The PanelStackBean object is introduced in this code block which is responsible for maintaining the state of selected panel stack in the panelStack component. The full source for this simple class is available with the tree-selection demo source code.

Setting up a tree component for user interaction is a relatively simple process which utilizes actionListener mechanisms in the JSF framework. Each tree node's backing bean is responsible for setting a selected panel in the panelStack component. The component nature of JSF allows for easy encapsulation of application logic which makes the application quick to build and debug. The follow is a screen shot of the tree-selection demo.




Customizing a Tree's Display


In the preceding section we have seen how the tree component iterates over a DefaultTreeModel binding and applies a template to each node in the model. The template in previous examples used commandLinks but the template could have used any of the ICEfaces components. In this section we will apply the full XP theme to a tree with multiple children.

The ICEfaces framework comes configured with three distinctly different CSS based themes for all components. The style sheets for these themes, XP, Royale and Rime are available in the resources folder of the ICEfaces bundle. In order apply the XP theme the following include is needed in your xhtml page:

<link href="./xmlhttp/css/xp/xp.css" rel="stylesheet" type="text/css"/>

For most ICEfaces components added, the above CSS file include is all that is needed. The tree component requires slightly more configuration to fully apply the theme. First, the tree component requires that an imageDir attribute is set which points to a location where the expand and contract control images can be found. Lastly, the node state icons must be set for each IceUserObject; branch contracted icon, branch expanded icon and finally a leaf icon. This may seem like a lot of work but it allows for a tree that can have multiple variations of branch and leaf icons. The following is an updated tree component declaration which is fully styled:

<ice:tree id="tree"
          value="#{tree.model}"
          var="item"
          hideRootNode="false"
          hideNavigation="false"
          imageDir="./xmlhttp/css/xp/css-images/">
    <ice:treeNode>
        <f:facet name="icon">
            <ice:panelGroup style="display: inline">
                 <h:graphicImage value="#{item.userObject.icon}"/>
            </ice:panelGroup>
        </f:facet>
        <f:facet name="content">
            <ice:panelGroup style="display: inline">
                <ice:commandLink
                        actionListener="#{item.userObject.selectPanelStackPanel}"
                        value="#{item.userObject.text}"/>
            </ice:panelGroup>
        </f:facet>
    </ice:treeNode>
</ice:tree>

In summary, the ICEfaces components can easily be styled using CSS and in some cases by specifying image directories as we have done with our tree. In the next example we are going to dynamically change a tree's DefaultTreeModel which will automatically be reflected in the view.


Dynamically Changing a Tree


In this next example, we are going to dynamically remove and add nodes from the tree. When a user selects a tree node they will optionally have a choice to copy the selected node or remove it from the tree model. The following is a screen capture of this application.

This demo will start with the same tree that we used in the tree style demo. We must first add an action listener to the the treeNode commandLink component to listen for user clicks. Next we add a panel which will display the selected node and have add/remove controls when a node is selected. The JSF code for dynamically changing a tree node is as follows:

<ice:panelGroup>
    <p>Dynamic Tree Node Control:</p>
    <ice:commandButton
        actionListener="#{tree.copySelectedNode}"
        disabled="#{tree.copyDisabled}" value="Copy" />

    <ice:commandButton
        actionListener="#{tree.deleteSelectedNode}"
        disabled="#{tree.deleteDisabled}" value="Delete" />
    <p/>
    <ice:outputText
        value="Selected Node: #{tree.selectedNodeObject.text}"
        escape="false" />
</ice:panelGroup>

The ActionListeners for the add and remove node controls can be added to the TreeBean class. When a node in the tree is clicked an instance variable in TreeBean references the source of the click. The reference to the selected node can them be used to copy or remove the selected node from the DefaultTreeModel when one of the corresponding commandButtons is pressed. Note that the root node is treated as a special case, if it is removed no more nodes can be added to the tree.

TreeBean.java
public void deleteSelectedNode(ActionEvent event){
    if (selectedNodeObject != null && !selectedNode.equals(ROOT_NODE_TEXT)){
        selectedNodeObject.deleteNode(event);
        selectedNodeObject = null;
    }
}

public void copySelectedNode(ActionEvent event){
    if (selectedNodeObject != null)
        selectedNodeObject.copyNode(event);
}




Tutorial Source Code Downloads


Example Source Notes
tree-basic tree-basic source code Simple example of how to setup a basic tree component and backing bean
tree-links tree-links source code Tree component which has commandLink components as nodes. When a node is clicked a new browser window is launched with the respective URL.
tree-selection tree-selection source code Tree component is used to manipulate the selected panel in a panel stack.
tree-style [tree-style source code |ICE Tree Tutorial^tree-style-tutorial.zip|Download Source Code] The XP theme is fully applied to a tree component producing a fully styled tree.
tree-dynamic tree-dynamic source code A tree components default tree model is manipulated by other Java Beans. This applicaiton shows how the Tree component can be dynamically changed.
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

© Copyright 2021 ICEsoft Technologies Canada Corp.