【问题标题】:JSTL in JSF2 Facelets... makes sense?JSF2 Facelets 中的 JSTL ... 有意义吗?
【发布时间】:2016-11-24 21:04:42
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: jsf jsf-2 jstl facelets


    【解决方案1】:

    使用

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

    【讨论】:

    • 谢谢,很好的答案。更笼统地说:JSTL 标记是否仍然有意义,或者我们是否应该将它们视为自 JSF 2.0 以来已弃用?
    • 在大多数情况下,是的。但有时使用它们是合适的
    • 使用 h:panelGroup 是一个肮脏的解决方案,因为它会生成一个 标签,而 c:if 不会向 html 代码添加任何内容。 h:panelGroup 在 panelGrids 中也存在问题,因为它对元素进行了分组。
    【解决方案2】:

    简介

    JSTL&lt;c:xxx&gt;标签都是taghandlers,在view build time执行,而JSF&lt;h:xxx&gt;标签都是UI components,在view render执行时间

    请注意,从 JSF 自己的 &lt;f:xxx&gt;&lt;ui:xxx&gt; 标签中,只有那些 notUIComponent 扩展的标签也是标签处理程序,例如&lt;f:validator&gt;&lt;ui:include&gt;&lt;ui:define&gt; 等。从 UIComponent 扩展而来的也是 JSF UI 组件,例如&lt;f:param&gt;&lt;ui:fragment&gt;&lt;ui:repeat&gt; 等。从 JSF UI 组件中,只有 idbinding 属性也在视图构建时进行评估。因此,以下关于 JSTL 生命周期的答案也适用于 JSF 组件的 idbinding 属性。

    视图构建时间是 XHTML/JSP 文件被解析并转换为 JSF 组件树的时刻,然后将其存储为 FacesContextUIViewRoot。视图渲染时间是 JSF 组件树即将生成 HTML 的时刻,从 UIViewRoot#encodeAll() 开始。所以:JSF UI 组件和 JSTL 标记不会像您对编码所期望的那样同步运行。您可以将其可视化如下:JSTL首先从上到下运行,生成JSF组件树,然后轮到JSF再次从上到下运行,生成HTML输出。

    &lt;c:forEach&gt;&lt;ui:repeat&gt;

    例如,这个 Facelets 标记使用 &lt;c:forEach&gt; 迭代 3 个项目:

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

    ...在视图构建期间在 JSF 组件树中创建三个独立的 &lt;h:outputText&gt; 组件,大致表示如下:

    <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 的唯一性,并且这些 ID 在视图构建时也会被评估。

    虽然这个 Facelets 标记使用 &lt;ui:repeat&gt;(这是一个 JSF UI 组件)迭代了 3 个项目:

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

    ...已经在 J​​SF 组件树中按原样结束,其中相同的 &lt;h:outputText&gt; 组件在视图渲染期间被重用以根据当前迭代轮生成 HTML 输出:

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

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

    &lt;c:if&gt;/&lt;c:choose&gt;rendered

    作为另一个例子,这个 Facelets 标记使用&lt;c:if&gt; 有条件地添加不同的标签(您也可以为此使用&lt;c:choose&gt;&lt;c:when&gt;&lt;c:otherwise&gt;):

    <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,只会将&lt;h:inputText&gt; 组件添加到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 trouble

    &lt;c:set&gt;&lt;ui:param&gt;

    它们不可互换。 &lt;c:set&gt; 在 EL 范围内设置了一个变量,该变量只能在视图构建期间标记位置之后访问,但在视图渲染期间可以在视图中的任何位置访问。 &lt;ui:param&gt; 将 EL 变量传递给通过 &lt;ui:include&gt;&lt;ui:decorate template&gt;&lt;ui:composition template&gt; 包含的 Facelet 模板。较旧的 JSF 版本存在错误,即 &lt;ui:param&gt; 变量在相关的 Facelet 模板之外也可用,永远不应依赖这一点。

    没有scope 属性的&lt;c:set&gt; 的行为类似于别名。它不会在任何范围内缓存 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 stream:

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

    仅当您将scope 属性设置为允许值requestviewsessionapplication 之一时,它将在视图构建期间立即评估并存储在指定的范围。

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

    这将只评估一次,并在整个应用程序中以#{dev} 的形式提供。

    使用JSTL控制JSF组件树构建

    &lt;h:dataTable&gt;&lt;ui:repeat&gt;等JSF迭代组件内部使用JSTL,或者当JSTL标签属性依赖于preRenderView等JSF事件的结果或提交的表单值时,使用JSTL可能只会导致意想不到的结果在视图构建期间不可用的模型中。因此,仅使用 JSTL 标签来控制 JSF 组件树构建的流程。使用 JSF UI 组件来控制 HTML 输出生成的流程。不要将迭代 JSF 组件的 var 绑定到 JSTL 标记属性。不要依赖 JSTL 标记属性中的 JSF 事件。

    任何时候你认为你需要通过binding 将一个组件绑定到支持bean,或者通过findComponent() 获取一个组件,并使用new SomeComponent() 的支持bean 中的Java 代码创建/操作它的子组件等等,那么您应该立即停止并考虑改用 JSTL。由于 JSTL 也是基于 XML 的,因此动态创建 JSF 组件所需的代码将变得更好的可读性和可维护性。

    重要的是要知道,早于 2.1.18 的 Mojarra 版本在引用 JSTL 标记属性中的视图范围 bean 时存在部分状态保存错误。整个视图范围的 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 组件,请改用JSF HTML 组件上的rendered 属性,尤其是 if @987654419 @ 表示 JSF 迭代组件的当前迭代项,例如 &lt;h:dataTable&gt;&lt;ui:repeat&gt;

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

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

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

    另见:

    【讨论】:

    • @Aklin:不是吗? this example 怎么样?
    • 我很长一段时间都无法正确解释第一段(虽然给出的例子很清楚)。因此,我将这条评论作为唯一的方法。通过那段,我的印象是 &lt;ui:repeat&gt; 是一个标签处理程序(因为这行,“注意 JSF 自己的 &lt;f:xxx&gt;&lt;ui:xxx&gt;...”)就像 @ 987654428@,因此,它在查看构建时间进行评估(再次就像&lt;c:forEach&gt;)。如果是这样,那么&lt;ui:repeat&gt;&lt;c:forEach&gt; 之间不应该有任何可见的功能差异吗?我不明白那段到底是什么意思:)
    • 对不起,我不会再污染这篇文章了。我注意到了您之前的评论,但我没有注意到这句话,“请注意,JSF 自己的 &lt;f:xxx&gt;&lt;ui:xxx&gt; 不扩展 UIComponent 的标签也是标签处理程序。”试图暗示&lt;ui:repeat&gt; 也是一个标签处理程序,因为&lt;ui:xxx&gt; 还包括&lt;ui:repeat&gt;?这应该意味着&lt;ui:repeat&gt;&lt;ui:xxx&gt; 中扩展UIComponent 的组件之一。因此,它不是标签处理程序。 (其中一些可能不会扩展UIComponent。因此,它们是标签处理程序)是吗?
    • @Shirgill: &lt;c:set&gt; 没有 scope 会创建 EL 表达式的别名,而不是在目标范围内设置评估值。请尝试scope="request",它会立即评估该值(确实在视图构建期间)并将其设置为请求属性(在迭代期间不会被“覆盖”)。在幕后,它创建并设置了一个 ValueExpression 对象。
    • @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
    【解决方案3】:

    对于类似开关的输出,您可以使用 PrimeFaces Extensions 中的switch 面。

    【讨论】:

      猜你喜欢
      • 2013-10-02
      • 2011-09-15
      • 2011-04-25
      • 2011-02-08
      相关资源
      最近更新 更多