簡體   English   中英

JSF 中的遞歸(c:forEach 與 ui:repeat)

[英]Recursion in JSF (c:forEach vs. ui:repeat)

我正在嘗試通過 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 與 ui:repeat in Facelets )。 常見的問題似乎是混合構建時和渲染時組件/標簽。 就我而言:

  • 我的復合組件其實就是一個標簽,在樹構建的時候執行
  • ui:repeat 是一個實際的 JSF 組件,它在樹被渲染時被評估

子組件navigation:navigationNode真的在ui:repeat組件之前處理? 如果是這樣,它用於#{child}對象是什么? 它是否為空(似乎並非如此)? 這里的問題是實際上創建了子組件而不關心 ui:repeat 等每次創建一個新的子組件時,即使它不一定需要?

Facelets文章中的c:forEach 與 ui:repeat對此(遞歸)有一個單獨的部分。 建議使用c:forEach代替。 我試過這個,但是它仍然給我相同的StackOverflowError ,具有我無法理解的不同跟蹤。

我知道我們也可以通過擴展UIComponent來構建組件,但是這種方法(用 Java 代碼編寫 html)看起來很丑陋。 我寧願使用 MVC 樣式/模板。 但是,如果沒有其他方法,我是否必須將這種遞歸實現為 UIComponent ?

JSF 的內置聲明性標簽不適合處理這種遞歸。 JSF 構建了一個在請求之間持久化的有狀態組件樹。 如果視圖在后續請求中恢復,則視圖狀態可能不會反映模型中的更改。

我贊成一種命令式的方法。 在我看來,您有兩個選擇:

  • 使用binding屬性將控件(例如某種形式的面板)綁定到提供UIComponent實例及其子項的支持 bean - 您編寫代碼來實例化UIComponent並添加您想要的UIComponent項。 請參閱binding屬性協定的規范。
  • 編寫一個自定義控件,實現一些: UIComponent Renderer 標簽處理程序; 元數據文件(根據需要刪除 - 您可以根據您正在做什么以及如何以及在哪個版本的 JSF 中執行其中的部分或全部操作)。

也許另一種選擇是選擇已經執行此操作的第 3 方控件。

更新:如果有人正在使用非常有用的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>

編輯:

這是一種模型驅動的方法,它不涉及編寫自定義組件或支持 bean 生成的組件樹。 有點丑。

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>

托管 bean:

@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 

我仍然會咬緊牙關寫一個自定義的樹控件。

在將我們的應用程序從 jsf 1.x 遷移到 2.x 時,我遇到了類似的問題(StackOverflowException)。 如果您使用 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>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM