[英]Recursion in JSF (c:forEach vs. ui:repeat)
I am trying to build a navigation tree via recursion in JSF.我正在尝试通过 JSF 中的递归构建导航树。 I have defined a
navigationNode
component as:我已经定义了一个
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>
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
.问题是,这会导致
StackOverflowError
。
There are a few references to building recursion in JSF (for example, c:forEach vs ui:repeat in Facelets ).有一些关于在 JSF 中构建递归的参考资料(例如, c:forEach 与 ui:repeat in Facelets )。 The common problem seems to be mixing the build-time and render-time components/tags.
常见的问题似乎是混合构建时和渲染时组件/标签。 In my case:
就我而言:
Is the child component navigation:navigationNode
actually processed before the ui:repeat
component?子组件
navigation:navigationNode
真的在ui:repeat
组件之前处理? If so, what object is it using for #{child}
?如果是这样,它用于
#{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?
这里的问题是实际上创建了子组件而不关心 ui:repeat 等每次创建一个新的子组件时,即使它不一定需要?
The c:forEach vs ui:repeat in Facelets article has a separate section for this (recursion). Facelets文章中的c:forEach 与 ui:repeat对此(递归)有一个单独的部分。 The suggestion is to to use
c:forEach
instead.建议使用
c:forEach
代替。 I tried this, however it is still giving me the same StackOverflowError
, with different trace that I cannot make sense of.我试过这个,但是它仍然给我相同的
StackOverflowError
,具有我无法理解的不同跟踪。
I know that we can also build components by extending UIComponent
, but that approach (writing html in Java code) seems ugly.我知道我们也可以通过扩展
UIComponent
来构建组件,但是这种方法(用 Java 代码编写 html)看起来很丑陋。 I would rather use MVC style / templates.我宁愿使用 MVC 样式/模板。 However, if there are no other ways, do I have to implement this sort of recursion as UIComponent instead?
但是,如果没有其他方法,我是否必须将这种递归实现为 UIComponent ?
JSF's built-in declarative tags are ill-suited for handling this sort of recursion. JSF 的内置声明性标签不适合处理这种递归。 JSF builds a stateful component tree that is persisted between requests.
JSF 构建了一个在请求之间持久化的有状态组件树。 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:
在我看来,您有两个选择:
binding
attribute to bind a control (eg 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.binding
属性将控件(例如某种形式的面板)绑定到提供UIComponent
实例及其子项的支持 bean - 您编写代码来实例化UIComponent
并添加您想要的UIComponent
项。 See the spec for the binding
attribute contract.binding
属性协定的规范。UIComponent
;UIComponent
; a Renderer
; Renderer
; a tag handler; Perhaps another option is to pick up a 3rd party control that already does this.也许另一种选择是选择已经执行此操作的第 3 方控件。
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.更新:如果有人正在使用非常有用的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>
EDIT:编辑:
Here's a model-driven approach that doesn't involve writing custom components or backing-bean-generated component trees.这是一种模型驱动的方法,它不涉及编写自定义组件或支持 bean 生成的组件树。 It's kind of ugly.
有点丑。
The Facelets view: 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>
The managed bean:托管 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.我仍然会咬紧牙关写一个自定义的树控件。
I had a similar issue(StackOverflowException) while migrating our app from jsf 1.x to 2.x.在将我们的应用程序从 jsf 1.x 迁移到 2.x 时,我遇到了类似的问题(StackOverflowException)。 If you're using the c:forEach approach to jsf recursion, make sure you're using the new namespace for jstl core.
如果您使用 c:forEach 方法进行 jsf 递归,请确保您使用的是新的 jstl 核心命名空间。 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客户端.xhtml
<ui:include src="recursive.xhtml">
<ui:param name="node" value="#{child}" />
</ui:include>
recursive.xhtml递归.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>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.