JSF2 Facelets中的JSTL有意义吗?


163

我想有条件地输出一些Facelets代码。

为此,JSTL标记似乎可以正常工作:

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

但是,我不确定这是否是最佳做法?还有另一种方法可以实现我的目标吗?

Answers:


320

介绍

JSTL <c:xxx>标签都是标记处理程序,它们在视图构建时执行,而JSF <h:xxx>标签都是UI组件,它们在视图渲染时执行。

请注意,从JSF自己<f:xxx><ui:xxx>标签只有那些根本没有从延长UIComponent也taghandlers,例如<f:validator><ui:include><ui:define>等从延长的那些UIComponent也JSF UI组件,例如<f:param><ui:fragment><ui:repeat>等从JSF UI组件只idbinding属性在视图构建期间也进行了评估。因此,以下有关JSTL生命周期的答案也适用于JSF组件的idbinding属性。

该视图生成时间是当XHTML / JSP文件是被解析并转化成一个JSF组件树,然后将其存储为那一刻UIViewRootFacesContext。视图渲染时间是指从JSF组件树将要生成HTML的那一刻起UIViewRoot#encodeAll()。因此:JSF UI组件和JSTL标记不会像您期望的那样同步运行。您可以将其可视化如下:JSTL首先从上到下运行,生成JSF组件树,然后轮到JSF再次从上到下运行,生成HTML输出。

<c:forEach><ui:repeat>

例如,此Facelets标记使用<c:forEach>以下代码迭代了3个项目:

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

...在视图构建期间<h:outputText>,在JSF组件树中创建三个单独的组件,大致表示如下:

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

...依次在视图渲染期间分别生成其HTML输出:

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

请注意,您需要手动确保组件ID的唯一性,并且还在视图构建期间对其进行评估。

尽管此Facelets标记使用来迭代3个项目<ui:repeat>,这是JSF UI组件:

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

...已经在JSF组件树中按原样结束,由此,基于当前迭代回合,<h:outputText>可以在视图渲染期间重用相同的组件来生成HTML输出:

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

请注意,<ui:repeat>作为NamingContainer组件已基于迭代索引确保了客户端ID的唯一性;也不能以id这种方式在子组件的属性中使用EL,因为它也在视图构建期间进行评估,而#{item}仅在视图渲染期间可用。h:dataTable和相似的组件也是如此。

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

再举一个例子,这个Facelets标记有条件地使用添加不同的标签<c:if>(您也可以使用<c:choose><c:when><c:otherwise>此标签):

<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 = TEXT仅在将<h:inputText>组件添加到JSF组件树的情况下:

<h:inputText ... />

在此Facelets标记中:

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

...无论情况如何,最终将完全按照上述方法在JSF组件树中结束。因此,当您拥有许多组件树并且它们实际上是基于“静态”模型时(即field,至少在视图范围内,它永远不会发生变化),最终可能会变成“膨胀”的组件树。另外,在2.2.7之前的Mojarra版本中处理带有附加属性的子类时,可能会遇到EL 麻烦

<c:set><ui:param>

它们不可互换。该<c:set>套在EL作用域的变量,只可以访问在视图生成时的标签位置,但在任何地方视图中查看渲染时间。所述<ui:param>传递一个EL变量为一个facelet模板通过包括<ui:include><ui:decorate template>,或<ui:composition template>。较早的JSF版本存在一些错误,因此该<ui:param>变量在有关Facelet模板之外也可以使用,因此永远不应依赖该变量。

<c:set>scope属性将表现得像一个别名。它不会在任何范围内缓存EL表达式的结果。因此,它可以完美地用于内部,例如迭代JSF组件。因此,例如下面将正常工作:

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

它仅不适用于例如在循环中计算总和。为此,请使用EL 3.0流

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

只是,当你设置scope有允许值的一个属性requestviewsession,或application,那么它会立即在视图生成时评估,并存储在指定的范围内。

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

这将只评估一次,并且#{dev}在整个应用程序中都可用。

使用JSTL控制JSF组件树的构建

使用JSTL JSF迭代组分如内部使用时可以仅导致意外的结果<h:dataTable><ui:repeat>等等,或者当JSTL标记属性取决于JSF事件的结果,例如preRenderView或提交在模型中它们是不期间视图生成时可用的形式的值。因此,仅使用JSTL标记来控制JSF组件树构建的流程。使用JSF UI组件来控制HTML输出生成的流程。不要将varJSF迭代组件绑定到JSTL标记属性。不要依赖JSTL标记属性中的JSF事件。

每当您认为需要通过binding或通过抓取一个组件将组件绑定到backing bean上,或者使用或不findComponent()使用backing bean在Java Bean中创建/操纵其子组件时new SomeComponent(),都应该立即停止并考虑使用JSTL。由于JSTL也是基于XML的,因此动态创建JSF组件所需的代码将变得更加易于阅读和维护。

重要的是要知道,当在JSTL标签属性中引用视图作用域的bean时,Mojarra的2.1.18版之前的版本存在部分状态保存错误。作用域bean整个视图将被新的重建,而不是从视图树检索(仅仅是因为完整视图树还没有提供在点JSTL运行)。如果您希望通过JSTL标签属性在视图范围内的bean中存储某些状态,那么它将不会返回您期望的值,否则它将在实际视图范围内的bean中“丢失”,并在视图后还原树被建立。如果您无法升级到Mojarra 2.1.18或更高版本,解决方法是关闭部分状态保存,web.xml如下所示:

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

也可以看看:

要查看一些实际的示例,其中的JSTL标签很有用(例如,在构建视图时真正使用了该标签),请参见以下问题/解答:


简而言之

关于您的具体功能需求,如果要有条件地呈现 JSF组件,请改用renderedJSF HTML组件上的属性,特别是如果#{lpc}表示JSF迭代组件的当前迭代项,例如<h:dataTable><ui:repeat>

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

或者,如果您想有条件地构建(创建/添加)JSF组件,请继续使用JSTL。这比new SomeComponent()在Java中冗长地做要好得多。

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

也可以看看:


3
@Aklin:不?怎么样这个例子
BalusC 2012年

1
我不能长时间正确地解释第一段(尽管给出的例子很清楚)。因此,我只留下这一评论。在该段中,我的印象<ui:repeat>是就像是一个标记处理程序(由于这一行,“ 请注意,JSF自己的<f:xxx><ui:xxx>... ”)就像<c:forEach>因此,它是在视图构建时进行评估的(再次就像一样<c:forEach>) 。如果是的话,不应该有之间的任何可见的,功能上的差异<ui:repeat><c:forEach>?我不明白那一段到底是什么意思:)
Tiny 2014年

1
抱歉,我不会再对此帖子造成任何污染。我把您先前的评论引起了我的注意,但没有写这句话,“ 请注意,JSF自己的<f:xxx><ui:xxx>未扩展UIComponent的标签也是标签处理程序。 ”试图暗示这<ui:repeat>也是一个标签处理程序,因为<ui:xxx>还包括<ui:repeat>?然后,这应该意味着这<ui:repeat>是其中<ui:xxx>扩展的组成部分之一UIComponent。因此,它不是标签处理程序。(其中一些可能不会扩展UIComponent。因此,它们是标记处理程序)是吗?
微小

2
@Shirgill:<c:set>scope创建EL表达式的别名,而不是在目标范围内设置评估值。尝试一下scope="request",它将立即评估值(实际上是在视图构建期间),并将其设置为请求属性(在迭代过程中不会“覆盖”)。在幕后,它创建并设置了一个ValueExpression对象。
BalusC

1
@ K.Nicholas:在一个掩护下ClassNotFoundException。项目的运行时依赖关系已损坏。您很可能使用的是非JavaEE服务器(例如Tomcat),却忘记了安装JSTL,或者不小心同时包含了JSTL 1.0和JSTL 1.1+。因为在JSTL 1.0中,软件包是javax.servlet.jstl.core.*,从JSTL 1.1开始,它已成为javax.servlet.jsp.jstl.core.*。可在此处找到安装JSTL的线索:stackoverflow.com/a/4928309
BalusC,

13

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

谢谢,好答案。更笼统地说:自JSF 2.0以来,JSTL标记仍然有意义吗?还是应该将其视为已弃用?
1

在大多数情况下,是的。但是有时候使用它们是适当的
Bozho 2010年

3
使用h:panelGroup是一个肮脏的解决方案,因为它会生成一个<span>标记,而c:if则不会向html代码添加任何内容。h:panelGroup在panelGrids内部也是有问题的,因为它对元素进行了分组。
Rober2D2 '18年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.