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

我试图通过JSF中的递归来构建导航树。 我已将navigationNode组件定义为:

     

我的树被宣布为:

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

我叫组件:

 

问题是,这会导致StackOverflowError

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

  • 我的复合组件实际上是一个标签,它在构建树时执行
  • ui:repeat是一个实际的JSF组件,在呈现树时进行评​​估

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

c:forEach vs ui:在Facelets中重复文章有一个单独的部分(递归)。 建议是使用c:forEach 。 我试过这个,但它仍然给我相同的StackOverflowError ,具有我无法理解的不同跟踪。

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

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

我赞成采取强制措施。 我看到你有两个选择:

  • 使用binding属性将控件(例如某种forms的面板)绑定到提供UIComponent实例及其子节点的辅助bean – 您编写代码来实例化UIComponent并添加您想要的UIComponent节点。 请参阅binding属性合同的规范。
  • 编写一个自定义控件,实现一些: UIComponent ; 一个Renderer ; 标签处理程序; 元数据文件(根据需要删除 – 您可以根据您正在做什么以及如何以及在哪个版本的JSF中执行部分或全部操作)。

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


编辑:

这是一种模型驱动的方法,不涉及编写自定义组件或后端bean生成的组件树。 这有点难看。

Facelets视图:

    Facelet Tree  

托管bean:

 @javax.faces.bean.ManagedBean(name = "tree") @javax.faces.bean.RequestScoped public class Tree { private Node 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 chapter2 = root.getKids().get(1); chapter2.getKids().add(new Node(chapter2, "Section A")); chapter2.getKids().add(new Node(chapter2, "Section B")); } public List> getTreeNodes() { return walk(new ArrayList>(), root); } private List> walk(List> list, Node node) { list.add(node); for(Node kid : node.getKids()) { walk(list, kid); } return list; } } 

树节点:

 public class Node { private T value; private Node parent; private LinkedList> kids = new LinkedList<>(); public Node(Node parent, T value) { this.parent = parent; this.value = value; } public List> 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 getLastChildLineage() { Node node = this; List 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 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

    

recursive.xhtml

  
  • #{child.label}