Рекурсия в JSF (c:forEach против ui: repeat)
Я пытаюсь построить дерево навигации с помощью рекурсии в JSF. Я определил navigationNode
компонент 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>
мое дерево объявляется как:
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 (не кажется так)? Проблема здесь в том, что дочерний компонент на самом деле создан, даже не заботясь о пользовательском интерфейсе: повторять и так каждый раз, когда создается новый дочерний компонент, даже если он не обязательно нужен?
на c: forEach vs ui: повторить в Facelets статье есть отдельный раздел для этого (рекурсия). Предлагается использовать . Я пробовал это, однако он все еще дает мне то же самое StackOverflowError
, С другим следом, который я не могу понять.
Я знаю, что мы можем построить компоненты путем расширения UIComponent
, но этот подход (написание html в Java-коде) кажется уродливым. Я бы предпочел использовать стиль / шаблоны MVC. Однако, если нет других способов, должен ли я реализовать этот вид рекурсии как UIComponent вместо этого?
2 ответов
декларативные теги JSF плохо подходят для обработки такого рода рекурсии. JSF создает дерево компонентов с сохранением состояния, которое сохраняется между запросами. Если представление восстанавливается в последующем запросе, состояние представления может не отражать изменения в модели.
Я бы предпочел императивный подход. У вас есть два варианта как мне кажется:
- использовать
binding
атрибут для привязки элемента управления (например, некоторая форма панели) к вспомогательному компоненту, который предоставляетUIComponent
экземпляр и его дочерние элементы-вы пишете код для создания экземпляраUIComponent
и добавьте любых детей, которых вы хотите. См. спецификацию дляbinding
контракт атрибута. - напишите пользовательский элемент управления, реализующий некоторые из: A
UIComponent
; aRenderer
; обработчик тегов; файлы метаданных (удалить по мере необходимости-вы делаете некоторые или все из них в зависимости от того, что вы делаете, как и в какой версии JSF).
возможно, другой вариант-подобрать сторонний элемент управления, который уже делает это.
EDIT:
вот управляемый моделью подход, который не включает в себя написание пользовательских компонентов или деревьев компонентов, созданных с помощью резервного компонента. Это довольно уродливо.
вид 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>
управляемого компонента:
@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
я бы все равно укусил пулю и написал пользовательское дерево управление.
у меня была аналогичная проблема (StackOverflowException) при переносе нашего приложения из jsf 1.x-2.X. Если вы используете подход c:forEach к рекурсии jsf, убедитесь, что вы используете новое пространство имен для ядра jstl. Использовать
xmlns:c="http://java.sun.com/jsp/jstl/core"
вместо
xmlns:c="http://java.sun.com/jstl/core"
вот шаблон, который мы используем, адаптированный к вашему сценарию.
клиент.в 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>