Saturday, 12 May 2007

Back to programming: Programmatic ADF Faces Tree component

Typically ADF Faces components in our JDeveloper 10.1.3 applications are data bound, meaning they map to objects in the binding layer as specified in separate pageDef files. This is expressed in our components through EL references to bindings}.

However what if we don't want to map to a binding layer object, but instead want to construct the data elements programmatically? For this blog we'll consider the example of an <af:tree> component to demonstrate this, showing how we construct the elements in the tree programmatically rather than relying on bindings.

Our goal is to construct an <af:tree> component comprised of <af:goLink> tags and looks as follows:

Logically understanding our hierarchy

Before we consider the <af:tree> component specifically, have a think about what the hierarchy represents. Each node in the tree has a text title, and a hyperlink destination. Each node is a child to a parent node, and each node may be comprised of 0, 1 or more child nodes. Collectively this represents our tree and is a simple logical concept.

Understanding the ADF Faces Tree component

The code for an <af:tree> in a web page would typically look as follows:

<af:tree var="node" value="#{treeModel.model}">
  <f:facet name="nodeStamp">
    <af:goLink text="#{node.text}" destination="#{node.destination}"/>
  </f:facet>
</af:tree>


In our example you'll note that the nodes within the tree are comprised of <af:goLink> tags that render the links in our tree above. You'll also note if you constructed a <af:tree> tag using the drag n drop facilities of JDeveloper based on a data bound component from the data control palette, the code would look very similar. In other words there isn't anything really special about the code above besides the use of <af:goLink> tags.

The <af:tree value> attribute demands at runtime an oracle.adf.view.faces.model.TreeModel instance. This class is the data model for the ADF Faces Tree component, where rows in the tree may contain other trees, or alternatively described, nodes in the tree can contain children nodes which in turn can also have children nodes and so on.

The nodeStamp facet of the <af:tree> walks the TreeModel, creating for each node the subtags contained within the <f:facet> tag, in this case an <af:goLink> tag. You'll note the <af:goLink> tag makes reference to the current node in the TreeModel as it walks the tree via the <af:tree> tag's var="node" attribute.

So we now logically have an understanding of what we want to model, and we also understand what the <af:tree> component wants. Lets consider our design solution.

Keeping it simple with a POJO

From the design point of view, to model the hierarchy we'd like to create our own very simple POJO TreeItem that stores a String text and String destination required for each goLink, along with a List of children TreeItem objects to reflect the hierarchy. At runtime we'll construct this tree programmatically populating the hierarchy in any way we desire.

The POJO approach is very easy to understand and implement. However the problem with our POJO approach is the datatype doesn't match that required by the <af:tree>, namely oracle.adf.view.faces.model.TreeModel. How do we address this?

Creating an Adapter between our TreeItem and TreeModel

In the OO world they have the concept of the adapter design pattern. An Adapter class is one that wraps another interface, to provide an interface the client expects. In our case we have our POJO TreeItem interface, but our client the <af:tree> demands an oracle.adf.view.faces.model.TreeModel, so we'll provide a class called TreeModelAdapter to work as the adapter. It will be this class that we'll define in our faces-config.xml file as a managed bean for the <af:tree> control to make use of at runtime.

The TreeModelAdapter will internally store our TreeItem hierarchy and TreeModel, and provide accessor methods to access and manipulate these items.

Coding the solution

The following describes the steps in coding our design solution. It assumes you've already created your web page with the <af:tree> code as previous:

Create our POJO Java Bean TreeItem class as follows:

package view;

import java.util.List;

public class TreeItem {
  private String _text = null;
  private String _destination = null;
  private List<treeitem> _children = null;

  public TreeItem(String text, String destination) {
    setText(text);
    setDestination(destination);
  }

  public String getText() { return _text; }
  public void setText(String text) { this._text = text; }

  public String getDestination() { return _destination; }
  public void setDestination(String destination) { this._destination = destination; }

  public List getChildren() { return _children; }
  public void setChildren(List children) { this._children = children; }
}

Note the following about the TreeItem class:
  • Internally stores the Strings text and destination and provides appropriate accessors.
  • Contains a list of child TreeItems _children that may contain 0, 1 or many child TreeItem objects within the current TreeItem. This represents the hierarchy. Note that it is important the Java Bean provides accessors specifically named getChildren() and setChildren() to retrieve the private _children attribute as you'll see in a moment.
Create a TreeModelAdapter class:

package view;

import java.beans.IntrospectionException;
import java.util.ArrayList;
import java.util.List;
import oracle.adf.view.faces.model.ChildPropertyTreeModel;
import oracle.adf.view.faces.model.TreeModel;

public class TreeModelAdapter {
  private Object _instance = null;
  private transient TreeModel _model = null;

  public TreeModelAdapter() {

    ArrayList<TreeItem> rootTreeItems = new ArrayList<TreeItem>();

    TreeItem treeItem1 = new TreeItem("Fish", "http://www.someurl1.com");
    TreeItem treeItem2 = new TreeItem("Dog", "http://www.someurl2.com");
    TreeItem treeItem3 = new TreeItem("Cat", "http://www.someurl3.com");

    TreeItem treeItem2_1 = new TreeItem("Blue Heeler", "http://www.someurl4.com");

    TreeItem treeItem2_1_1 = new TreeItem("Rover", "http://www.someurl5.com");
    TreeItem treeItem2_1_2 = new TreeItem("Ruffus", "http://www.someurl6.com");

    rootTreeItems.add(treeItem1);
    rootTreeItems.add(treeItem2);
    rootTreeItems.add(treeItem3);

    ArrayList<TreeItem> treeItem2Children = new ArrayList<TreeItem>();
    ArrayList<TreeItem> treeItem2_1Children = new ArrayList<TreeItem>();

    treeItem2Children.add(treeItem2_1);
    treeItem2.setChildren(treeItem2Children);

    treeItem2_1Children.add(treeItem2_1_1);
    treeItem2_1Children.add(treeItem2_1_2);
    treeItem2_1.setChildren(treeItem2_1Children);

    this.setListInstance(rootTreeItems);
  }

  public TreeModel getModel() throws IntrospectionException {
    if (_model == null) {
      _model = new ChildPropertyTreeModel(_instance, "children");
    }
    return _model;
  }

  public void setListInstance(List instance) {
    _instance = instance;
    _model = null;
  }
}


Note the following about the TreeModelAdapter class:
  • It contains a private oracle.adf.view.faces.model.TreeModel _model attribute. This is the TreeModel we'll provide to the <af:tree> tag. It is exposed via the getModel() method, and you previously saw this method was called via the <af:tree value="#{treeModel.model"> EL expression.
  • The class also contains an Object named _instance. This is where we'll store our own programmatic tree constructed from our own home baked Java Bean TreeItem hierarchy. The nice thing about this implementation is it doesn't care if it gets our TreeItem class, or any other sort of Java Bean, as long as it follows the Java Bean spec by providing accessors to retrieve the attributes, including specifically accessors for _children (more on this soon). The setListInstance() accessor provides a method to set the Object _instance.
  • In our simple example here, the constructor creates 3 root TreeItem nodes "Fish", "Dog", "Cat". For the "Dog" TreeItem node it creates a child "Blue Heeler" node. For the "Blue Heeler" node it creates 2 TreeItem nodes "Rover" and "Ruffus" in turn. The constructors last action is to assign the TreeItem hierarchy we've populated into rootTreeItems into the TreeModelAdapter class's _instance private attribute.
  • The getModel() method requires further explanation. The ChildPropertyTreeModel class is a subclass of oracle.adf.view.faces.model.TreeModel. It allows the construction of a TreeModel based on a list of beans. This suites our purposes well as we've constructed a list of TreeItem beans stored in the TreeModelAdapter's _instance attribute. You'll note that the ChildPropertyTreeModel constructor for its 2nd parameter specifies the String "children". The ChildPropertyTreeModel class uses this name to work out what accessor methods it needs to use in the TreeItem Java Bean to access the hierarchical part of the TreeItem class. It's important that the string "children" matches the accessor names getChildren() and setChildren() in the TreeItem class following the Java Bean specification rules. If these mismatch you'll get a runtime error.
Finally in our faces-config.xml file we declare our managed bean as follows:

<managed-bean>
  <managed-bean-name>treeModel
  <managed-bean-class>view.TreeModelAdapter
  <managed-bean-scope>request
</managed-bean>

Note that the request scope used for treeModel will result in the instantiation of TreeModelAdapter for each request to the page containing the treeModel reference, potentially an expensive exercise. If the tree is duplicated across multiple screens a session scope may be appropriate.

Credit

To give credit the inspiration for the TreeModelAdapter in this solution is a modification of MenuTreeModelAdapter from Oracle's ADF Developer's Guide for Forms/4GL Developers 10.1.3 (page 19-8). Those with lateral thinking should be able to see the ability to create programmatic <af:page> and <af:panelPage> menus with this technique.

Final Caveat

The code above is not applicable for JDev 11g and its new Rich Faces components as the class structure for the components has changed. Once Oracle releases the JavaDocs for the components I'll look at revising this article on request.

10 comments:

Eric said...

Thank you, this was a huge help. I was trying to create a dynamic page of links without using the tree, and do it in the programatic fashion, but that wasn't working. Your example was extremely easy to follow and customize for my needs. Who knows how many hours(days/weeks?) I might have been working on this if I hadn't found your blog.

Chris Muir said...

No worries Eric, happy Tree coding. Make sure to read the two follow up posts too.

CM.

Tove said...

This looks just like the information I need :-)

... excepting the fact that I use JDev 11g

Any chance of an update?

Chris Muir said...

Hi Tove

Unfortunately no, with the arrival of a new baby daughter I'm currently snowed with domestic duties. Hopefully the OTN JDev forums can assist you.

Regards,

CM.

mekovster said...

I have managed to run it on JDev 11g with some slight corrections:
"............
Create our POJO Java Bean TreeItem class as follows:

package view;

import java.util.List;

public class TreeItem {
private String _text = null;
private String _destination = null;
private List<TreeItem> _children = null;
........................"
(on the last row there is a typo)

Next:
"........................
Create a TreeModelAdapter class:

package view;

import java.beans.IntrospectionException;
import java.util.ArrayList;
import java.util.List;
import org.apache.myfaces.trinidad.model.ChildPropertyTreeModel;
import org.apache.myfaces.trinidad.model.TreeModel;
................................"
(updated last 2 imports using deprecated libraries in JDev 11g)

Chris Muir said...

Great. Thanks very much for taking time out to post your solution, I really do appreciate that. A lot of people land on this page and I haven't had time to revisit my solution. Your post will help a lot of people.

CM.

Bhaskar Kolloju said...

Awesome. Thank you so much for your time. This is of great help.
- Bhaskar

Bhaskar Kolloju said...

Awesome. Thank you so much Chris and mekovster. This is of great help.

Thiru said...

Its was a really useful post. Thanks

-Thiru

Azeem said...

In 11g, we need to add var="node" to the af:tree tag as mentiond below. Otherwise tree labels will be blank