JSF中的递归(c:forEach vs. ui:repeat)

Tuu*_*nen 8 java recursion jsf components jsf-2

我试图通过JSF中的递归来构建导航树.我已将navigationNode组件定义为:

<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>
Run Code Online (Sandbox Code Playgroud)

我的树被宣布为:

rootNode = new DefaultMutableTreeNode(new NodeData("Dashboard", "dashboard.xhtml"), true);
DefaultMutableTreeNode configurationsNode = new DefaultMutableTreeNode(new NodeData("Configurations", "configurations.xhtml"), true);
rootNode.add(configurationsNode);
Run Code Online (Sandbox Code Playgroud)

我叫组件:

<nav:navigationNode node="#{rootNode}" />
Run Code Online (Sandbox Code Playgroud)

问题是,这导致了StackOverflowError.

在JSF中有一些关于构建递归的引用(例如,c:forEach vs ui:在Facelets中重复).常见的问题似乎是混合构建时和渲染时组件/标签.就我而言:

  • 我的复合组件实际上是一个标签,它在构建树时执行
  • ui:repeat是一个实际的JSF组件,在呈现树时进行评​​估

子组件是否在组件navigation:navigationNode之前实际处理ui:repeat?如果是这样,它用于什么对象#{child}?它是否为null(似乎不是这样)?这里的问题是,实际创建子组件时甚至没有关心ui:repeat,所以每次创建一个新的子组件时,即使它不一定需要吗?

该C:VS的forEach UI:重复的Facelets制品具有这种(递归)一个单独的部分.建议是改为使用c:forEach.我尝试了这个,但它仍然给了我相同的StackOverflowError,有不同的痕迹,我无法理解.

我知道我们也可以通过扩展来构建组件UIComponent,但是这种方法(在Java代码中编写html)看起来很难看.我宁愿使用MVC样式/模板.但是,如果没有其他方法,我是否必须将此类递归实现为UIComponent?

McD*_*ell 5

JSF的内置声明性标记不适合处理这种递归.JSF构建一个在请求之间持久存在的有状态组件树.如果在后续请求中恢复视图,则视图状态可能不会反映模型中的更改.

我赞成采取强制措施.我看到你有两个选择:

  • 使用该binding属性将控件(例如某种形式的面板)绑定到提供UIComponent实例及其子项的辅助bean - 您编写代码来实例化UIComponent并添加您想要的任何子项.请参阅binding属性合同的规范.
  • 编写自定义控件,实现一些:a UIComponent; 一个Renderer; 标签处理程序; 元数据文件(根据需要删除 - 您可以根据您正在做什么以及如何以及在哪个版本的JSF中执行部分或全部操作).

也许另一个选择是选择已经执行此操作的第三方控件.

更新: 如果一个人正在使用非常有用的OmniFaces库(如果你还没有,你就应该这样),那么<o:tree>它没有任何html生成,但专门设计用于支持这样的用例.

<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>
Run Code Online (Sandbox Code Playgroud)

编辑:

这是一种模型驱动的方法,不涉及编写自定义组件或后端bean生成的组件树.这有点难看.

Facelets视图:

<?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>
Run Code Online (Sandbox Code Playgroud)

托管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;
  }
}
Run Code Online (Sandbox Code Playgroud)

树节点:

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

输出:

*  JSF Stuff
      o Chapter One
      o Chapter Two
            + Section A
            + Section B 
      o Chapter Three 
Run Code Online (Sandbox Code Playgroud)

我仍然会咬紧牙关并编写自定义树控件.