JSTL no JSF2 Facelets… faz sentido?

163

Gostaria de emitir um pouco do código Facelets condicionalmente.

Para esse propósito, as tags JSTL parecem funcionar bem:

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

No entanto, não tenho certeza se essa é uma prática recomendada? Existe outra maneira de alcançar meu objetivo?

Jan
fonte

Respostas:

320

Introdução

As <c:xxx>tags JSTL são todos manipuladores de tags e são executadas durante o tempo de construção da exibição , enquanto as <h:xxx>tags JSF são todos componentes da UI e são executadas durante o tempo de renderização da exibição .

Note-se que a partir do próprio JSF <f:xxx>e <ui:xxx>etiquetas apenas aqueles que não não se estendem desde UIComponentsão também taghandlers, por exemplo <f:validator>, <ui:include>, <ui:define>, etc. Os que se estendem desde UIComponenttambém são componentes JSF UI, por exemplo <f:param>, <ui:fragment>, <ui:repeat>, etc. A partir componentes JSF UI apenas o ide bindingatributos são também avaliado durante o tempo de construção da visualização. Portanto, a resposta abaixo sobre o ciclo de vida JSTL também se aplica aos atributos ide bindingdos componentes JSF.

O tempo de construção da visualização é o momento em que o arquivo XHTML / JSP deve ser analisado e convertido em uma árvore de componentes JSF, que é então armazenada a partir UIViewRootdo arquivo FacesContext. O tempo de renderização da visualização é o momento em que a árvore do componente JSF está prestes a gerar HTML, começando com UIViewRoot#encodeAll(). Portanto, os componentes da interface do usuário JSF e as tags JSTL não são executados em sincronia como seria de esperar da codificação. Você pode visualizá-lo da seguinte forma: O JSTL é executado de cima para baixo primeiro, produzindo a árvore de componentes JSF, então é a vez do JSF executar de cima para baixo novamente, produzindo a saída HTML.

<c:forEach> vs <ui:repeat>

Por exemplo, essa marcação Facelets itera mais de 3 itens usando <c:forEach>:

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

... cria durante o tempo de construção da visualização três <h:outputText>componentes separados na árvore de componentes JSF, representados aproximadamente assim:

<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, por sua vez, geram individualmente sua saída HTML durante o tempo de exibição:

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

Observe que você precisa garantir manualmente a exclusividade dos IDs do componente e que eles também sejam avaliados durante o tempo de construção da exibição.

Enquanto essa marcação do Facelets itera mais de 3 itens <ui:repeat>, o que é um componente da interface do usuário JSF:

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

... já acaba como está na árvore de componentes JSF, na qual o mesmo <h:outputText>componente é durante o tempo de renderização da exibição sendo reutilizado para gerar saída HTML com base na rodada de iteração atual:

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

Note que o <ui:repeat>como sendo um NamingContainercomponente já assegurada a exclusividade do ID do cliente com base no índice de iteração; também não é possível usar o EL no idatributo de componentes filho dessa maneira, pois ele também é avaliado durante o tempo de criação da exibição, enquanto #{item}só está disponível durante o tempo de renderização da exibição. O mesmo vale para h:dataTablecomponentes e similares.

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

Como outro exemplo, essa marcação do Facelets adiciona condicionalmente diferentes tags usando <c:if>(você também pode usar <c:choose><c:when><c:otherwise>para isso):

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

... no caso de type = TEXTapenas adicionar o <h:inputText>componente à árvore de componentes JSF:

<h:inputText ... />

Enquanto esta marcação Facelets:

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

... terminará exatamente como acima na árvore de componentes JSF, independentemente da condição. Assim, isso pode acabar em uma árvore de componentes "inchada" quando você tem muitos deles e eles são realmente baseados em um modelo "estático" (ou seja field, nunca muda durante pelo menos o escopo da visualização). Além disso, você pode ter problemas no EL quando lida com subclasses com propriedades adicionais nas versões Mojarra anteriores à 2.2.7.

<c:set> vs <ui:param>

Eles não são intercambiáveis. Ele <c:set>define uma variável no escopo EL, que é acessível somente após o local da tag durante o tempo de criação da exibição, mas em qualquer lugar da exibição durante o tempo de exibição. O <ui:param>passa uma variável de SR para um molde Facelet incluído via <ui:include>, <ui:decorate template>, ou <ui:composition template>. As versões mais antigas do JSF tinham bugs, pelo que a <ui:param>variável também está disponível fora do modelo do Facelet em questão, isso nunca deve ser considerado.

O <c:set>sem um scopeatributo se comportará como um alias. Ele não armazena em cache o resultado da expressão EL em nenhum escopo. Portanto, ele pode ser perfeitamente bem usado no interior, por exemplo, na iteração de componentes JSF. Assim, por exemplo, abaixo funcionará bem:

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

Só não é adequado para, por exemplo, calcular a soma em um loop. Para isso, use o fluxo do EL 3.0 :

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

Só que, quando você definir o scopeatributo com um dos valores permitidos request, view, session, ou application, então ele vai ser avaliado imediatamente durante o tempo de vista de construção e armazenados no escopo especificado.

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

Isso será avaliado apenas uma vez e estará disponível em #{dev}todo o aplicativo.

Use JSTL para controlar a construção da árvore de componentes JSF

O uso de JSTL pode levar apenas a resultados inesperados ao ser usado dentro de componentes de iteração JSF, como <h:dataTable>, <ui:repeat>etc, ou quando os atributos de tag JSTL dependem dos resultados de eventos JSF, como preRenderViewvalores de formulário enviados no modelo, que não estão disponíveis durante o tempo de construção da exibição . Portanto, use tags JSTL apenas para controlar o fluxo da construção da árvore de componentes JSF. Use os componentes da interface do usuário JSF para controlar o fluxo da geração de saída HTML. Não ligue os varcomponentes JSF de iteração aos atributos de tag JSTL. Não confie nos eventos JSF nos atributos de tag JSTL.

Sempre que achar necessário vincular um componente ao bean de backup via binding, ou pegar um via findComponent(), e criar / manipular seus filhos usando código Java em um bean de backup com o new SomeComponent()que não, você deve parar imediatamente e considerar o uso de JSTL. Como o JSTL também é baseado em XML, o código necessário para criar componentes JSF dinamicamente se tornará muito melhor legível e sustentável.

É importante saber que as versões Mojarra anteriores a 2.1.18 tiveram um erro no salvamento parcial do estado ao referenciar um bean com escopo de exibição em um atributo de tag JSTL. Todo o bean com escopo da visualização seria recriado novamente em vez de recuperado da árvore de visualização (simplesmente porque a árvore de visualização completa ainda não está disponível no momento em que o JSTL é executado). Se você estiver esperando ou armazenando algum estado no bean com escopo definido na exibição por um atributo de tag JSTL, ele não retornará o valor esperado ou será "perdido" no bean com escopo real que será restaurado após a exibição árvore é construída. Caso você não possa atualizar para o Mojarra 2.1.18 ou mais recente, a solução é desativar o salvamento parcial do estado, web.xmlcomo abaixo:

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

Veja também:

Para ver alguns exemplos do mundo real em que as tags JSTL são úteis (ou seja, quando realmente usadas corretamente durante a construção da exibição), consulte as seguintes perguntas / respostas:


Em poucas palavras

Quanto ao seu requisito funcional concreto, se você deseja renderizar componentes JSF condicionalmente, use o renderedatributo no componente HTML JSF, principalmente se #{lpc}representa o item iterado no momento de um componente iterativo JSF como <h:dataTable>ou <ui:repeat>.

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

Ou, se você deseja construir (criar / adicionar) componentes JSF condicionalmente, continue usando JSTL. É muito melhor do que fazer verbalmente new SomeComponent()em java.

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

Veja também:

BalusC
fonte
3
@Aklin: Não? Que tal esse exemplo ?
BalusC
1
Não consigo interpretar o primeiro parágrafo adequadamente por um longo tempo (os exemplos dados são muito claros). Portanto, estou deixando este comentário como o único caminho. Por esse parágrafo, tenho a impressão de que <ui:repeat>é um manipulador de tags (por causa dessa linha, " Observe que o JSF é próprio <f:xxx>e <ui:xxx>... "), assim como <c:forEach>, portanto, ele é avaliado no tempo de construção da exibição (novamente como exatamente <c:forEach>) . Se é assim, então, não deve haver nenhuma diferença funcional visível entre <ui:repeat>e <c:forEach>? Eu não entendo exatamente o que esse parágrafo significa :) #
04414 minúsculo
1
Desculpe, não vou poluir mais este post. Eu levei o seu comentário anterior em minha atenção, mas essa frase não " Observe que o próprio JSF <f:xxx>e as <ui:xxx>tags que não se estendem UIComponenttambém são manipuladores de tags " . Tenta sugerir que <ui:repeat>também é um manipulador de tags porque <ui:xxx>também inclui <ui:repeat>? Isso deve significar que esse <ui:repeat>é um dos componentes <ui:xxx>que se estende UIComponent. Portanto, não é um manipulador de tags. (Alguns deles podem não se estender UIComponent. Portanto, eles são manipuladores de tags) Sim?
pequeno
2
@ Shirgill: <c:set>sem scopecria um alias da expressão EL em vez de definir o valor avaliado no escopo de destino. Tente scope="request", em vez disso, avaliar imediatamente o valor (durante o tempo de construção da exibição) e defini-lo como atributo de solicitação (que não será "substituído" durante a iteração). Sob as cobertas, ele cria e define um ValueExpressionobjeto.
BalusC
1
@ K.Nicholas: Está embaixo das cobertas a ClassNotFoundException. As dependências de tempo de execução do seu projeto estão quebradas. Provavelmente, você está usando um servidor não JavaEE, como o Tomcat, e esqueceu de instalar o JSTL, ou você incluiu acidentalmente o JSTL 1.0 e o JSTL 1.1+. Como no JSTL 1.0 o pacote é javax.servlet.jstl.core.*e, desde o JSTL 1.1, isso se tornou javax.servlet.jsp.jstl.core.*. Dicas para instalar o JSTL podem ser encontradas aqui: stackoverflow.com/a/4928309
BalusC
13

usar

<h:panelGroup rendered="#{lpc.verbose}">
  ...
</h:panelGroup>
Bozho
fonte
Thx, ótima resposta. Mais em geral: as tags JSTL ainda fazem sentido ou devemos considerá-las obsoletas desde o JSF 2.0?
Jan
Na maioria dos casos, sim. Mas, às vezes, é apropriado usá-los #
Bozho 27/07/10
3
O uso de h: panelGroup é uma solução suja, porque gera uma tag <span>, enquanto c: if não adiciona nada ao código html. h: panelGroup também é problemático dentro do panelGrids, pois agrupa os elementos.
precisa saber é o seguinte
4

Para saída switch-like, você pode usar o interruptor rosto Primefaces Extensions.

Ravshan Samandarov
fonte