【问题标题】:Recursion in JSF (c:forEach vs. ui:repeat)JSF 中的递归(c:forEach 与 ui:repeat)
【发布时间】:2011-03-30 10:25:22
【问题描述】:

我正在尝试通过 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>

我的树被声明为:

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

我通过以下方式调用组件:

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

问题是,这会导致StackOverflowError

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

  • 我的复合组件其实是一个标签,在建树的时候执行
  • ui:repeat 是一个实际的 JSF 组件,在渲染树时对其进行评估

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

c:forEach vs ui:repeat in Facelets 文章对此有一个单独的部分(递归)。建议改用c:forEach。我试过这个,但它仍然给我相同的StackOverflowError,但我无法理解不同的痕迹。

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

【问题讨论】:

    标签: java jsf recursion components jsf-2


    【解决方案1】:

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

    我倾向于采用命令式方法。在我看来,您有两个选择:

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

    也许另一种选择是选择已经这样做的第 3 方控件。

    更新: 如果有人正在使用非常有用的OmniFaces library(如果你还没有的话,你应该这样做),那么&lt;o:tree&gt; 没有任何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>
    

    编辑:

    这是一种模型驱动的方法,不涉及编写自定义组件或支持 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>
    

    托管 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;
      }
    }
    

    一个树节点:

    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;
      }
    }
    

    输出:

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

    我还是会硬着头皮写一个自定义的树形控件。

    【讨论】:

    • 感谢您的建议。如果是这样的话,我不得不说我对 JSF 2.0 有点失望。仍在寻找其他观点,但可能我将不得不沿着这条路走。我看过 RichFaces 和 PrimeFaces,但它们看起来非常重量级,而且它们的样式似乎需要比我想要的更多的工作(我们已经有 XHTML/CSS 模板),所以我仍在寻找构建自定义导航树。
    • PrimeFaces 支持/使用jQuery themeroller CSS framework。一般的 CSS 设计师应该已经很熟悉了。
    • @Tuukka Mustonen - 作为 JSF 框架基础的有状态组件树使您想要的东西变得困难,删除它将是一个重大更改。
    • @McDowell:感谢您提供示例代码和答案。建立在这样的迭代基础上似乎很老套,但我也觉得它很有趣:) 从来没有想过。我觉得这只有在树以正确的顺序构建时才有效。但是,这不会是一个问题。 @BalusC:感谢您指向themeroller。然而,我担心它太简单了。每个组件也有自己的 CSS,你没有在 themeroller 中自定义。但是,我会再看一下,乍一看,我没有找到如何覆盖组件特定的 CSS。
    • 用每个节点只有一个子节点的树来尝试你的算法。这三个将在同一级别上渲染所有内容。
    【解决方案2】:

    在将我们的应用程序从 jsf 1.x 迁移到 2.x 时,我遇到了类似的问题 (StackOverflowException)。如果您使用 c:forEach 方法进行 jsf 递归,请确保您使用的是 jstl 核心的新命名空间。 使用

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

    而不是

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

    这是我们使用的模式,适合您的场景。

    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>
    

    【讨论】:

    • 只是想澄清一下,在我们的例子中,递归是使用嵌套的 ui:include/c:forEach 实现的。
    • 问题确实提到了使用 c:forEach 的 StackoverflowException,这是我使用无效命名空间得到的。我更新了答案以包含递归的完整代码。
    • 这至少似乎是递归,而不是其他解决方案,它似乎只是构建一个巨大的节点列表,带有一些标志来指示节点是否是叶子。我认为其他解决方案不会像递归那样有深度。
    猜你喜欢
    • 2014-02-04
    • 1970-01-01
    • 1970-01-01
    • 2013-06-29
    • 2011-04-02
    • 1970-01-01
    • 2012-11-14
    • 1970-01-01
    • 2013-03-19
    相关资源
    最近更新 更多