JSTL in JSF2-Facelets… macht Sinn?


163

Ich möchte ein bisschen Facelets-Code bedingt ausgeben.

Zu diesem Zweck scheinen die JSTL-Tags einwandfrei zu funktionieren:

<c:if test="${lpc.verbose}">
    ...
</c:if>

Ich bin mir jedoch nicht sicher, ob dies eine bewährte Methode ist. Gibt es einen anderen Weg, um mein Ziel zu erreichen?

Antworten:


320

Einführung

JSTL- <c:xxx>Tags sind alle Taghandler und werden während der Erstellungszeit der Ansicht ausgeführt , während JSF- <h:xxx>Tags alle UI-Komponenten sind und während der Renderzeit der Ansicht ausgeführt werden .

Beachten Sie, dass von JSF eigenen <f:xxx>und <ui:xxx>Tags nur diejenigen , die ihnen nicht aus erstrecken UIComponentauch taghandlers, zum Beispiel <f:validator>, <ui:include>, <ui:define>usw. Diejenigen , die aus erstrecken UIComponentauch JSF UI - Komponenten, zum Beispiel <f:param>, <ui:fragment>, <ui:repeat>usw. Von JSF UI - Komponenten nur die idund bindingAttribute sind Wird auch während der Erstellungszeit der Ansicht ausgewertet. Daher gilt die folgende Antwort zum JSTL-Lebenszyklus auch für die Attribute idund bindingvon JSF-Komponenten.

Die Ansicht Kompilierzeit ist , dass , wenn das Moment XHTML / JSP - Datei analysiert werden soll , und zu einer JSF Komponentenstruktur umgewandelt , die dann als gespeichert wird UIViewRootvon den FacesContext. Die Renderzeit für die Ansicht ist der Moment, in dem der JSF-Komponentenbaum HTML generiert, beginnend mit UIViewRoot#encodeAll(). Also: JSF-UI-Komponenten und JSTL-Tags werden nicht synchron ausgeführt, wie Sie es von der Codierung erwarten würden. Sie können es wie folgt visualisieren: JSTL wird zuerst von oben nach unten ausgeführt, wobei der JSF-Komponentenbaum erstellt wird. Anschließend wird JSF erneut von oben nach unten ausgeführt und die HTML-Ausgabe erstellt.

<c:forEach> vs. <ui:repeat>

Beispiel: Dieses Facelets-Markup iteriert über 3 Elemente mit <c:forEach>:

<c:forEach items="#{bean.items}" var="item">
    <h:outputText id="item_#{item.id}" value="#{item.value}" />
</c:forEach>

... erstellt während der Erstellungszeit der Ansicht drei separate <h:outputText>Komponenten im JSF-Komponentenbaum, die ungefähr so ​​dargestellt werden:

<h:outputText id="item_1" value="#{bean.items[0].value}" />
<h:outputText id="item_2" value="#{bean.items[1].value}" />
<h:outputText id="item_3" value="#{bean.items[2].value}" />

... die wiederum ihre HTML-Ausgabe während der Renderzeit der Ansicht individuell generieren:

<span id="item_1">value1</span>
<span id="item_2">value2</span>
<span id="item_3">value3</span>

Beachten Sie, dass Sie die Eindeutigkeit der Komponenten-IDs manuell sicherstellen müssen und dass diese auch während der Erstellungszeit der Ansicht ausgewertet werden.

Während dieses Facelets-Markup über 3 Elemente iteriert <ui:repeat>, ist dies eine JSF-UI-Komponente:

<ui:repeat id="items" value="#{bean.items}" var="item">
    <h:outputText id="item" value="#{item.value}" />
</ui:repeat>

... landet bereits unverändert im JSF-Komponentenbaum, wobei dieselbe <h:outputText>Komponente während der Renderzeit der Ansicht wiederverwendet wird , um eine HTML-Ausgabe basierend auf der aktuellen Iterationsrunde zu generieren:

<span id="items:0:item">value1</span>
<span id="items:1:item">value2</span>
<span id="items:2:item">value3</span>

Beachten Sie, dass <ui:repeat>als NamingContainerKomponente bereits die Eindeutigkeit der Client-ID basierend auf dem Iterationsindex sichergestellt wurde. Es ist auch nicht möglich, EL als idAttribut für untergeordnete Komponenten auf diese Weise zu verwenden, da es auch während der Erstellungszeit der Ansicht ausgewertet wird, während #{item}es nur während der Renderzeit der Ansicht verfügbar ist. Gleiches gilt für eine h:dataTableund ähnliche Komponenten.

<c:if>/ <c:choose>vs.rendered

Als weiteres Beispiel fügt dieses Facelets-Markup unter bestimmten Bedingungen verschiedene Tags hinzu <c:if>(Sie können dies auch verwenden <c:choose><c:when><c:otherwise>):

<c:if test="#{field.type eq 'TEXT'}">
    <h:inputText ... />
</c:if>
<c:if test="#{field.type eq 'PASSWORD'}">
    <h:inputSecret ... />
</c:if>
<c:if test="#{field.type eq 'SELECTONE'}">
    <h:selectOneMenu ... />
</c:if>

... type = TEXTfügt nur die <h:inputText>Komponente zum JSF-Komponentenbaum hinzu:

<h:inputText ... />

Während dieses Facelets-Markup:

<h:inputText ... rendered="#{field.type eq 'TEXT'}" />
<h:inputSecret ... rendered="#{field.type eq 'PASSWORD'}" />
<h:selectOneMenu ... rendered="#{field.type eq 'SELECTONE'}" />

... wird unabhängig von der Bedingung genau wie oben im JSF-Komponentenbaum angezeigt. Dies kann daher zu einem "aufgeblähten" Komponentenbaum führen, wenn Sie viele davon haben und diese tatsächlich auf einem "statischen" Modell basieren (dh das fieldändert sich zumindest während des Ansichtsbereichs nie). Außerdem können EL- Probleme auftreten, wenn Sie sich mit Unterklassen mit zusätzlichen Eigenschaften in Mojarra-Versionen vor 2.2.7 befassen.

<c:set> vs. <ui:param>

Sie sind nicht austauschbar. Die <c:set>Sätze eine Variable in dem EL - Umfang, der nur zugänglich ist , nach dem Tag Lage während Ansicht Kompilierzeit, sondern überall in der Ansicht während Ansicht Zeit machen. Die <ui:param>übergibt ein EL - Variable auf eine Facelet Vorlage über enthalten <ui:include>, <ui:decorate template>oder <ui:composition template>. Ältere JSF-Versionen hatten Fehler, bei denen die <ui:param>Variable auch außerhalb der fraglichen Facelet-Vorlage verfügbar ist. Darauf sollte man sich niemals verlassen.

Das <c:set>ohne scopeAttribut verhält sich wie ein Alias. Das Ergebnis des EL-Ausdrucks wird in keinem Bereich zwischengespeichert. Es kann daher perfekt im Inneren verwendet werden, um beispielsweise JSF-Komponenten zu iterieren. So wird zB unten gut funktionieren:

<ui:repeat value="#{bean.products}" var="product">
    <c:set var="price" value="#{product.price}" />
    <h:outputText value="#{price}" />
</ui:repeat>

Es ist nur nicht geeignet, um beispielsweise die Summe in einer Schleife zu berechnen. Verwenden Sie dazu stattdessen den EL 3.0-Stream :

<ui:repeat value="#{bean.products}" var="product">
    ...
</ui:repeat>
<p>Total price: #{bean.products.stream().map(product->product.price).sum()}</p>

Nur, wenn Sie das Set - scopeAttribut mit einem der zulässigen Werten request, view, session, oder application, dann wird es sofort in dem angegebenen Bereich während der Ansichtserstellungszeit und gespeichert ausgewertet werden.

<c:set var="dev" value="#{facesContext.application.projectStage eq 'Development'}" scope="application" />

Dies wird nur einmal ausgewertet und ist für #{dev}die gesamte Anwendung verfügbar .

Verwenden Sie JSTL, um die Erstellung von JSF-Komponentenbäumen zu steuern

JSTL verwenden , kann nur zu unerwarteten Ergebnissen führen , wenn sie innerhalb JSF Iterieren Komponenten wie verwendet werden <h:dataTable>, <ui:repeat>usw., oder wenn JSTL - Tag Attribute sind abhängig von Ergebnissen der JSF Ereignisse wie preRenderViewoder übermittelten Formularwerte in dem Modell , die während der Ansicht Kompilierzeit nicht verfügbar sind . Verwenden Sie JSTL-Tags daher nur, um den Ablauf der JSF-Komponentenbaumerstellung zu steuern. Verwenden Sie JSF-UI-Komponenten, um den Fluss der HTML-Ausgabegenerierung zu steuern. Binden Sie die variterierenden JSF-Komponenten nicht an JSTL-Tag-Attribute. Verlassen Sie sich nicht auf JSF-Ereignisse in JSTL-Tag-Attributen.

Immer wenn Sie der Meinung sind, dass Sie eine Komponente über an die Backing Bean binden bindingoder eine über greifen findComponent()und ihre untergeordneten Elemente mithilfe von Java-Code in einer Backing Bean erstellen und bearbeiten müssen new SomeComponent()und was nicht, sollten Sie sofort aufhören und stattdessen die Verwendung von JSTL in Betracht ziehen. Da JSTL auch XML-basiert ist, wird der Code, der zum dynamischen Erstellen von JSF-Komponenten benötigt wird, viel besser lesbar und wartbar.

Es ist wichtig zu wissen, dass Mojarra-Versionen, die älter als 2.1.18 sind, einen Fehler beim teilweisen Speichern des Status hatten, wenn auf eine Bean mit Ansichtsbereich in einem JSTL-Tag-Attribut verwiesen wurde. Die Bean mit dem gesamten Ansichtsbereich wird neu erstellt, anstatt aus dem Ansichtsbaum abgerufen zu werden (einfach, weil der vollständige Ansichtsbaum zum Zeitpunkt der Ausführung von JSTL noch nicht verfügbar ist). Wenn Sie einen Status in der Bean mit Ansichtsbereich durch ein JSTL-Tag-Attribut erwarten oder speichern, wird der erwartete Wert nicht zurückgegeben, oder er geht in der Bean mit realem Ansichtsbereich "verloren", die nach der Ansicht wiederhergestellt wird Baum wird gebaut. Falls Sie kein Upgrade auf Mojarra 2.1.18 oder neuer durchführen können, müssen Sie das Speichern des teilweisen Status web.xmlwie folgt deaktivieren :

<context-param>
    <param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
    <param-value>false</param-value>
</context-param>

Siehe auch:

In den folgenden Fragen / Antworten finden Sie einige Beispiele aus der Praxis, in denen JSTL-Tags hilfreich sind (dh wenn sie beim Erstellen der Ansicht wirklich richtig verwendet werden):


In einer Nussschale

Wenn Sie JSF-Komponenten bedingt rendern möchten , verwenden Sie renderedstattdessen das Attribut für die JSF-HTML-Komponente, insbesondere wenn es sich #{lpc}um das aktuell iterierte Element einer JSF-iterierenden Komponente wie <h:dataTable>oder handelt <ui:repeat>.

<h:someComponent rendered="#{lpc.verbose}">
    ...
</h:someComponent>

Oder, wenn Sie wollen Build (Erstellen / Hinzufügen) JSF - Komponenten bedingt, dann halten JSTL verwenden. Es ist viel besser als wörtlich new SomeComponent()in Java.

<c:if test="#{lpc.verbose}">
    <h:someComponent>
        ...
    </h:someComponent>
</c:if>

Siehe auch:


3
@Aklin: Nein? Wie wäre es mit diesem Beispiel ?
BalusC

1
Ich kann den ersten Absatz lange Zeit nicht richtig interpretieren (die Beispiele sind jedoch sehr klar). Daher hinterlasse ich diesen Kommentar als den einzigen Weg. Durch diesen Absatz habe ich den Eindruck, dass <ui:repeat>es sich um einen Tag-Handler handelt (aufgrund dieser Zeile " Beachten Sie, dass JSF eigene <f:xxx>und <ui:xxx>... "), der genau wie <c:forEach>und daher zum Zeitpunkt der Erstellung der Ansicht ausgewertet wird (wieder genau wie <c:forEach>). . Wenn es das ist, sollte es keinen sichtbaren funktionalen Unterschied zwischen <ui:repeat>und geben <c:forEach>? Ich verstehe nicht, was genau dieser Absatz bedeutet :)
Winziger

1
Entschuldigung, ich werde diesen Beitrag nicht weiter verschmutzen. Ich habe Ihren vorherigen Kommentar auf mich aufmerksam gemacht, aber dieser Satz " Beachten Sie, dass JSFs eigene <f:xxx>und <ui:xxx>nicht erweiterte Tags UIComponentauch Tag-Handler sind. " Versucht zu implizieren, dass dies <ui:repeat>auch ein Tag-Handler ist, weil er <ui:xxx>auch enthält <ui:repeat>? Dies sollte dann bedeuten, dass dies <ui:repeat>eine der Komponenten ist <ui:xxx>, die sich ausdehnen UIComponent. Daher ist es kein Tag-Handler. (Einige von ihnen können möglicherweise nicht erweitert werden UIComponent. Daher sind sie Tag-Handler.) Ist das so?
Winzig

2
@Shirgill: <c:set>ohne scopeerstellt einen Alias ​​des EL-Ausdrucks, anstatt den ausgewerteten Wert im Zielbereich festzulegen. Versuchen Sie es scope="request"stattdessen, wodurch der Wert sofort ausgewertet wird (tatsächlich während der Erstellungszeit der Ansicht) und als Anforderungsattribut festgelegt wird (das während der Iteration nicht "überschrieben" wird). Unter der Decke wird ein ValueExpressionObjekt erstellt und festgelegt .
BalusC

1
@ K.Nicholas: Es ist unter der Decke a ClassNotFoundException. Die Laufzeitabhängigkeiten Ihres Projekts sind fehlerhaft. Höchstwahrscheinlich verwenden Sie einen Nicht-JavaEE-Server wie Tomcat und haben vergessen, JSTL zu installieren, oder Sie haben versehentlich sowohl JSTL 1.0 als auch JSTL 1.1+ eingeschlossen. Denn in JSTL 1.0 ist das Paket javax.servlet.jstl.core.*und seit JSTL 1.1 ist dies geworden javax.servlet.jsp.jstl.core.*. Hinweise zur Installation von JSTL finden Sie hier: stackoverflow.com/a/4928309
BalusC

13

verwenden

<h:panelGroup rendered="#{lpc.verbose}">
  ...
</h:panelGroup>

Danke, tolle Antwort. Allgemeiner: Sind JSTL-Tags immer noch sinnvoll oder sollten wir sie seit JSF 2.0 als veraltet betrachten?
Januar

In den meisten Fällen ja. Aber manchmal ist es angebracht, sie zu verwenden
Bozho

3
Die Verwendung von h: panelGroup ist eine schmutzige Lösung, da ein <span> -Tag generiert wird, während c: if dem HTML-Code nichts hinzufügt. h: panelGroup ist auch innerhalb von panelGrids problematisch, da es die Elemente gruppiert.
Rober2D2

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.