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

发布于 2024-09-14 04:59:49 字数 1786 浏览 4 评论 0原文

我正在尝试通过 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 与ui:在 Facelets 中重复)。常见的问题似乎是混合构建时和渲染时组件/标签。就我而言:

  • 我的复合组件实际上是一个标签,在构建树时执行
  • ui:repeat 是一个实际的 JSF 组件,在渲染树时对其求值

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

Facelets 中的 c:forEach 与 ui:repeat< /em> 文章对此有一个单独的部分(递归)。建议改用c:forEach。我尝试了这个,但是它仍然给我相同的 StackOverflowError ,并且带有我无法理解的不同跟踪。

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

玩世 2024-09-21 04:59:49

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

我赞成采取命令式的方法。在我看来,您有两个选择:

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

也许另一种选择是选择已经执行此操作的第 3 方控件。

更新:
如果您正在使用非常有用的 OmniFaces 库(如果您还没有使用的话,您应该使用),这里有没有任何 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="<ul>" 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="</ul>" 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'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="<ul>" 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="</ul>" 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.

苦妄 2024-09-21 04:59:49

在将我们的应用程序从 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>

递归.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>

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>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文