JSTL en JSF2 Facelets ... ¿tiene sentido?

163

Me gustaría generar un poco de código Facelets condicionalmente.

Para ese propósito, las etiquetas JSTL parecen funcionar bien:

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

Sin embargo, no estoy seguro de si esta es una mejor práctica. ¿Hay otra forma de lograr mi objetivo?

ene
fuente

Respuestas:

320

Introducción

Todas las <c:xxx>etiquetas JSTL son manipuladores de etiquetas y se ejecutan durante el tiempo de creación de la vista , mientras que las <h:xxx>etiquetas JSF son todos componentes de la interfaz de usuario y se ejecutan durante el tiempo de representación de la vista .

Tenga en cuenta que a partir del propio JSF <f:xxx>y <ui:xxx>etiquetas solamente aquellas que no se extienden desde UIComponenttambién son taghandlers, por ejemplo <f:validator>, <ui:include>, <ui:define>, etc. Los que se extienden desde UIComponenttambién son componentes JSF interfaz de usuario, por ejemplo <f:param>, <ui:fragment>, <ui:repeat>, etc. A partir de componentes de interfaz de usuario JSF sólo el idy bindingatributos son también evaluado durante el tiempo de construcción de la vista. Por lo tanto, la respuesta a continuación sobre el ciclo de vida JSTL también se aplica a los atributos idy bindingde los componentes JSF.

El tiempo de compilación de la vista es el momento en que el archivo XHTML / JSP debe analizarse y convertirse en un árbol de componentes JSF que luego se almacena a partir UIViewRootde FacesContext. El tiempo de representación de la vista es el momento en que el árbol de componentes JSF está a punto de generar HTML, comenzando por UIViewRoot#encodeAll(). Entonces: los componentes de la interfaz de usuario JSF y las etiquetas JSTL no se ejecutan en sincronización como cabría esperar de la codificación. Puede visualizarlo de la siguiente manera: JSTL se ejecuta primero de arriba a abajo, produciendo el árbol de componentes JSF, luego le toca a JSF volver a ejecutar de arriba a abajo, produciendo la salida HTML.

<c:forEach> vs <ui:repeat>

Por ejemplo, este marcado de Facelets iterando sobre 3 elementos usando <c:forEach>:

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

... crea durante el tiempo de compilación de la vista tres <h:outputText>componentes separados en el árbol de componentes JSF, aproximadamente representados así:

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

... que a su vez generan individualmente su salida HTML durante el tiempo de visualización:

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

Tenga en cuenta que debe garantizar manualmente la unicidad de los ID de componentes y que también se evalúan durante el tiempo de construcción de la vista.

Si bien este marcado de Facelets itera sobre 3 elementos usando <ui:repeat>, que es un componente JSF UI:

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

... ya termina como está en el árbol de componentes JSF, por lo que el mismo <h:outputText>componente está durante el tiempo de representación de la vista y se reutiliza para generar resultados HTML basados ​​en la ronda de iteración actual:

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

Tenga en cuenta que el hecho de <ui:repeat>ser un NamingContainercomponente ya garantizaba la unicidad de la ID del cliente basada en el índice de iteración; Tampoco es posible utilizar EL en el idatributo de componentes secundarios de esta manera, ya que también se evalúa durante el tiempo de creación de la vista, mientras #{item}que solo está disponible durante el tiempo de representación de la vista. Lo mismo es cierto para un h:dataTabley componentes similares.

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

Como otro ejemplo, este marcado de Facelets agrega condicionalmente diferentes etiquetas usando <c:if>(también puede usar <c:choose><c:when><c:otherwise>para esto):

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

... en caso de que type = TEXTsolo agregue el <h:inputText>componente al árbol de componentes JSF:

<h:inputText ... />

Si bien este marcado de Facelets:

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

... terminará exactamente como se indica arriba en el árbol de componentes JSF independientemente de la condición. Por lo tanto, esto puede terminar en un árbol de componentes "hinchado" cuando tiene muchos de ellos y en realidad se basan en un modelo "estático" (es decir, fieldque nunca cambia durante al menos el alcance de la vista). Además, puede encontrarse con problemas EL cuando trata con subclases con propiedades adicionales en las versiones de Mojarra anteriores a 2.2.7.

<c:set> vs <ui:param>

No son intercambiables. Los <c:set>conjuntos de una variable en el ámbito EL, que sólo se puede acceder después de la ubicación de la etiqueta durante el tiempo de vista de construcción, sino en cualquier parte de la vista durante la vista el tiempo de renderización. El <ui:param>pasa una variable de EL a una plantilla Facelet incluye a través de <ui:include>, <ui:decorate template>o <ui:composition template>. Las versiones anteriores de JSF tenían errores por los cuales la <ui:param>variable también está disponible fuera de la plantilla de Facelet en cuestión, esto nunca se debe confiar.

El <c:set>sin un scopeatributo se comportará como un alias. No almacena en caché el resultado de la expresión EL en ningún ámbito. Por lo tanto, puede usarse perfectamente en el interior, por ejemplo, iterando componentes JSF. Por lo tanto, por ejemplo, a continuación funcionará bien:

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

Solo no es adecuado, por ejemplo, para calcular la suma en un bucle. Para eso, en su lugar, use el flujo EL 3.0 :

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

Solamente, cuando se establece el scopeatributo con uno de los valores permisibles request, view, session, o application, entonces será evaluado inmediatamente durante el tiempo de vista de construcción y se almacena en el ámbito especificado.

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

Esto se evaluará solo una vez y estará disponible como en #{dev}toda la aplicación.

Utilice JSTL para controlar la construcción de árbol de componentes JSF

El uso de JSTL solo puede generar resultados inesperados cuando se usa dentro de componentes iterativos JSF como <h:dataTable>, <ui:repeat>etc., o cuando los atributos de la etiqueta JSTL dependen de los resultados de eventos JSF como preRenderViewvalores de formulario enviados en el modelo que no están disponibles durante el tiempo de construcción de la vista . Por lo tanto, use etiquetas JSTL solo para controlar el flujo de la construcción de árbol de componentes JSF. Utilice los componentes de la interfaz de usuario JSF para controlar el flujo de la generación de salida HTML. No enlace los varcomponentes JSF iterativos a los atributos de etiqueta JSTL. No confíe en los eventos JSF en los atributos de etiqueta JSTL.

Cada vez que piense que necesita vincular un componente al backing bean via binding, o tomar uno via findComponent(), y crear / manipular sus hijos usando el código Java en un backing bean con new SomeComponent()y lo que no, debe detenerse inmediatamente y considerar usar JSTL en su lugar. Como JSTL también está basado en XML, el código necesario para crear dinámicamente componentes JSF será mucho más fácil de leer y mantener.

Es importante saber que las versiones de Mojarra anteriores a 2.1.18 tenían un error en el ahorro de estado parcial al hacer referencia a un bean de ámbito de vista en un atributo de etiqueta JSTL. La visión de conjunto de frijol de ámbito sería recién recreado en lugar de recuperar de la visión árbol (simplemente porque el árbol visión completa aún no está disponible en las carreras punto de JSTL). Si espera o almacena algún estado en el bean de ámbito de vista mediante un atributo de etiqueta JSTL, no devolverá el valor que espera, o se "perderá" en el bean de ámbito de vista real que se restaura después de la vista El árbol está construido. En caso de que no pueda actualizar a Mojarra 2.1.18 o más reciente, la solución consiste en desactivar el ahorro de estado parcial de la web.xmlsiguiente manera:

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

Ver también:

Para ver algunos ejemplos del mundo real en los que las etiquetas JSTL son útiles (es decir, cuando se usan realmente correctamente durante la creación de la vista), consulte las siguientes preguntas / respuestas:


En una palabra

En cuanto a su requisito funcional concreto, si desea representar los componentes JSF condicionalmente, utilice el renderedatributo en el componente HTML JSF, en particular si #{lpc}representa el elemento iterado actualmente de un componente iterativo JSF como <h:dataTable>o <ui:repeat>.

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

O, si desea construir (crear / agregar) componentes JSF condicionalmente, siga usando JSTL. Es mucho mejor que hacerlo new SomeComponent()en Java.

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

Ver también:

BalusC
fuente
3
@Aklin: ¿No? ¿Qué tal este ejemplo ?
BalusC
1
No puedo interpretar el primer párrafo correctamente durante mucho tiempo (sin embargo, los ejemplos dados son muy claros). Por lo tanto, estoy dejando este comentario como la única forma. En ese párrafo, tengo la impresión de que <ui:repeat>es un manejador de etiquetas (debido a esta línea, " Tenga en cuenta que JSF es propio <f:xxx>y <ui:xxx>... ") exactamente igual <c:forEach>y, por lo tanto, se evalúa en el momento de la compilación de la vista (nuevamente igual que igual <c:forEach>) . Si es así, entonces, ¿no debería haber ninguna diferencia visible y funcional entre <ui:repeat>y <c:forEach>? No entiendo qué significa exactamente ese párrafo :)
Tiny
1
Lo siento, no voy a contaminar más esta publicación. Tomé en cuenta su comentario anterior, pero esta oración, " Tenga en cuenta que las etiquetas propias <f:xxx>y las <ui:xxx>etiquetas de JSF que no se extienden UIComponenttambién son manejadores de etiquetas ", intenta implicar que <ui:repeat>también es un manejador de etiquetas porque <ui:xxx>también incluye <ui:repeat>. Esto debería significar que <ui:repeat>es uno de los componentes <ui:xxx>que se extiende UIComponent. Por lo tanto, no es un manejador de etiquetas. (Algunos de ellos pueden no extenderse UIComponent. Por lo tanto, son manejadores de etiquetas) ¿Lo hace?
Diminuto
2
@ Shirgill: <c:set>sin scopecrea un alias de la expresión EL en lugar de establecer el valor evaluado en el alcance objetivo. En su scope="request"lugar, intente , que evaluará inmediatamente el valor (durante el tiempo de creación de la vista) y lo establecerá como atributo de solicitud (que no se "sobrescribirá" durante la iteración). Debajo de las cubiertas, crea y establece un ValueExpressionobjeto.
BalusC
1
@ K.Nicholas: Está debajo de las sábanas a ClassNotFoundException. Las dependencias de tiempo de ejecución de su proyecto están rotas. Lo más probable es que esté utilizando un servidor que no sea JavaEE como Tomcat y haya olvidado instalar JSTL, o haya incluido accidentalmente tanto JSTL 1.0 como JSTL 1.1+. Porque en JSTL 1.0 el paquete es javax.servlet.jstl.core.*y desde JSTL 1.1 esto se ha convertido javax.servlet.jsp.jstl.core.*. Las pistas para instalar JSTL se pueden encontrar aquí: stackoverflow.com/a/4928309
BalusC
13

utilizar

<h:panelGroup rendered="#{lpc.verbose}">
  ...
</h:panelGroup>
Bozho
fuente
Gracias, gran respuesta. Más en general: ¿todavía tienen sentido las etiquetas JSTL o deberíamos considerarlas obsoletas desde JSF 2.0?
Jan
En la mayoría de los casos, sí. Pero a veces es apropiado usarlos
Bozho
3
Usar h: panelGroup es una solución sucia, porque genera una etiqueta <span>, mientras que c: if no agrega nada al código html. h: panelGroup también es problemático dentro de panelGrids, ya que agrupa los elementos.
Rober2D2
4

Para una salida tipo interruptor, puede usar la cara del interruptor de PrimeFaces Extensions.

Ravshan Samandarov
fuente