JSTL in JSF2 Facelets ... ha senso?


163

Vorrei produrre un po 'di codice Facelets in modo condizionale.

A tale scopo, i tag JSTL sembrano funzionare correttamente:

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

Tuttavia, non sono sicuro che questa sia una buona pratica? C'è un altro modo per raggiungere il mio obiettivo?

Risposte:


320

introduzione

I <c:xxx>tag JSTL sono tutti gestori di tag e vengono eseguiti durante il tempo di creazione della vista , mentre i <h:xxx>tag JSF sono tutti componenti dell'interfaccia utente e vengono eseguiti durante il tempo di rendering della vista .

Si noti che dal di JSF proprie <f:xxx>e <ui:xxx>tag solo quelle che non si estendono da UIComponentsono anche taghandlers, ad esempio <f:validator>, <ui:include>, <ui:define>, ecc Quelli che si estendono da UIComponentsono anche componenti JSF UI, ad esempio <f:param>, <ui:fragment>, <ui:repeat>, ecc Dai componenti JSF UI solo l' ide bindingattributi sono valutato anche durante il tempo di costruzione della vista. Pertanto, la risposta di seguito relativa al ciclo di vita JSTL si applica anche agli attributi ide ai bindingcomponenti JSF.

Il tempo di creazione della vista è quel momento in cui il file XHTML / JSP deve essere analizzato e convertito in un albero componente JSF che viene quindi archiviato a partire UIViewRootdal file FacesContext. Il tempo di rendering della vista è quel momento in cui l'albero dei componenti JSF sta per generare HTML, a partire da UIViewRoot#encodeAll(). Quindi: i componenti dell'interfaccia utente JSF e i tag JSTL non vengono sincronizzati come ci si aspetterebbe dalla codifica. È possibile visualizzarlo come segue: JSTL viene eseguito dall'alto verso il basso, producendo l'albero dei componenti JSF, quindi è il turno di JSF di eseguire dall'alto verso il basso di nuovo, producendo l'output HTML.

<c:forEach> vs <ui:repeat>

Ad esempio, questo markup Facelets che scorre su 3 elementi usando <c:forEach>:

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

... crea durante il tempo di costruzione della vista tre <h:outputText>componenti separati nella struttura dei componenti JSF, rappresentati approssimativamente in questo modo:

<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}" />

... che a loro volta generano individualmente il loro output HTML durante il tempo di rendering della vista:

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

Si noti che è necessario garantire manualmente l'unicità degli ID componente e che anche quelli vengano valutati durante il tempo di costruzione della vista.

Mentre questo Facelets markup iterando su 3 elementi utilizzando <ui:repeat>, che è un componente dell'interfaccia utente JSF:

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

... finisce già così com'è nell'albero del componente JSF per cui lo stesso <h:outputText>componente è durante il tempo di rendering della vista che viene riutilizzato per generare output HTML in base al round di iterazione corrente:

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

Si noti che <ui:repeat>essendo un NamingContainercomponente già assicurato l'unicità dell'ID client in base all'indice di iterazione; non è inoltre possibile utilizzare EL idnell'attributo dei componenti figlio in questo modo poiché viene anche valutato durante il tempo di costruzione della vista mentre #{item}è disponibile solo durante il tempo di rendering della vista. Lo stesso vale per un h:dataTablee simili componenti.

<c:if>/ <c:choose>vsrendered

Come altro esempio, questo markup Facelets aggiunge in modo condizionale diversi tag usando <c:if>(puoi anche usare <c:choose><c:when><c:otherwise>per questo):

<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 = TEXTaggiungerà solo <h:inputText>il componente all'albero dei componenti JSF:

<h:inputText ... />

Mentre questo markup Facelets:

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

... finirà esattamente come sopra nella struttura dei componenti JSF indipendentemente dalla condizione. Questo può quindi finire in un albero dei componenti "gonfio" quando ne hai molti e sono in realtà basati su un modello "statico" (cioè fieldnon cambia mai durante almeno l'ambito della vista). Inoltre, potresti riscontrare problemi EL quando hai a che fare con sottoclassi con proprietà aggiuntive nelle versioni Mojarra precedenti alla 2.2.7.

<c:set> vs <ui:param>

Non sono intercambiabili. I <c:set>set di una variabile nel campo di applicazione EL, che è accessibile solo dopo la posizione tag durante il tempo di visualizzazione di compilazione, ma ovunque nella vista durante la visualizzazione tempo di rendering. Il <ui:param>passa una variabile EL a un modello facelet incluso via <ui:include>, <ui:decorate template>o <ui:composition template>. Le versioni precedenti di JSF avevano dei bug per cui la <ui:param>variabile è disponibile anche al di fuori del modello Facelet in questione, questo non dovrebbe mai essere fatto affidamento.

Il <c:set>senza scopeattributo si comporterà come un alias. Non memorizza nella cache il risultato dell'espressione EL in alcun ambito. Può quindi essere perfettamente utilizzato all'interno, ad esempio iterando i componenti JSF. Pertanto, ad esempio, di seguito funzionerà bene:

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

Non è adatto solo per il calcolo della somma in un ciclo. Per questo invece usa il flusso EL 3.0 :

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

Solo, quando si imposta l' scopeattributo con uno dei valori ammissibili request, view, session, o application, allora sarà valutata subito durante il tempo di visualizzazione di compilazione e memorizzati nell'ambito specificato.

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

Questo sarà valutato solo una volta e disponibile come #{dev}nell'intera applicazione.

Utilizzare JSTL per controllare la costruzione dell'albero dei componenti JSF

Utilizzando JSTL può portare a risultati inaspettati quando viene utilizzato all'interno componenti JSF iterazione come <h:dataTable>, <ui:repeat>ecc, o quando gli attributi tag JSTL dipendono risultati degli eventi JSF come preRenderViewo scritte valori di modulo nel modello che non sono disponibili durante la visualizzazione del tempo di costruzione . Quindi, utilizzare i tag JSTL solo per controllare il flusso della costruzione dell'albero dei componenti JSF. Utilizzare i componenti dell'interfaccia utente JSF per controllare il flusso di generazione dell'output HTML. Non associare i varcomponenti JSF iteranti agli attributi dei tag JSTL. Non fare affidamento sugli eventi JSF negli attributi dei tag JSTL.

Ogni volta che pensi di dover associare un componente al backing bean tramite binding, o afferrarne uno tramite findComponent(), e creare / manipolare i suoi figli usando il codice Java in un backing bean con new SomeComponent()e cosa no, allora dovresti immediatamente smettere e considerare invece l'uso di JSTL. Poiché anche JSTL è basato su XML, il codice necessario per creare dinamicamente componenti JSF diventerà molto più leggibile e gestibile.

È importante sapere che le versioni di Mojarra precedenti alla 2.1.18 avevano un bug nel salvataggio parziale dello stato quando si faceva riferimento a un bean con ambito vista in un attributo tag JSTL. L'intero bean con ambito vista verrà ricreato di recente anziché recuperato dall'albero della vista (semplicemente perché l'albero della vista completo non è ancora disponibile nel punto in cui viene eseguito JSTL). Se ti aspetti o memorizzi un certo stato nel bean con ambito vista da un attributo tag JSTL, allora non restituirà il valore che ti aspetti, o sarà "perso" nel bean con ambito vista reale che viene ripristinato dopo la vista l'albero è costruito. Nel caso in cui non sia possibile eseguire l'aggiornamento a Mojarra 2.1.18 o versioni successive, la soluzione è disattivare il salvataggio parziale dello stato web.xmlcome di seguito:

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

Guarda anche:

Per vedere alcuni esempi del mondo reale in cui i tag JSTL sono utili (ad esempio, se utilizzati correttamente durante la creazione della vista), vedere le seguenti domande / risposte:


In poche parole

Per quanto riguarda i requisiti funzionali concreti, se si desidera eseguire il rendering condizionale dei componenti JSF, utilizzare invece l' renderedattributo sul componente HTML JSF, in particolare se #{lpc}rappresenta l'elemento attualmente iterato di un componente iterante JSF come <h:dataTable>o <ui:repeat>.

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

Oppure, se si desidera creare (creare / aggiungere) componenti JSF in modo condizionale, continuare a utilizzare JSTL. È molto meglio che fare verbosamente new SomeComponent()in Java.

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

Guarda anche:


3
@Aklin: No? Che ne dici di questo esempio ?
BalusC

1
Non riesco a interpretare correttamente il primo paragrafo per molto tempo (gli esempi forniti sono comunque molto chiari). Quindi, lascio questo commento come unico modo. Con quel paragrafo, ho l'impressione che <ui:repeat>sia un gestore di tag (a causa di questa riga, " Nota che JSF è proprio <f:xxx>e <ui:xxx>... ") proprio come <c:forEach>e quindi, viene valutato al momento della creazione della vista (di nuovo proprio come <c:forEach>) . In tal caso, non ci dovrebbero essere differenze visibili e funzionali tra <ui:repeat>e <c:forEach>? Non capisco cosa significhi esattamente quel paragrafo :)
Piccolo

1
Siamo spiacenti, non inquinerò più questo post. Ho preso il tuo commento precedente alla mia attenzione, ma questa frase, " Nota che i propri <f:xxx>e i <ui:xxx>tag di JSF che non si estendono UIComponentsono anche gestori di tag. " Tenta di implicare che <ui:repeat>è anche un gestore di tag perché <ui:xxx>include anche <ui:repeat>? Ciò dovrebbe quindi significare che <ui:repeat>è uno dei componenti <ui:xxx>che si estende UIComponent. Quindi, non è un gestore di tag. (Alcuni di essi potrebbero non estendersi UIComponent. Quindi, sono gestori di tag).
Piccolo

2
@Shirgill: <c:set>senza scopecrea un alias dell'espressione EL invece di impostare il valore valutato nell'ambito di destinazione. Prova scope="request"invece, che valuterà immediatamente il valore (durante il tempo di creazione della vista) e lo imposterà come attributo di richiesta (che non verrà "sovrascritto" durante l'iterazione). Sotto le coperte, crea e imposta un ValueExpressionoggetto.
BalusC

1
@ K. Nicholas: è sotto le coperte a ClassNotFoundException. Le dipendenze di runtime del progetto sono interrotte. Molto probabilmente stai usando un server non JavaEE come Tomcat e hai dimenticato di installare JSTL, oppure hai accidentalmente incluso sia JSTL 1.0 che JSTL 1.1+. Perché in JSTL 1.0 il pacchetto è javax.servlet.jstl.core.*e da JSTL 1.1 questo è diventato javax.servlet.jsp.jstl.core.*. Indizi per l'installazione JSTL possono essere trovate qui: stackoverflow.com/a/4928309
BalusC

13

uso

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

Grazie, ottima risposta. Più in generale: i tag JSTL hanno ancora senso o dovremmo considerarli obsoleti dal JSF 2.0?
Jan

Nella maggior parte dei casi, sì. Ma a volte è opportuno usarli
Bozho,

3
L'uso di h: panelGroup è una soluzione sporca, perché genera un tag <span>, mentre c: if non aggiunge nulla al codice html. h: panelGroup è anche problematico all'interno di panelGrids, in quanto raggruppa gli elementi.
Rober2D2,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.