Рекурсия в JSF (c: forEach vs. ui: repeat) - PullRequest
8 голосов
/ 16 августа 2010

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

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

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

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

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

Ответы [ 2 ]

5 голосов
/ 17 августа 2010

Встроенные декларативные теги JSF плохо подходят для обработки рекурсии такого рода.JSF создает дерево компонентов с сохранением состояния, которое сохраняется между запросами.Если представление восстанавливается в последующем запросе, состояние представления может не отражать изменения в модели.

Я бы предпочел императивный подход.У меня есть два варианта, как я вижу:

  • Используйте атрибут binding, чтобы связать элемент управления (например, некоторую форму панели) с базовым компонентом, который предоставляет экземпляр UIComponent и его дочерние элементы -Вы пишете код для создания экземпляра UIComponent и добавляете детей, которых хотите.См. Спецификацию для binding контракта атрибута.
  • Напишите пользовательский элемент управления, реализуя некоторые из: a UIComponentRenderer;обработчик тега;Файлы метаданных (удаляйте по необходимости - вы делаете некоторые или все из них в зависимости от того, что вы делаете, как и в какой версии JSF).

Возможно, другой вариант - выбратьдо стороннего элемента управления, который уже делает это.

ОБНОВЛЕНИЕ: Если кто-то использует очень полезную библиотеку OmniFaces (вам следует, если вы этого не сделаетеуже), есть <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>

РЕДАКТИРОВАТЬ:

Вот подход, основанный на модели, который не включает в себя написание пользовательских компонентов или деревьев компонентов, созданных на основе бина.Это некрасиво.

Представление 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 

Я бы все равно прикусил пулю и написал пользовательский древовидный элемент управления.

4 голосов
/ 07 марта 2013

У меня была похожая проблема (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"

Вот шаблон, который мы используем, адаптированный к вашему сценарию.

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>
...