JSTL dans JSF2 Facelets… ça a du sens?

163

Je voudrais sortir un peu de code Facelets sous condition.

Pour cela, les balises JSTL semblent fonctionner correctement:

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

Cependant, je ne suis pas sûr qu'il s'agisse d'une meilleure pratique? Existe-t-il une autre façon d'atteindre mon objectif?

Jan
la source

Réponses:

320

introduction

Les <c:xxx>balises JSTL sont toutes des gestionnaires de balises et elles sont exécutées au moment de la création de la vue , tandis que les <h:xxx>balises JSF sont toutes des composants de l'interface utilisateur et elles sont exécutées au moment du rendu de la vue .

Notez que des propres JSF <f:xxx>et <ui:xxx>balises uniquement ceux qui ne pas prolonger de UIComponentsont également taghandlers, par exemple <f:validator>, <ui:include>, <ui:define>, etc. Ceux qui s'étendent UIComponentsont également des composants JSF interface utilisateur, par exemple <f:param>, <ui:fragment>, <ui:repeat>, etc. A partir de composants JSF UI seulement les idet bindingattributs sont également évalué pendant la génération de la vue. Ainsi, la réponse ci-dessous concernant le cycle de vie JSTL s'applique également aux attributs idet bindingdes composants JSF.

L'heure de création de la vue correspond au moment où le fichier XHTML / JSP doit être analysé et converti en une arborescence de composants JSF qui est ensuite stockée à partir UIViewRootdu FacesContext. Le temps de rendu de la vue correspond au moment où l'arborescence des composants JSF est sur le point de générer du HTML, en commençant par UIViewRoot#encodeAll(). Donc: les composants de l'interface utilisateur JSF et les balises JSTL ne fonctionnent pas de manière synchronisée comme vous l'attendriez du codage. Vous pouvez le visualiser comme suit: JSTL s'exécute d'abord de haut en bas, produisant l'arborescence des composants JSF, puis c'est au tour de JSF de s'exécuter de haut en bas à nouveau, produisant la sortie HTML.

<c:forEach> contre <ui:repeat>

Par exemple, ce balisage Facelets itère sur 3 éléments en utilisant <c:forEach>:

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

... crée pendant la construction de la vue trois <h:outputText>composants distincts dans l'arborescence des composants JSF, représentés à peu près comme ceci:

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

... qui à leur tour génèrent individuellement leur sortie HTML pendant le temps de rendu de la vue:

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

Notez que vous devez vous assurer manuellement de l'unicité des ID de composant et que ceux-ci sont également évalués lors de la génération de la vue.

Alors que ce balisage Facelets itère sur 3 éléments en utilisant <ui:repeat>, qui est un composant d'interface utilisateur JSF:

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

... se termine déjà tel quel dans l'arborescence des composants JSF, le même <h:outputText>composant étant réutilisé pendant le temps de rendu de la vue pour générer une sortie HTML basée sur le cycle d'itération actuel:

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

Notez que le <ui:repeat>comme étant un NamingContainercomposant garantissait déjà l'unicité de l'ID client basé sur l'index d'itération; il n'est pas non plus possible d'utiliser EL dans l' idattribut des composants enfants de cette façon car il est également évalué pendant la construction de la vue alors qu'il #{item}n'est disponible que pendant le temps de rendu de la vue. La même chose est vraie pour un h:dataTableet des composants similaires.

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

Comme autre exemple, ce balisage Facelets ajoute conditionnellement différentes balises en utilisant <c:if>(vous pouvez également utiliser <c:choose><c:when><c:otherwise>pour cela):

<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 = TEXTajoutera uniquement le <h:inputText>composant à l'arborescence des composants JSF:

<h:inputText ... />

Alors que ce balisage Facelets:

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

... se terminera exactement comme ci-dessus dans l'arborescence des composants JSF, quelle que soit la condition. Cela peut donc aboutir à une arborescence de composants "gonflée" lorsque vous en avez beaucoup et qu'ils sont en fait basés sur un modèle "statique" (c'est-à-dire que le fieldne change jamais pendant au moins la portée de la vue). De plus, vous pouvez rencontrer des problèmes EL lorsque vous traitez avec des sous-classes avec des propriétés supplémentaires dans les versions Mojarra antérieures à la 2.2.7.

<c:set> contre <ui:param>

Ils ne sont pas interchangeables. Les <c:set>jeux d' une variable dans le champ EL, qui est accessible uniquement après l'emplacement de la balise durant la compilation de vue, mais partout dans la vue pendant la vue du rendu. La <ui:param>passe d' une variable à un modèle EL Facelet inclus via <ui:include>, <ui:decorate template>ou <ui:composition template>. Les anciennes versions de JSF avaient des bogues dans lesquels la <ui:param>variable est également disponible en dehors du modèle Facelet en question, cela ne devrait jamais être invoqué.

Le <c:set>sans scopeattribut se comportera comme un alias. Il ne met en cache le résultat de l'expression EL dans aucune portée. Il peut donc parfaitement être utilisé à l'intérieur, par exemple, de l'itération de composants JSF. Ainsi, par exemple ci-dessous fonctionnera bien:

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

Il ne convient pas, par exemple, pour calculer la somme dans une boucle. Pour cela, utilisez plutôt le flux EL 3.0 :

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

Seulement, lorsque vous définissez l' scopeattribut avec l' une des valeurs autorisées request, view, sessionou application, il sera évalué immédiatement au moment de la construction de la vue et stockée dans le champ spécifié.

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

Celui-ci ne sera évalué qu'une seule fois et disponible comme #{dev}dans toute la candidature.

Utilisez JSTL pour contrôler la création de l'arborescence des composants JSF

L' utilisation JSTL ne peut conduire à des résultats inattendus lorsqu'ils sont utilisés à l' intérieur des composants JSF tels que <h:dataTable>, <ui:repeat>, etc, ou lorsque les attributs de balise JSTL dépendent des résultats des événements JSF tels que preRenderViewou les valeurs de forme présentées dans le modèle qui ne sont pas disponibles pendant la vue du temps de construction . Par conséquent, utilisez les balises JSTL uniquement pour contrôler le flux de création de l'arborescence des composants JSF. Utilisez les composants de l'interface utilisateur JSF pour contrôler le flux de génération de sortie HTML. Ne liez pas les varcomposants JSF en cours d'itération aux attributs de balise JSTL. Ne vous fiez pas aux événements JSF dans les attributs de balise JSTL.

Chaque fois que vous pensez avoir besoin de lier un composant au backing bean via binding, ou d'en saisir un via findComponent(), et de créer / manipuler ses enfants en utilisant du code Java dans un backing bean avec new SomeComponent()et quoi pas, alors vous devez immédiatement arrêter et envisager d'utiliser JSTL à la place. Comme JSTL est également basé sur XML, le code nécessaire pour créer dynamiquement des composants JSF deviendra tellement mieux lisible et maintenable.

Il est important de savoir que les versions de Mojarra antérieures à 2.1.18 avaient un bogue lors de la sauvegarde d'état partielle lors du référencement d'un bean à portée de vue dans un attribut de balise JSTL. Le bean à portée de vue entière serait nouvellement recréé au lieu d'être extrait de l'arborescence de vues (simplement parce que l'arborescence de vues complète n'est pas encore disponible au moment où JSTL s'exécute). Si vous attendez ou stockez un état dans le bean à portée de vue par un attribut de balise JSTL, il ne retournera pas la valeur que vous attendez, ou il sera "perdu" dans le bean à portée de vue réelle qui est restauré après la vue l'arbre est construit. Dans le cas où vous ne pouvez pas mettre à niveau vers Mojarra 2.1.18 ou une version plus récente, la solution consiste à désactiver l'enregistrement d'état partiel web.xmlcomme ci-dessous:

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

Voir également:

Pour voir des exemples concrets où les balises JSTL sont utiles (c'est-à-dire lorsqu'elles sont vraiment correctement utilisées lors de la construction de la vue), consultez les questions / réponses suivantes:


En un mot

En ce qui concerne vos exigences fonctionnelles concrètes, si vous souhaitez rendre des composants JSF de manière conditionnelle, utilisez renderedplutôt l' attribut sur le composant HTML JSF, en particulier si #{lpc}représente l'élément actuellement itéré d'un composant itératif JSF tel que <h:dataTable>ou <ui:repeat>.

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

Ou, si vous souhaitez créer (créer / ajouter) des composants JSF de manière conditionnelle, continuez à utiliser JSTL. C'est bien mieux que de le faire verbeusement new SomeComponent()en java.

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

Voir également:

BalusC
la source
3
@Aklin: Non? Et cet exemple ?
BalusC
1
Je ne peux pas interpréter correctement le premier paragraphe pendant longtemps (les exemples donnés sont cependant très clairs). Par conséquent, je laisse ce commentaire comme le seul moyen. Par ce paragraphe, j'ai l'impression qu'il <ui:repeat>s'agit d'un gestionnaire de balises (à cause de cette ligne, " Notez que JSF est propre <f:xxx>et <ui:xxx>... ") juste comme <c:forEach>et par conséquent, il est évalué au moment de la construction de la vue (encore une fois comme juste comme <c:forEach>) . Si tel est le cas, il ne devrait pas y avoir de différence fonctionnelle visible entre <ui:repeat>et <c:forEach>? Je ne comprends pas exactement ce que signifie ce paragraphe :)
Minuscule
1
Désolé, je ne polluerai plus ce message. J'ai attiré mon attention sur votre commentaire précédent, mais cette phrase n'est-elle pas la suivante: " Notez que JSF est propre <f:xxx>et les <ui:xxx>balises qui ne s'étendent pasUIComponent sont également des gestionnaires de balises. " tente-t-elle pas d'impliquer que <ui:repeat>c'est aussi un gestionnaire de balises car <ui:xxx>inclut également <ui:repeat>? Cela devrait alors signifier que <ui:repeat>c'est l'un des composants <ui:xxx>qui s'étend UIComponent. Par conséquent, ce n'est pas un gestionnaire de balises. (Certains d'entre eux peuvent ne pas s'étendre UIComponent. Par conséquent, ce sont des gestionnaires de balises) Vraiment?
Minuscule
2
@Shirgill: <c:set>without scopecrée un alias de l'expression EL au lieu de définir la valeur évaluée dans la portée cible. Essayez à la scope="request"place, qui évaluera immédiatement la valeur (pendant la construction de la vue en effet) et la définira comme attribut de requête (qui ne sera pas «écrasé» pendant l'itération). Sous les couvertures, il crée et pose un ValueExpressionobjet.
BalusC
1
@ K.Nicholas: C'est sous les couvertures a ClassNotFoundException. Les dépendances d'exécution de votre projet sont rompues. Vous utilisez très probablement un serveur non JavaEE tel que Tomcat et vous avez oublié d'installer JSTL, ou vous avez accidentellement inclus à la fois JSTL 1.0 et JSTL 1.1+. Parce que dans JSTL 1.0, le package est javax.servlet.jstl.core.*et depuis JSTL 1.1, il est devenujavax.servlet.jsp.jstl.core.* . Des indices pour l'installation de JSTL peuvent être trouvés ici: stackoverflow.com/a/4928309
BalusC
13

utilisation

<h:panelGroup rendered="#{lpc.verbose}">
  ...
</h:panelGroup>
Bozho
la source
Thx, excellente réponse. Plus en général: les balises JSTL ont-elles encore un sens ou devrions-nous les considérer comme obsolètes depuis JSF 2.0?
janvier
Dans la plupart des cas, oui. Mais parfois, il est approprié de les utiliser
Bozho
3
Utiliser h: panelGroup est une solution sale, car il génère une balise <span>, tandis que c: if n'ajoute rien au code html. h: panelGroup est également problématique à l'intérieur de panelGrids, car il regroupe les éléments.
Rober2D2
4

Pour la sortie de commutation semblable, vous pouvez utiliser le commutateur visage PrimeFaces Extensions.

Ravshan Samandarov
la source