Рекурсия в JSF (c: forEach vs. ui: повторить)

Я пытаюсь построить дерево навигации с помощью рекурсии в 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 ). Кажется, что общая проблема заключается в смешении компонентов / тегов времени компоновки и времени рендеринга. В моем случае:

  • Мой составной компонент на самом деле является тегом, который выполняется при построении дерева.
  • ui: repeat является фактическим компонентом JSF, который оценивается при визуализации дерева.

Является ли дочерний компонент navigation: navigationNode фактически обработан до компонента ui: repeat ? Если так, какой объект он использует для # {child} ? Это ноль (не кажется так)? Проблема здесь в том, что дочерний компонент фактически создается, даже не заботясь о пользовательском интерфейсе: repeat, и поэтому каждый раз, когда создается новый дочерний компонент, даже если он не является обязательным?

c: forEach vs ui: повторить в Facelets статья имеет отдельный раздел для этого (рекурсия). Предлагаем вместо этого использовать c: forEach . Я попробовал это, однако он все еще дает мне тот же StackOverflowError , с другой трассировкой, которую я не могу понять.

Я знаю, что мы также можем создавать компоненты, расширяя UIComponent , но такой подход (написание HTML в коде Java) кажется уродливым. Я бы предпочел использовать стиль / шаблоны MVC. Однако, если нет других способов, я должен вместо этого реализовать такую ​​рекурсию как UIComponent?

8
задан BalusC 17 August 2010 в 14:24
поделиться

1 ответ

Встроенные в 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="&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>

Управляемый боб:

@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 

Я бы все же перестраховался и написал собственный элемент управления деревом.

5
ответ дан 5 December 2019 в 18:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: