Michael Bevels

All | General | Java | Ruby
XML
20080617 Tuesday June 17, 2008
Dynamic Trees in JSF - The Easy Way

Another JSF project I've been working on required another dynamic JSF tree. However, this time the component library being used was IceFaces. If you recall, the last time I used RichFaces. I have to say that using IceFaces made this task MUCH easier.

The basic setup:

  • A “tree” structure that is made up of “node” objects. Each node has a parent node as well as a list of child nodes. Root nodes have a null parent. The tree object takes a list of root nodes. (see code below)
  • A JSF Tree. IceFaces was used this time. (see code below)
  • A JSF managed backing bean with ActionListeners and DAO access. ActionListeners are used for the CRUD operations on nodes. DAO access is used for directly updating the objects in the database. (see code below)
  • A great place to start is with one of IceFaces tree examples. I used this one: IceFaces Sample Tree Application

All of this sounds very familiar, right?

Here's where it starts to get different.

The Tree UI:

<ice:panelGroup style="border: 1px solid gray;">
                <ice:tree id="tree"
                          value="#{tree.model}"
                          var="item"
                          hideRootNode="false"
                          hideNavigation="false"
                        >
                    <ice:treeNode>
                        <f:facet name="content">
                            <ice:panelGroup style="display: inline">
                                <ice:commandLink
                                        actionListener="#{item.userObject.nodeClicked}"
                                        value="#{item.userObject.label}"/>
                            </ice:panelGroup>
                        </f:facet>
                    </ice:treeNode>
                </ice:tree>
            </ice:panelGroup>

Note that "tree.model" is passed in. Also note that there is a commandLink with actionListener. This sets the selected node id on the backing bean. Both of these things can be seen in the IceFaces sample tree application referenced above.

What you'll need to do to make this truly dynamic is add some controls to your tree display page and some backing bean methods. I'll use adding a node as an example.

Add the code below to the page that displays the tree. newNodeName is a field on the backing bean. addChildToSelectedNode is the method on the backing bean that takes "newNodeName", creates a node, and persists it to the database.

<div id="addNode">
    <table width="100%">
        <tr>
            <td><ice:outputText value="Name:"/></td>
            <td><ice:inputText value="#{nodeManagement.newNodeName}"/></td>
        </tr>
        <tr>
            <td>
                <ice:commandButton 
                                   value="Add Child to Selected Node"
                                   action="#{nodeManagement.addChildToSelectedNode}"
                                   partialSubmit="true"/>
            </td>
        </tr>
    </table>
</div>

<ice:outputText
        value="Selected Node: #{tree.selectedNodeObject.label}"
        escape="false" style="font-weight:bold;"/>

The method that add a node to the tree is shown below. Note the last line (tree.buildTreeModel()). This rebuilds the tree model that used to display the tree. If this method is not implemented on the Tree bean, you will have to refresh the page manually to see the node that was just created.

    public void addNodeToSelectedNode() {
        //you'll probably want to handle errors here
        //selectedNodeId can't be blank or null
        //newNodeName shouldn't be blank or null

        Node newNode = new Node();
        
        //load the selectedNode using the selectedNodeId
        Node selectedNode = nodeDao.load(selectedNodeId);
        
        //get the selected node's Set of children
        Set<Node> childrenOfParent = selectedNode.getChildren();

        //create node to be added
        newNode.setChildren(null);
        newNode.setLabel(newNodeName);
        newNode.setParent(selectedNode);

        //add new node to selectedNode
        childrenOfParent.add(newNode);

        //saving selected node saves selectedNode as well as newNode
        nodeDao.save(selectedNode);

        //rebuild tree
        tree.buildTreeModel();
    }

The node: (more of the same from last time)

import javax.persistence.*;
import java.util.List;
import org.hibernate.annotations.GenericGenerator;

@Entity
@GenericGenerator(
        name = "hibernate-uuid", strategy = "uuid"
)
public class Node {
    @Id
    @GeneratedValue(generator = "hibernate-uuid")
    private String id;

    @Column(nullable = false)
    private String label;

    @OneToMany(mappedBy = "parent", cascade = {CascadeType.ALL})
    private List children;

    @ManyToOne
    @JoinColumn(name = "fk_parent_id")
    private Node parent;

   // Getters and Setters...
}

I think is a much more straight forward way of creating a dynamic JSF tree. Using the IceFaces Sample Application is a good starting point. Adding the few snippets of code I've provided above should get you the rest of the way.

Posted by mbevels Jun 17 2008, 08:50:12 AM EDT
20080423 Wednesday April 23, 2008
Passing Params to a New Window with JSF

Here's what I wanted to accomplish:

A user performs a search. The user sees a list of results. The user clicks on a "details" link for a single record in the list of results. The action of clicking on the details link launches a popup window that displays the details of the record selected.

Here's how I thought it should work:

The page with the list of results and the details popup page would share the same backing bean. Each "details" link (commandLink, commandButton, outputLink...doesn't matter) in the list of results would use f:setPropertyActionListener to set the "selectedRecordId" on the backing bean. An onclick event on the details link would launch a popup window. The popup window would get the "selectedRecordId" from the shared backing bean along with other data relating to the selected record.

Sounds pretty straight forward, right? Wrong. The problem is that any javascript events are executed before the form is submitted. So in the situation described above, the popup launches first and THEN the setProperty ActionListener sets selectedRecordId on the backing bean. When the popup window loads, it can't retrieve selectedRecordId because it hasn't been set yet.

A Solution!

Here's a high level overview of how it works. The javascript on the details link passes the recordId on the URL. When the details popup page accesses the "selectedRecordId" on the backing bean, the getter for this field calls a method which retrieves the value of "recordId" that was passed on the URL. This value can then be set to "selectedRecordId" on the backing bean. Both the page with the dataTable results and the details popup page now have access to "selectedRecordId".

The Code!

I used icefaces in the example code below.

The dataTable with details popup link:

<ice:dataTable id="searchResults" value="#{basicSearch.records}" var="item">
    <ice:column>
        <f:facet name="header">
            <ice:commandSortHeader
                    columnName="#{basicSearch.labelColumnName}"
                    arrow="true">
                <ice:outputText value="#{basicSearch.labelColumnName}"/>
            </ice:commandSortHeader>
        </f:facet>
        <ice:outputText value="#{item.label}"/>
    </ice:column>

    <ice:column>
        <f:facet name="header">
            <ice:outputText value="Details"/>
        </f:facet>

        <ice:commandButton image="/resources/images/icon_small_info.gif"
                           onclick="doPopup('#{request.contextPath}/recordDetails/details.jspx?recordId=#{item.recordId}'); ">
        </ice:commandButton>
    </ice:column>
</ice:dataTable>

The important stuff from the backing bean:

    private String selectedRecordId;

    public String getSelectedRecordId() {
        selectedRecordId = (String) getParamValue("#{param.recordId}");
        return selectedRecordId;
    }

    public void setSelectedRecordId(String selectedRecordId) {
        this.selectedRecordId = selectedRecordId;
    }

    public Object getParamValue(String s) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        Object value = facesContext.getApplication().createValueBinding(s).getValue(facesContext);
        return value;
    }

The javascript:

function doPopup(source) {
    popup = window.open(source, "popup", "height=600,width=900)");
    popup.focus();
}

Accessing the recordId from the popup window:

<ice:outputText  value="#{metadataView.selectedRecordId}"/>

Concurrent DOM Access so both windows can access the same backing bean: (web.xml)

    <context-param>
        <param-name>com.icesoft.faces.concurrentDOMViews</param-name>
        <param-value>true</param-value>
    </context-param>
Posted by mbevels Apr 23 2008, 02:35:33 PM EDT
20080422 Tuesday April 22, 2008
Dynamic Trees in JSF

A JSF project that I've been working on required a dynamic tree. Not just a tree that loads dynamically, but a tree that a user can perform CRUD operations on and instantly see the changes they've made. I'm going to walk through an example of how to accomplish this.

The basic setup:

  • A “tree” structure that is made up of “node” objects. Each node has a parent node as well as a list of child nodes. Root nodes have a null parent. The tree object takes a list of root nodes. (see code below)
  • A JSF Tree. I used the RichFaces tree because RichFaces was already being used throughout the application, but this can be applied to other JSF trees as well. (see code below)
  • A JSF managed backing bean with ActionListeners and DAO access. ActionListeners are used for the CRUD operations on nodes. DAO access is used for directly updating the objects in the database. (see code below)

The node:

import javax.persistence.*;
import java.util.List;
import org.hibernate.annotations.GenericGenerator;

@Entity
@GenericGenerator(
        name = "hibernate-uuid", strategy = "uuid"
)
public class Node {
    @Id
    @GeneratedValue(generator = "hibernate-uuid")
    private String id;

    @Column(nullable = false)
    private String label;

    @OneToMany(mappedBy = "parent", cascade = {CascadeType.ALL})
    private List children;

    @ManyToOne
    @JoinColumn(name = "fk_parent_id")
    private Node parent;

   // Getters and Setters...
}

The tree:

<rich:tree id="tree" ajaxSubmitSelection="true" switchType="client" onselected="return false;">
    <rich:recursiveTreeNodesAdaptor var="node" roots="#{treeCreator.rootNodes}" nodes="#{node.getChildren}">
        <rich:treeNode data="#{node.id}">
            <!--Node Name-->
            <h:outputText value="#{node.label}" />

            <!-- Control Buttons -->
            <!--Button used to toggle addNode Div -->
            <input type="image" value="Add Child" title="Add Child"
                   onclick="toggleLayer('addNode#{node.id}');return false;"
                   src="resources/images/controls_addlevel.gif"/>
            <!--Button used to toggle editNode Div -->
            <input type="image" value="Edit Node" title="Edit Node"
                   onclick="toggleLayer('editNode#{node.id}');return false;"
                   src="resources/images/controls_edit.gif"/>
            <!--Button for Deleting node -->
            <a4j:commandButton actionListener="#{treeCreator.deleteNode}"
                               data="#{node.id}" value="Delete Node" reRender="tree"
                               image="/resources/images/controls_delete.gif"
                               title="Delete Node"
                               onclick="if(!confirm('Delete Node?')){ return; };"/>


            <!-- Divs for name entry/edit-->
            <div id="addNode#{node.id}" style="display:none;">
                <h:outputText value="Node Name: "/>
                <h:inputText binding="#{treeCreator.nodeAdd}"/>
                <a4j:commandButton value="Save and Create" title="Save and Create"
                                   actionListener="#{treeCreator.addNode}"
                                   data="#{node.id}"
                                   onclick="toggleLayer('addNode#{node.id}');"
                                   image="/resources/images/icon_small_save.gif"
                                   reRender="tree"/>
                <input type="image" src="resources/images/icon_small_cancel.gif" value="Cancel"
                       onclick="toggleLayer('addNode#{node.id}');return false;" title="Cancel"/>
            </div>

            <!--Div for Editing an Org -->
            <div id="editNode#{node.id}" style="display:none;">
                <h:outputText value="New Node Name: "/>
                <h:inputText binding="#{treeCreator.editNode}"/>
                <a4j:commandButton value="Save Changes" title="Save Changes"
                                   actionListener="#{treeCreator.editNode}"
                                   data="#{node.id}"
                                   onclick="toggleLayer('editNode#{node.id}');"
                                   image="/resources/images/icon_small_save.gif"
                                   reRender="tree"/>
                <input type="image" src="resources/images/icon_small_cancel.gif" value="Cancel"
                       onclick="toggleLayer('editNode#{node.id}');return false;" title="Cancel"/>
            </div>
        </rich:treeNode>
    </rich:recursiveTreeNodesAdaptor>
</rich:tree>

*Note that toggleLayer() is a javascript function that hides/unhides the contents of a given div tag.

*Note that the inputText fields have bindings to the backing bean.

The ActionListeners:

 public void addNode(ActionEvent event) {
        //get the button that fired the event   
        HtmlAjaxCommandButton button = (HtmlAjaxCommandButton) event.getComponent();
        //get ID of the parent Node - it's stored in the data attribute of the add button
        String parentNodeId = (String) button.getData();
        //get parent node object
        Node parentNode = nodeDao.getById(parentNodeId);
        //create new node object and set label, parent, children, etc
        Node nodeToAdd = new Node();
        nodeToAdd.setLabel((String) addNode.getValue());
        nodeToAdd.setParent(parentNode);
        nodeToAdd.setChildren(new HashSet());
        parentNode.getChildren().add(nodeToAdd);
        //save node to DB
        nodeDao.saveOrUpdate(parentNode);
    }

    public void editNode(ActionEvent event) {
        //get the button that fired the event
        HtmlAjaxCommandButton button = (HtmlAjaxCommandButton) event.getComponent();
        //get ID of Node to edit - it's stored in the data attribute of the edit button
        String nodeId = (String) button.getData();
        //load node from DB
        Node nodeToEdit = nodeDao.getById(nodeId);
        //update node label from UI input
        nodeToEdit.setLabel((String) editNode.getValue());
        //update node label in DB
        nodeDao.update(nodeToEdit);
    }

    public void deleteNode(ActionEvent event) {
        //get ID of Node to delete - it's stored in the data attribute of the delete button
        HtmlAjaxCommandButton button = (HtmlAjaxCommandButton) event.getComponent();
        String nodeId = (String) button.getData();
        //get node object via Dao
        Node nodeToDelete = nodeDao.getById(nodeId);

        //set reference from parent to null
        Node parentNode = nodeToDelete.getParent();

        //don't delete the Root Node
        if (parentNode != null) {
            Set childNodes = parentNode.getChildren();
            childNodes.remove(nodeToDelete);
            //put updated list of childNodes on parentNode
            parentNode.setChildren(childNodes);
            //save parentNode to DB
            nodeDao.save(parentNode);
            //set parent to null
            nodeToDelete.setParent(null);
            //delete the node from the DB
            nodeDao.delete(nodeToDelete);
        }
    }

The managed bean within faces config:

    <managed-bean>
        <description>
            Backing bean for the tree
        </description>
        <managed-bean-name>treeCreator</managed-bean-name>
        <managed-bean-class>path.to.the.TreeCreator</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
        <managed-property>
            <property-name>nodeDao</property-name>
            <value>#{nodeDao}</value>
        </managed-property>
    </managed-bean>

Here's how it all fits together using the “Add Child” operation as an example:

  1. A user visits the tree page and sees the tree with a single root node:
  2. The user clicks the “Add Child” button on the root node:
    1. The “addNode” div is unhidden (via javascript) which displays an input text box, a save button, and a cancel button to the right of the root node.
  3. The user types in a name for the node and clicks the save button. Note that the save button has a few attributes including actionListener=”treeCreator.addNode” and data=”#{node.id}”.
    1. The “addNode” div is hidden again (via javascript).
    2. The addNode ActionListener is called. See the addNode ActionListener.
      1. The save button is retrieved from the ActionEvent that was passed to this ActionListener. The “data” attribute stored on this save button is retrieved which contains the ID of the soon-to-be-parent node. The parent node object is loaded via dao access. A new node (with user specified name which is retrieved via binding of the inputText box to backing beans) is added to the parent's children list. The parent node is saved which also saves the new child object to the database.
  4. The tree is refreshed and the new node appears. Note that the tree “switchType” can be set to client, server, or ajax. This will effect how the page refreshes and the tree loads.

Edit and delete follow a similar process with slightly different implementations in their ActionListeners.

Posted by mbevels Apr 22 2008, 02:15:23 PM EDT