我正在尝试通过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)。常见的问题似乎是混合构建时和渲染时组件/标签。就我而言:
是否在navigation:navigationNode
组件之前实际处理了子组件ui:repeat
?如果是,那么#{child}
使用了什么对象?它是否为null(似乎不是这样)?这里的问题是,实际创建子组件时甚至没有关心ui:repeat,所以每次创建一个新的子组件时,即使它不一定是想要的吗?
c:forEach vs ui:repeat in Facelets 文章有一个单独的部分(递归)。建议改为使用c:forEach
。我尝试了这个,但它仍然给了我相同的StackOverflowError
,有不同的痕迹,我无法理解。
我知道我们也可以通过扩展UIComponent
来构建组件,但是这种方法(在Java代码中编写html)看起来很难看。我宁愿使用MVC样式/模板。但是,如果没有其他方法,我是否必须将此类递归实现为UIComponent?
答案 0 :(得分:5)
JSF的内置声明性标记不适合处理这种递归。 JSF构建一个在请求之间持久存在的有状态组件树。如果在后续请求中恢复视图,则视图状态可能不会反映模型中的更改。
我赞成采取强制措施。我看到你有两个选择:
binding
属性将控件(例如某种形式的面板)绑定到提供UIComponent
实例及其子节点的辅助bean - 您编写代码来实例化UIComponent
并添加你想要的任何孩子。请参阅binding
属性合同的规范。UIComponent
;一个Renderer
;标签处理程序;元数据文件(根据需要删除 - 您可以根据您正在做的以及如何以及在哪个版本的JSF中执行部分或全部操作)。也许另一个选择是选择已经执行此操作的第三方控件。
<强>更新强>
如果一个人正在使用非常有用的OmniFaces library(如果你还没有使用的话),那么<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>
编辑:
这是一种模型驱动的方法,不涉及编写自定义组件或后端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
我仍然会咬紧牙关并编写自定义树控件。
答案 1 :(得分:4)
在将我们的应用程序从jsf 1.x迁移到2.x时,我遇到了类似的问题(StackOverflowException)。如果您正在使用c:forEach方法来进行jsf递归,请确保使用jstl core的新命名空间。 使用
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>