JSTL en JSF2 Facelets... tiene sentido?
jsf-2 (3)
Introducción
Las etiquetas JSTL <c:xxx>
son todas taghandlers etiquetas y se ejecutan durante el tiempo de compilación de la vista , mientras que las etiquetas JSF <h:xxx>
son todos componentes de la IU y se ejecutan durante el tiempo de visualización de la vista .
Tenga en cuenta que desde las propias etiquetas <f:xxx>
y <ui:xxx>
de JSF solo aquellas que no se extienden desde UIComponent
son también manejadores de etiquetas, por ejemplo <f:validator>
, <ui:include>
, <ui:define>
, etc. Los que se extienden desde UIComponent
también son componentes JSF UI, por ejemplo <f:param>
, <ui:fragment>
, <ui:repeat>
, etc. Desde los componentes JSF UI, solo los atributos id
y binding
también se evalúan durante el tiempo de compilación view . Por lo tanto, la respuesta a continuación en cuanto al ciclo de vida JSTL también se aplica a los atributos de id
y binding
de 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 como UIViewRoot
de FacesContext
. El tiempo de visualización de vista es el momento en que el árbol de componentes JSF está a punto de generar HTML, comenzando con UIViewRoot#encodeAll()
. Entonces: los componentes JSF UI y las etiquetas JSTL no se ejecutan sincronizados como cabría esperar de la codificación. Puede visualizarlo de la siguiente manera: JSTL se ejecuta de arriba a abajo primero, 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 Facele itera 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 vista tres componentes <h:outputText>
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 de HTML durante el tiempo de visualización de la vista:
<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 exclusividad de los ID de los componentes y que también se evalúan durante el tiempo de compilación de la vista.
Mientras que este marcado de Facele itera sobre 3 elementos usando <ui:repeat>
, que es un componente de UI JSF:
<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 el cual el mismo componente <h:outputText>
se ve reutilizado durante la visualización del tiempo de renderizado para generar un resultado HTML basado 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 <ui:repeat>
como componente de NamingContainer
ya garantiza la exclusividad de la ID del cliente en función del índice de iteración; tampoco es posible usar EL en el atributo id
de los componentes secundarios de esta manera, ya que también se evalúa durante el tiempo de compilación de la vista, mientras que #{item}
solo está disponible durante el tiempo de visualización de la vista. Lo mismo es cierto para una h:dataTable
y componentes similares.
<c:if>
/ <c:choose>
vs rendered
Como otro ejemplo, este marcado de Facelets agrega de manera condicional etiquetas diferentes 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 el caso de type = TEXT
solo agregará el componente <h:inputText>
al árbol de componentes JSF:
<h:inputText ... />
Mientras que 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 arriba en el árbol de componentes JSF, independientemente de la condición. 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, el field
no cambia durante al menos el alcance de la vista). Además, puede encontrarse con un trouble EL cuando maneja subclases con propiedades adicionales en las versiones de Mojarra anteriores a la 2.2.7.
<c:set>
vs <ui:param>
No son intercambiables. The <c:set>
establece una variable en el ámbito EL, al que solo se puede acceder después de la ubicación de la etiqueta durante el tiempo de construcción de la vista, pero en cualquier parte de la vista durante el tiempo de visualización de la vista. El <ui:param>
pasa una variable EL a una plantilla de Facelet incluida 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 variable <ui:param>
también está disponible fuera de la plantilla de Facelet en cuestión, nunca se debe confiar en esto.
El <c:set>
sin un atributo de scope
se comportará como un alias. No almacena en caché el resultado de la expresión EL en ningún ámbito. Por lo tanto, puede perfectamente usarse dentro de, por ejemplo, iterar 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>
No es adecuado, por ejemplo, para calcular la suma en un bucle. Para eso, en su lugar, use la secuencia 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 cuando establezca el atributo de scope
con una de las request
valores permitidos, view
, session
o application
, se evaluará inmediatamente durante el tiempo de construcción de la vista y se almacenará en el alcance especificado.
<c:set var="dev" value="#{facesContext.application.projectStage eq ''Development''}" scope="application" />
Esto se evaluará solo una vez y estará disponible como #{dev}
toda la aplicación.
Use JSTL para controlar la construcción de árbol de componentes JSF
El uso de JSTL solo puede generar resultados inesperados cuando se utiliza dentro de componentes <h:dataTable>
JSF, como <h:dataTable>
, <ui:repeat>
, etc., o cuando los atributos de la etiqueta JSTL dependen de los resultados de los eventos JSF como preRenderView
o los valores de formularios enviados en el modelo que no está disponible durante el tiempo de compilación de vista. Por lo tanto, use etiquetas JSTL solo para controlar el flujo de creación de árbol de componentes JSF. Use los componentes de la interfaz de usuario JSF para controlar el flujo de generación de salida HTML. No vincule la var
de iteración de componentes JSF a atributos de etiqueta JSTL. No confíe en los eventos JSF en los atributos de etiqueta JSTL.
Cada vez que crea que necesita vincular un componente al bean de respaldo a través del binding
, o agarrar uno a través de findComponent()
, y crear / manipular sus hijos usando código Java en un bean de respaldo con un new SomeComponent()
y lo que no, entonces debe inmediatamente detente y considera usar JSTL en su lugar. Como JSTL también está basado en XML, el código necesario para crear componentes JSF de forma dinámica será mucho más legible y mantenible.
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. Toda la vista de ámbito de bean se recrearía nuevamente en lugar de recuperarse del árbol de vista (simplemente porque el árbol de vista completo aún no está disponible en el punto donde se ejecuta JSTL). Si está esperando o almacenando algún estado en la vista del bean con ámbito por un atributo de etiqueta JSTL, entonces 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 árbol está construido. En caso de que no pueda actualizar a Mojarra 2.1.18 o posterior, lo que resta es desactivar el ahorro de estado parcial en web.xml
como se muestra a continuación:
<context-param>
<param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
<param-value>false</param-value>
</context-param>
Ver también:
- ¿Cuál es el tiempo de construcción de la vista?
- ¿Cómo funciona el atributo ''vinculante'' en JSF? ¿Cuándo y cómo se debe usar?
- ¿Cómo refactorizar fragmento de JSP antiguo a un equivalente de JSF?
- ¿Debería PARTIAL_STATE_SAVING establecerse en falso?
- Comunicación en JSF 2.0 -
@ViewScoped
falla en los controladores de etiquetas
Para ver algunos ejemplos del mundo real en los que las etiquetas JSTL son útiles (es decir, cuando se utilizan de forma correcta durante la construcción de la vista), consulte las siguientes preguntas / respuestas:
- ¿Cómo hacer una grilla del componente compuesto JSF?
- Crear columnas de tabla dinámicamente en JSF
- Cómo personalizar el diseño h: selectOneRadio
- Definición de variable condicional en JSF
- Cómo hacer un componente compuesto similar a <h: selectOneRadio />
- JSF 2 - Componente compuesto con atributo de escucha opcional en f: ajax
- Componentes compuestos JSF anidados que conducen a una excepción de desbordamiento de pila
En una palabra
En cuanto a su requisito funcional concreto, si desea representar componentes JSF condicionalmente, utilice el atributo rendered
en el componente HTML JSF, particularmente si #{lpc}
representa el elemento iterado actualmente de un componente <h:dataTable>
JSF como <h:dataTable>
o <ui:repeat>
.
<h:someComponent rendered="#{lpc.verbose}">
...
</h:someComponent>
O bien, si desea construir (crear / agregar) componentes JSF condicionalmente, entonces continúe usando JSTL. Es mucho mejor que verbosely haciendo un new SomeComponent()
en java.
<c:if test="#{lpc.verbose}">
<h:someComponent>
...
</h:someComponent>
</c:if>
Ver también:
Me gustaría dar salida un poco de código de Facelets condicionalmente.
Para ese propósito, las etiquetas JSTL parecen funcionar bien:
<c:if test="${lpc.verbose}">
...
</c:if>
Sin embargo, no estoy seguro si esta es una mejor práctica? ¿Hay alguna otra manera de lograr mi objetivo?
Perdón por la respuesta por separado, pero no pude comentar las respuestas anteriores.
Para la salida tipo interruptor, puede usar el interruptor de las primefaces-extensions .
utilizar
<h:panelGroup rendered="#{lpc.verbose}">
...
</h:panelGroup>