Я пытаюсь построить дерево навигации с помощью рекурсии в 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: repeat в Facelets ). Кажется, что общая проблема заключается в смешении компонентов / тегов времени компоновки и времени рендеринга. В моем случае:
Является ли дочерний компонент navigation: navigationNode
фактически обработан до компонента ui: repeat
? Если так, какой объект он использует для # {child}
? Это ноль (не кажется так)? Проблема здесь в том, что дочерний компонент фактически создается, даже не заботясь о пользовательском интерфейсе: repeat, и поэтому каждый раз, когда создается новый дочерний компонент, даже если он не является обязательным?
c: forEach vs ui: повторить в Facelets статья имеет отдельный раздел для этого (рекурсия). Предлагаем вместо этого использовать c: forEach
. Я попробовал это, однако он все еще дает мне тот же StackOverflowError
, с другой трассировкой, которую я не могу понять.
Я знаю, что мы также можем создавать компоненты, расширяя UIComponent
, но такой подход (написание HTML в коде Java) кажется уродливым. Я бы предпочел использовать стиль / шаблоны MVC. Однако, если нет других способов, я должен вместо этого реализовать такую рекурсию как UIComponent?
Встроенные в JSF декларативные теги плохо подходят для обработки такого рода рекурсии. JSF строит дерево компонентов с состоянием, которое сохраняется между запросами. Если представление восстанавливается в последующем запросе, состояние представления может не отражать изменений в модели.
Я бы отдал предпочтение императивному подходу. У вас есть два варианта, как мне кажется:
binding
для привязки элемента управления (например, какой-нибудь панели) к backing bean, который предоставляет экземпляр UIComponent
и его дочерние элементы - вы пишете код для инстанцирования UIComponent
и добавления любых дочерних элементов, которые вы хотите. См. спецификацию контракта атрибута binding
. UIComponent
; Renderer
; обработчик тегов; файлы мета-данных (удалите по мере необходимости - вы делаете некоторые или все из них в зависимости от того, что вы делаете, как и в какой версии JSF). Возможно, другой вариант - взять сторонний контрол, который уже делает это.
UPDATE:
Если вы используете очень полезную библиотеку OmniFaces (если вы ее еще не используете, то вам стоит это сделать), есть
которая не имеет генерации 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:
Вот подход, основанный на модели, который не включает в себя написание пользовательских компонентов или деревьев компонентов, генерируемых бэкингом. Он немного уродлив.
Представление 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
Я бы все же перестраховался и написал собственный элемент управления деревом.