JSF中的递归(c:forEach vs. ui:repeat) [英] Recursion in JSF (c:forEach vs. ui:repeat)
问题描述
我正在尝试通过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:在Facelets中重复)。常见问题似乎是混合构建时和渲染时组件/标签。在我的情况下:
- 我的复合组件实际上是一个标签,在树构建时执行
- ui:repeat是一个实际的JSF组件,在呈现树时进行评估
子组件 navigation:navigationNode
实际上是在 ui:repeat
组件之前处理的?如果是这样,它用于#{child}
的对象是什么?它是否为null(似乎不是这样)?这里的问题是,实际创建子组件时甚至没有关心ui:repeat,所以每次创建一个新的子组件时,即使它不一定是想要的吗?
c:forEach vs ui:repeat在Facelets 文章中有一个单独的部分(递归)。建议是使用 c:forEach
。我试过这个,但它仍然给了我相同的 StackOverflowError
,带有我无法理解的不同痕迹。
<我知道我们也可以通过扩展
UIComponent
来构建组件,但是这种方法(在Java代码中编写html)似乎很难看。我宁愿使用MVC样式/模板。但是,如果没有其他方法,我是否必须将这种递归实现为UIComponent?JSF的声明性标签不适合处理这种递归。 JSF构建一个在请求之间保持的有状态组件树。如果视图在后续请求中恢复,则视图状态可能不会反映模型中的更改。
我赞成采用强制方法。我有两种选择:
- 使用
绑定
属性进行绑定一个控件(例如某种形式的面板)到一个提供UIComponent
实例及其子节点的支持bean - 你编写代码来实例化UIComponent
并添加你想要的任何孩子。请参阅绑定
属性合同的规范。 - 编写自定义控件,实现以下部分:
UIComponent
; a渲染器
;标签处理程序;元数据文件(根据需要删除 - 你可以根据你在做什么以及如何以及在哪个版本的JSF中执行部分或全部操作)。
也许另一种选择是选择已经执行此操作的第三方控件。
编辑:
这是一种模型驱动的方法,不涉及编写自定义组件或后端bean生成的组件树。这有点难看。
Facelets视图:
< ?xml version ='1.0'coding ='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);
}
返回清单;
}
}
树节点:
公共类节点< T> {
私人T值;
私有节点< T>父母;
private LinkedList< Node< T>> kids = new LinkedList<>();
公共节点(节点< T> parent,T值){
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;
列表<节点> lineage = new ArrayList<>();
while(node.isLastChild()){
lineage.add(node);
node = node.parent;
}
返回血统;
}
}
输出:
* JSF东西
o第一章
o第二章
+ A节
+ B节
o第三章
我仍然会咬紧牙关并编写自定义树控件。
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?
JSF's 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 theUIComponent
instance and its children - you write code to instantiate theUIComponent
and add whatever children you want. See the spec for thebinding
attribute contract. - Write a custom control, implementing some of: a
UIComponent
; aRenderer
; 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.
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.
这篇关于JSF中的递归(c:forEach vs. ui:repeat)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!