Getting started with ADF Faces in JDeveloper 10.1.3 – Using the Tree Component, comparison with MyFaces Tomahawk Tree2

After having tried the tree2 component from the MyFaces Tomahawk library – see: Apache MyFaces (open source JSF implementation) – Using the Tree2 Component with JDeveloper 10.1.3
-, I wanted to do something similar with MyFaces Cherokee aka ADF
Faces. Unfortunately, the Apache MyFaces Cherokee project has not yet
been released, so I go for the ADF Faces set in JDeveloper 10.1.3 EA1.
Supposedly this is interchangeable with Cherokee once released.

The
af:tree element seems somewhat sober at first glance. It also seems
less smart than the tree2 component from the Tomahawk library that I
discussed in the previous post. Well, I am getting ahead of myself.
Let’s first get things going.

Steps

The first steps are
more or less generic for anyone starting out with ADF Faces in
JDeveloper 10.1.3 EA. Oracle has many tutorials and howto’s on OTN that
describe these basic steps.

  • Download JDeveloper 10.1.3 EA1 and install it.
  • Run JDeveloper 10.1.3
  • Create
    a new Application Workspace – I call it AdfFacesTrials – and a new
    project – I call it AdfFacesTreeTrials. I did not select any special
    Technology scope.
  • From the New Gallery, pick Web Tier, JSF and
    choose JSF JSP. The wizard for creating a JSF page – or rather a JSP
    that contains JSF components – starts. I call my new JSP
    MyFirstAdfFacesPage.jsp.  From a tutorial on OTN:Build a Master-Detail Web Page with JDeveloper and ADF Business Components I learned that I should include the following tag libraries on page 4 of the wizard (Tag Libraries):

JSF Core 1.0
JSF HTML 1.0

ADF Faces Components
ADF Faces HTML
Getting started with ADF Faces in JDeveloper 10.1.3 - Using the Tree Component, comparison with MyFaces Tomahawk Tree2 adfFacesTLD

  • I
    close the wizard and my new JSP page is created; it contains the proper
    Tag Library directives as well as the basic outline of the JSF
    structure: <f:view> and <h:form>.
  • Run the page to
    verify the set up is correct. The JSP should display in the browser,
    showing only its title – MyFirstAdfFacesPage.

Now it will get more interesting, as we are actually going to use the tree component from the ADF Faces Core Component Palette.
Getting started with ADF Faces in JDeveloper 10.1.3 - Using the Tree Component, comparison with MyFaces Tomahawk Tree2 adfFacesComponentPaletteTree

I
drag the Tree component to my JSP, positioning it right after the
<h:form> tag. From the documentation – see the resources below –
I have more or less gathered what the properties are that must be set.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@ page contentType="text/html;charset=windows-1252"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://xmlns.oracle.com/adf/faces" prefix="af"%>
<%@ taglib uri="http://xmlns.oracle.com/adf/faces/html" prefix="afh"%>
<f:view>
  <html>
    <head>
      <meta http-equiv="Content-Type"
            content="text/html; charset=windows-1252"/>
      <title>MyFirstAdfFacesPage</title>
    </head>
    <body><h:form>
        <af:tree var="node" value="#{libaryTreeHandler.libraryTreeModel}">
          <f:facet name="nodeStamp">
            <h:panelGroup>
              <af:outputText value="#{node.description}"/>
              <af:outputText value=" (#{node.childCount})"/>
              <af:outputText value=" (#{node.nodeType})" 
                             rendered="#{node.nodeType == 'author'}"
              />
            </h:panelGroup>
          </f:facet>
        </af:tree>
      </h:form></body>
  </html>
</f:view>

The
<af:tree> element has a <f:facet> child element named
nodeStamp. This facet is rendered for each node in the tree. Unlike the
MyFaces tree2 component that had named facets for every nodeType –
where the name of the facet matches the nodeType – ADF Faces’ af:tree
has a single facet for every node. However, using multiple panelGroups
– one per nodeType – and tying the rendered property of these
panelGroups to the nodeType they represent, we can achieve that same
effect in ADF Faces.

In this case I have just a single
panelGroup with three elements: the description of the node, the number
of childnodes (between parentheses) and the node type – but the latter
only for nodes of type author.

Now I need to create the
backing bean to provide the TreeModel expected by the <af:tree>
component. I dug a little round the JavaDocs for TreeModel and came up
the class ChildPropertyTreeModel, that can be instantiated with a
Collection of node objects:

libraryTreeModel = new ChildPropertyTreeModel(library, "children");

where
library is a Collection (an ArrayList in this case) of objects. There
are two requirements: the objects in the Collection must all be of the
same type and there must be a property referring to the Collection of
child-nodes of each node object. We are free to design our own Node
object, as long as we adhere to these two requirements. So here is my
GenericTreeNode class:

package nl.amis.adffaces.library;

import java.util.ArrayList;
import java.util.Collection;

public class GenericTreeNode {

    private String description ;
    private String longDescription ;
    private Collection children;
    private String nodeType;
    private boolean nodeSelected;
    
    public GenericTreeNode() {
    }

    public GenericTreeNode(String description, String nodeType) {
      this.description = description;
      this.nodeType = nodeType;
      this.children = new ArrayList();
    }
    public GenericTreeNode(String description, String longDescription, Collection children) {
      this.description = description;
      this.longDescription = longDescription;
      this.children = children;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    public void setLongDescription(String longDescription) {
        this.longDescription = longDescription;
    }

    public String getLongDescription() {
        return longDescription;
    }

    public void setChildren(Collection children) {
        this.children = children;
    }

    public Collection getChildren() {
        return children;
    }
    
    public int getChildCount() {
      if (children == null)
        return 0;
      else
        return children.size();
    }

    public void setNodeType(String nodeType) {
        this.nodeType = nodeType;
    }

    public String getNodeType() {
        return nodeType;
    }

    public void setNodeSelected(boolean nodeSelected) {
        this.nodeSelected = nodeSelected;
    }

    public boolean isNodeSelected() {
        return nodeSelected;
    }
}

In the class LibraryTreeHandler – a class that I have instructed JSF to manage for me under the name libraryTreeHandler:

<faces-config xmlns="http://java.sun.com/JSF/Configuration">
  <managed-bean>
    <managed-bean-name>LibraryTreeHandler</managed-bean-name>
    <managed-bean-class>nl.amis.myfaces.tree.LibraryHandler</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
</faces-config>

I
set up the nodes collection and tree model expected by <af:tree>
under the libraryTreeModel property of the libraryTreeHandler managed
bean.

package nl.amis.adffaces.library;

import java.util.ArrayList;
import java.util.List;

import oracle.adf.view.faces.model.ChildPropertyTreeModel;
import oracle.adf.view.faces.model.TreeModel;

public class LibraryTreeHandler {

    private TreeModel libraryTreeModel;

    public LibraryTreeHandler() {

        GenericTreeNode library = new GenericTreeNode("Library", "folder");

        GenericTreeNode ben = new GenericTreeNode("Ben Elton", "author");
        GenericTreeNode book = new GenericTreeNode("Post Mortem", "book");
        ben.getChildren().add(book);
        book = new GenericTreeNode("Blast from the Past", "book");
        ben.getChildren().add(book);
        book = new GenericTreeNode("Stark", "book");
        ben.getChildren().add(book);

        GenericTreeNode rod = new GenericTreeNode("Rod Johnson", "author");
        book = new GenericTreeNode("J2EE Design and Development", "book");
        rod.getChildren().add(book);
        book =
            new GenericTreeNode("J2EE without Enterprise Java Bean", "book");
        rod.getChildren().add(book);
        book =
            new GenericTreeNode("Professional Java Development With The Spring Framework",
                                   "book");
        rod.getChildren().add(book);

        GenericTreeNode orson =
            new GenericTreeNode("Orson Scott Card", "author");
        book = new GenericTreeNode("Ender's Game", "book");
        orson.getChildren().add(book);
        book = new GenericTreeNode("Xenocide", "book");
        orson.getChildren().add(book);
        book = new GenericTreeNode("Shadow Puppets", "book");
        orson.getChildren().add(book);

        library.getChildren().add(ben);
        library.getChildren().add(rod);
        library.getChildren().add(orson);

        // create the list of root nodes:
        List nodes = new ArrayList();
        nodes.add(library);

        libraryTreeModel = new ChildPropertyTreeModel(library, "children");
    }

    public TreeModel getLibraryTreeModel() {
        return libraryTreeModel;
    }
}

Now
I can run the JSP: JSF will instantiate the libraryTreeHandler that in
its constructor sets up the TreeModel and makes it available under its
the treeModel property where the <af:tree> element can get it. It
looks like this in the browser:

Getting started with ADF Faces in JDeveloper 10.1.3 - Using the Tree Component, comparison with MyFaces Tomahawk Tree2 adfFacesTree1

Of course it is similar to what I did with the MyFaces Tomahawk Tree2 component:
Getting started with ADF Faces in JDeveloper 10.1.3 - Using the Tree Component, comparison with MyFaces Tomahawk Tree2 myfacesTree2 1

There
are some differences: the ADF Faces Tree does not let us specify the
icons for Node, Expanded and Collapsed like Tree2 does. Furthermore,
ADF Faces Tree does not look ahead to check whether a node has child
nodes: the Expand icon is always displayed, regardless of whether there
are in fact child nodes or not. I am not certain about the
configuration options for af:tree as the documentation is not
completely available and I have not been able to find any sample code
of the tree component. Perhaps it is much richer than I currently
suspect.

Make the tree application a little more interesting

Just like I did with MyFaces Tree2, I will bring a little more spark to the tree application. We
will allow Author nodes to be selected and we will display the
Biography of the currently selected author – marked in the tree with an
asterisk – next to the tree.

I
am sure that the ADF Faces Tree Component has its own events and built
in ways of handling those events. However, I have not been able to work
this out from the documentation, so I create my own CommandLink for the
nodes that can be selected. I also keep track of the currently selected
node myself – again, there very well be a mechanism in ADF Faces that
keeps track of the selected node, but it was not obvious to me. I make
small modifcations in the JSP to display an asterisk for the selected
node, as well as the biography for the currently selected node – if any.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@ page contentType="text/html;charset=windows-1252"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://xmlns.oracle.com/adf/faces" prefix="af"%>
<%@ taglib uri="http://xmlns.oracle.com/adf/faces/html" prefix="afh"%>
<f:view>
  <html>
    <head>
      <meta http-equiv="Content-Type"
            content="text/html; charset=windows-1252"/>
      <title>MyFirstAdfFacesPage</title>
    </head>
    <body><h:form>
        <h:panelGrid columns="2" border="1">
          <af:tree var="node" value="#{libaryTreeHandler.libraryTreeModel}"
                   binding="#{libaryTreeHandler.tree}" varStatus="status">
            <f:facet name="nodeStamp">
              <h:panelGroup>
                <h:outputText value="*"
                              rendered="#{libaryTreeHandler.selectedNode == node}"/>
                <h:commandLink id="my" title="#{node.description}"
                               actionListener="#{libaryTreeHandler.process}"
                               rendered="#{node.nodeType == 'author'}">
                  <h:outputText value="#{node.description}"/>
                  <f:param value="#{node.description}" name="authorName"/>
                </h:commandLink>
                <af:outputText value="#{node.description}"
                               rendered="#{node.nodeType != 'author'}"/>
                <af:outputText value=" (#{node.childCount})"/>
                <af:outputText value=" (#{node.nodeType})"
                               rendered="#{node.nodeType == 'author'}"/>
              </h:panelGroup>
            </f:facet>
          </af:tree>
          <h:panelGroup rendered="#{libaryTreeHandler.selectedNode!= null }">
            <f:verbatim>
              <h3>Results of the currently selected author</h3>
            </f:verbatim>
            <h:outputText style="font-size:70%; font-family:arial"
                          value="#{libaryTreeHandler.selectedNode.longDescription}"/>
          </h:panelGroup>
        </h:panelGrid>
      </h:form></body>
  </html>
</f:view>

For
some hugely annoying reason, when the command link is invoked, the
ActionEvent process in my bean is called three times, once for every
Author node that has such a CommandLink component. What’s worse, the
UIParam authorName has a different value for each of these three
invocations, namely the description of the Author node. So at this
point, I cannot make use of the normal UIParam processing. Instead, I
retrieve the value of the authorName Parameter from the
HttpServletRequest that I retrieve from ExternalContext that is
available on the current instance of the FacesContext. I cannot help
but feel that this is a bug in the tree component – but again, perhaps
I do not understand it all correctly. The code handling the ‘select
node’ event now becomes:

package nl.amis.adffaces.library;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;

import oracle.adf.view.faces.component.core.data.CoreTree;
import oracle.adf.view.faces.model.ChildPropertyTreeModel;
import oracle.adf.view.faces.model.TreeModel;


public class LibraryTreeHandler {

    private CoreTree tree;
    private TreeModel libraryTreeModel;
    private GenericTreeNode selectedNode = null;

    public LibraryTreeHandler() {
        GenericTreeNode library = new GenericTreeNode("Library", "folder");

        GenericTreeNode ben = new GenericTreeNode("Ben Elton", "author");
        String bio = " Already a successful comedian, Ben Elton turned to writing situation comedies during the 1980s and penned BBC classics such as \"The Young Ones\", \"Blackadder\", and during the 1990s \"The Thin Blue Line\".\n" + 
            "    He provided lyrics for Andrew Lloyd Webber Musical, The Beautiful Game, which was nominated for Best Musical at the Laurence Olivier Theatre Awards in 2001 (2000 season).\n" + 
            "    His comedy, Popcorn performed at the Apollo Theatre, was awarded the 1998 Laurence Olivier Theatre Award for Best New Comedy of the 1997 season.\n" + 
            "    Has three children : Bert, Lottie and Fred. Is co-writer of the Queen Musical 'We Will Rock You' with the band itself.\n" + 
            "Birth name: Benjamin Charles Elton. Height: 5' 8\" (1.73 m) \n"; 
        ben.setLongDescription(bio);
        GenericTreeNode book = new GenericTreeNode("Post Mortem", "book");
        ben.getChildren().add(book);
        book = new GenericTreeNode("Blast from the Past", "book");
        ben.getChildren().add(book);
        book = new GenericTreeNode("Stark", "book");
        ben.getChildren().add(book);

        GenericTreeNode rod = new GenericTreeNode("Rod Johnson", "author");
            bio = "Rod Johnson is an enterprise Java architect with extensive experience in the insurance, dot-com, and financial industries. He was the J2EE architect of one of Europe's largest web portals, and he has worked as a consultant on\n" + 
            "a wide range of projects.\n Rod has an arts degree majoring in music and computer science from the University of Sydney. He obtained a Ph.D. in musicology before returning to software development. He has been working with both Java and J2EE since\n" + 
            "their release and is actively involved in the Java Community Process as a member of the JSR-154 (Servlet 2.4) and JDO 2.0 Expert Groups. He is the author of several best-selling books, like \"Expert One-on-One J2EE Design and Development\" (Wrox, 2002) and has contributed to several other books on J2EE since 2000. As founder of the Spring Framework (www.springframework.org), he speaks frequently at leading industry conferences.\n";
        rod.setLongDescription(bio);
        book = new GenericTreeNode("J2EE Design and Development", "book");
        rod.getChildren().add(book);
        book =
            new GenericTreeNode("J2EE without Enterprise Java Bean", "book");
        rod.getChildren().add(book);
        book =
            new GenericTreeNode("Professional Java Development With The Spring Framework",
                                   "book");
        rod.getChildren().add(book);
        
        bio = "Nobody had ever won the Hugo and Nebula awards for best novel two years in a row, until Orson Scott Card received them for Ender's Game and its sequel, Speaker for the Dead, in 1986 and 1987. The third novel in the series, Xenocide, was published in 1991, and the fourth and seemingly final volume, Children of the Mind, was published in August 1996. However, the Ender cycle now includes the new parallel series that began with Ender's Shadow in 1999, followed by Shadow of the Hegemon in 2001, and continued with Shadow Puppets in 2002. Warner Brothers also recently announced that it has made a deal for director Wolfgang Petersen to bring Ender's Game to the big screen. Born in Richland, Washington, Card grew up in California, Arizona, and Utah. He lived in Brazil for two years as an unpaid missionary for the Mormon Church. He received degrees from Brigham Young University (1975) and the University of Utah (1981). He currently lives in Greensboro, North Carolina. He and his wife, Kristine, are the parents of five children: Geoffrey, Emily, Charles, Zina Margaret, and Erin Louisa (named for Chaucer, Bronte and Dickinson, Dickens, Mitchell, and Alcott, respectively).";
        GenericTreeNode orson =
            new GenericTreeNode("Orson Scott Card", "author");
        orson.setLongDescription(bio);
        book = new GenericTreeNode("Ender's Game", "book");
        orson.getChildren().add(book);
        book = new GenericTreeNode("Xenocide", "book");
        orson.getChildren().add(book);
        book = new GenericTreeNode("Shadow Puppets", "book");
        orson.getChildren().add(book);

        library.getChildren().add(ben);
        library.getChildren().add(rod);
        library.getChildren().add(orson);

        // create the list of root nodes:
        List nodes = new ArrayList();
        nodes.add(library);

        libraryTreeModel = new ChildPropertyTreeModel(nodes, "children");
        }

        public TreeModel getLibraryTreeModel() {
        return libraryTreeModel;
        }
    
    public void setTree(CoreTree tree) {
        this.tree=tree;
    }
    
    public CoreTree getTree() {
        return tree;
    }
    
    private GenericTreeNode findNode(String description, Collection<GenericTreeNode> nodes) {
        // iterate through the collection, verify each node and then - if it does not match - verify its child collection
        for (GenericTreeNode node: nodes) {
            if (node.getDescription().equalsIgnoreCase(description)) { return node;}
            GenericTreeNode childNode = findNode(description, node.getChildren());
            if (childNode != null) return childNode;            
        }
        return null;
    }
    
    public void process(ActionEvent event) throws AbortProcessingException {
    FacesContext context = FacesContext.getCurrentInstance();
    javax.servlet.http.HttpServletRequest req = (javax.servlet.http.HttpServletRequest)context.getExternalContext().getRequest();
    String authorName = req.getParameter("authorName");
    // find the selected node in the tree model:    
    Collection roots = (Collection)libraryTreeModel.getWrappedData();
    GenericTreeNode selectedNode = findNode( authorName, roots);
    this.selectedNode = selectedNode;

    // This would seem the more logical code for handling the event and its 
    // associated parameter. Unfortunately, it turns out that this method is
    // invoked for each of the Author nodes in the tree, regardless of the
    // node whose commandlink was actually clicked on; what's more: for each
    // of these invocations, the value of the UIParameter authorName is equal
    // to the node being dealt with. So for three authors, even if we select
    // the second one, this method is called three times with three different
    // values for the UIParameter. However, reading the parameter authorName
    // directly from the HttpServletRequest does get us a single value.
    // I probably make somehow inappropriate use of the ADF Faces tree component...
    /*
    UIComponent component = (UIComponent)event.getSource();
    List children = component.getChildren();
    for (int i=0;i<children.size();i++) {
      if (children.get(i) instanceof UIParameter) {
        UIParameter currentParam = (UIParameter)children.get(i);
        if (currentParam.getName().equals("authorName")) {
            String nodeAuthorName = (String)currentParam.getValue();            
        }            
      }
    }
    }
    // deal with selected Author node
    */
    }

    public void setSelectedNode(GenericTreeNode selectedNode) {
        this.selectedNode = selectedNode;
    }

    public GenericTreeNode getSelectedNode() {
        return selectedNode;
    }
    }

I have the feeling that I am missing out on some of the features
available to me in ADF Faces’s tree component, but at least I have my
application working. The result looks like this:

Getting started with ADF Faces in JDeveloper 10.1.3 - Using the Tree Component, comparison with MyFaces Tomahawk Tree2 adfFacesTree2

Next steps

Link the Tree up with a proper Database backed Model, for example
using Spring or EJB 3.0. Also: look at other ADF Faces components and
how they compare to MyFaces Tomahawk components. And of course delve
deeper into what the af:tree can do – since obviously I have only
scratched the surface. If anyone reading this has pointers on how to
dig deeper: please share them!

Resources

Working with ADF Faces Components – On Line documentation for JDeveloper 10.1.3

Download the JDeveloper 10.1.3EA Application Workspace for the AdfFacesTreeTrials application as discussed in this article, including all sources: AdfFacesTrials.zip

More specifically: Documentation for the <af:tree> element on OTN

14 Comments

  1. Punit November 11, 2009
  2. Dev June 3, 2009
  3. Lucas Jellema June 19, 2007
  4. Dimd80 October 17, 2006
  5. Adam September 12, 2006
  6. Nuruthin Ahammed September 10, 2006
  7. Nuruthin Ahammed September 7, 2006
  8. Ric Smith August 31, 2006
  9. shivkumar April 24, 2006
  10. Michal April 21, 2006
  11. Michal April 21, 2006
  12. Michal April 20, 2006
  13. Chintan March 19, 2006
  14. Deep Forest January 12, 2006