Recursion in JSF (c:forEach vs. ui:repeat)

前端 未结 2 1239
眼角桃花
眼角桃花 2020-12-06 12:05

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


    

        
相关标签:
2条回答
  • 2020-12-06 12:35

    I had a similar issue(StackOverflowException) while migrating our app from jsf 1.x to 2.x. If you're using the c:forEach approach to jsf recursion, make sure you're using the new namespace for jstl core. Use

    xmlns:c="http://java.sun.com/jsp/jstl/core"
    

    instead of

    xmlns:c="http://java.sun.com/jstl/core"
    

    Here's the pattern we we're using, adapted to your scenario.

    client.xhtml

    <ui:include src="recursive.xhtml">
        <ui:param name="node" value="#{child}" />
    </ui:include>
    

    recursive.xhtml

    <ui:composition xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:c="http://java.sun.com/jsp/jstl/core" >
        <ul>
            <c:forEach items="#{node.children}" var="child">
                <li>
                    #{child.label}
                    <ui:include src="recursive.xhtml">
                        <ui:param name="node" value="#{child}" />
                    </ui:include>
                </li>
            </c:forEach>
        </ul>   
    </ui:composition>
    
    0 讨论(0)
  • 2020-12-06 12:54

    JSF's built-in 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.

    UPDATE: If one is using the very useful OmniFaces library (you should if you don't already), there is the <o:tree> which has no html generation whatsoever but was specifically designed to support usecases like this.

    <o:tree value="#{bean.treeModel}" var="item" varNode="node">
        <o:treeNode>
            <ul>
                <o:treeNodeItem>
                    <li>
                        #{node.index} #{item.someProperty}
                        <o:treeInsertChildren />
                    </li>
                </o:treeNodeItem>
            </ul>
        </o:treeNode>
    </o:tree>
    

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

    The managed bean:

    @javax.faces.bean.ManagedBean(name = "tree")
    @javax.faces.bean.RequestScoped
    public class Tree {
      private Node<String> root = new Node(null, "JSF Stuff");
    
      @PostConstruct
      public void initData() {
        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<>();
    
      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;
      }
    
      public List<Node> getLastChildLineage() {
        Node node = this;
        List<Node> lineage = new ArrayList<>();
        while(node.isLastChild()) {
            lineage.add(node);
            node = node.parent;
        }
        return lineage;
      }
    }
    

    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.

    0 讨论(0)
提交回复
热议问题