views:

424

answers:

1

I am trying to build a navigation tree via recursion in JSF. I have defined a navigationNode component as:

<composite:interface>
    <composite:attribute name="node" />
</composite:interface>

<composite:implementation>
<ul>
    <ui:repeat value="#{navigationTreeBean.getChildrenForNode(cc.attrs.node)}" var="child">
        <li><navigation:navigationNode node="#{child}" /></li>
    </ui:repeat>
</ul>
</composite:implementation>

My tree is declared as:

rootNode = new DefaultMutableTreeNode(new NodeData("Dashboard", "dashboard.xhtml"), true);
DefaultMutableTreeNode configurationsNode = new DefaultMutableTreeNode(new NodeData("Configurations", "configurations.xhtml"), true);
rootNode.add(configurationsNode);

I call component by:

<nav:navigationNode node="#{rootNode}" />

The problem is, this results in StackOverflowError.

There are a few references to building recursion in JSF (for example, c:forEach vs ui:repeat in Facelets). The common problem seems to be mixing the build-time and render-time components/tags. In my case:

  • My composite component is actually a tag, which is executed when the tree is built
  • ui:repeat is an actual JSF component, which is evaluated when the tree is rendered

Is the child component navigation:navigationNode actually processed before the ui:repeat component? If so, what object is it using for #{child}? Is it null (doesn't seem so)? Is the problem here that the child component is actually created without even caring about the ui:repeat and so each time a new child component is created even though it is not necessarily wanted?

The c:forEach vs ui:repeat in Facelets article has a separate section for this (recursion). The suggestion is to to use c:forEach instead. I tried this, however it is still giving me the same StackOverflowError, with different trace that I cannot make sense of.

I know that we can also build components by extending UIComponent, but that approach (writing html in Java code) seems ugly. I would rather use MVC style / templates. However, if there are no other ways, do I have to implement this sort of recursion as UIComponent instead?

+2  A: 

JSF's declarative tags are ill-suited for handling this sort of recursion. JSF builds a stateful component tree that is persisted between requests. If the view is restored in a subsequent request, the view state may not reflect changes in the model.

I would favour an imperative approach. You have two options as I see it:

  • Use the binding attribute to bind a control (e.g. some form of panel) to a backing bean that provides the UIComponent instance and its children - you write code to instantiate the UIComponent and add whatever children you want. See the spec for the binding attribute contract.
  • Write a custom control, implementing some of: a UIComponent; a Renderer; a tag handler; meta-data files (delete as appropriate - you do some or all of these depending on what you are doing and how and in which version of JSF).

Perhaps another option is to pick up a 3rd party control that already does this.


EDIT:

Here's a model-driven approach that doesn't involve writing custom components or backing-bean-generated component trees. It's kind of ugly.

The Facelets view:

<?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"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ui="http://java.sun.com/jsf/facelets"&gt;
  <h:head><title>Facelet Tree</title></h:head>
  <h:body>
    <ul>
      <ui:repeat value="#{tree.treeNodes}" var="node">
        <h:outputText rendered="#{node.hasParent and node.firstChild}"
                value="&lt;ul&gt;" escape="false" />
        <li>
          <h:outputText value="#{node.value}" />
        </li>
        <h:outputText rendered="#{node.hasParent and node.lastChild}"
                value="&lt;/ul&gt;" escape="false" />
      </ui:repeat>
    </ul>
  </h:body>
</html>

The managed bean:

@ManagedBean(name = "tree")
@RequestScoped
public class Tree {
  private Node<String> root;

  public Tree() {
    root = new Node(null, "JSF Stuff");
    root.getKids().add(new Node(root, "Chapter One"));
    root.getKids().add(new Node(root, "Chapter Two"));
    root.getKids().add(new Node(root, "Chapter Three"));
    Node<String> chapter2 = root.getKids().get(1);
    chapter2.getKids().add(new Node(chapter2, "Section A"));
    chapter2.getKids().add(new Node(chapter2, "Section B"));
  }

  public List<Node<String>> getTreeNodes() {
    return walk( new ArrayList<Node<String>>(), root);
  }

  private List<Node<String>> walk(List<Node<String>> list, Node<String> node) {
    list.add(node);
    for(Node<String> kid : node.getKids()) {
      walk(list, kid);
    }
    return list;
  }
}

A tree node:

public class Node<T> {
  private T value;
  private Node<T> parent;
  private LinkedList<Node<T>> kids = new LinkedList<Node<T>>();

  public Node(Node<T> parent, T value) {
    this.parent = parent;
    this.value = value;
  }

  public List<Node<T>> getKids() {return kids;}
  public T getValue() { return value; }

  public boolean getHasParent() { return parent != null; }

  public boolean isFirstChild() {
    return parent != null && parent.kids.peekFirst() == this;
  }

  public boolean isLastChild() {
    return parent != null && parent.kids.peekLast() == this;
  }
}

Output:

*  JSF Stuff
      o Chapter One
      o Chapter Two
            + Section A
            + Section B 
      o Chapter Three 

I would still bite the bullet and write a custom tree control.

McDowell
Thanks for your suggestions. I have to say I am little disappointed with JSF 2.0 if this is the case. Still looking for other viewpoints, but probably I'll have to follow this road. I've glanced at RichFaces and PrimeFaces, but they seem terribly heavyweight and their styling seems to require more work than I would like (we already have XHTML/CSS templates) so I am still looking to build a custom navigation tree.
Tuukka Mustonen
PrimeFaces supports/uses [jQuery themeroller CSS framework](http://jqueryui.com/themeroller/). The average CSS designer should already be familiar with that.
BalusC
@Tuukka Mustonen - the stateful component tree that underlies the JSF framework makes what you want difficult and it would be a breaking change to remove that.
McDowell
@ McDowell: Thanks for providing example code with your answer. Building on such iterative basis seems hackyish but I also find it interesting :) Never thought of that. I feel this only works when the tree is built in right order. However, that wouldn't be a problem.@ BalusC: Thanks for pointer to the themeroller. I fear it's too simple, however. Each component also has their own CSS, which you don't customize in themeroller. However, I will take another look at that, in my first glance I didn't find how to override the component specific CSS.
Tuukka Mustonen