ChatGPT解决这个技术问题 Extra ChatGPT

JSF2 Facelets 中的 JSTL...有意义吗?

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

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

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

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


K
Kukeltje

介绍

JSTL <c:xxx> 标签都是 taghandlers,它们在 view build time 期间执行,而 JSF <h:xxx> 标签都是 UI components,它们在 view render time 期间执行强>。

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

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

例如,这个 Facelets 标记使用 <c:forEach> 迭代 3 个项目:

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

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

<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 标记使用 <ui:repeat>(它是一个 JSF UI 组件)迭代 3 个项目:

<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 和类似组件也是如此。

/ 与渲染

作为另一个示例,此 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 trouble

它们不可互换。 <c:set> 在 EL 范围内设置了一个变量,该变量只能在视图构建期间标记位置之后访问,但在视图呈现期间在视图中的任何位置都可以访问。 <ui:param> 将 EL 变量传递给通过 <ui:include><ui:decorate template><ui:composition template> 包含的 Facelet 模板。较旧的 JSF 版本存在缺陷,即 <ui:param> 变量在所讨论的 Facelet 模板之外也可用,永远不应依赖这一点。

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

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

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

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

使用JSTL控制JSF组件树构建

使用 JSTL 可能只会在 <h:dataTable><ui:repeat> 等 JSF 迭代组件内部使用时,或者当 JSTL 标签属性依赖于 preRenderView 等 JSF 事件的结果或模型中提交的表单值时,可能会导致意外结果在视图构建期间不可用。因此,仅使用 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>

也可以看看:

视图构建时间是多少?

JSF 中的“绑定”属性如何工作?何时以及如何使用它?

如何将旧 JSP 的片段重构为一些等效的 JSF?

是否应该将 PARTIAL_STATE_SAVING 设置为 false?

JSF 2.0 中的通信 - @ViewScoped 在标记处理程序中失败

要查看 JSTL 标记有用的一些真实世界示例(即在构建视图期间真正正确使用时),请参阅以下问题/答案:

如何制作 JSF 复合组件的网格?

在 JSF 中动态创建表列

如何自定义布局 h:selectOneRadio

JSF 中的条件变量定义

如何制作类似于 的复合组件

JSF 2——在 f:ajax 上具有可选监听器属性的复合组件

导致堆栈溢出异常的嵌套 JSF 复合组件

简而言之

至于您的具体功能需求,如果您想有条件地渲染 JSF 组件,请使用 JSF HTML 组件上的 rendered 属性,尤其是如果 #{lpc} 表示JSF 迭代组件的当前迭代项,例如 <h:dataTable><ui:repeat>

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

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

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

也可以看看:

有条件地显示 JSF 组件

JSTL c:if 在 JSF h:dataTable 中不起作用

中指定元素的条件渲染? 似乎不起作用


@Aklin:不是吗? this example 怎么样?
我很长一段时间都无法正确解释第一段(虽然给出的例子非常清楚)。因此,我将这条评论作为唯一的方法。通过该段,我的印象是 <ui:repeat> 是一个标签处理程序(因为这行,“注意 JSF 自己的 <f:xxx><ui:xxx>...”)就像 { 4},因此,它在查看构建时进行评估(再次就像<c:forEach>一样)。如果是这样,那么 <ui:repeat><c:forEach> 之间不应该有任何可见的功能差异吗?我不明白那段到底是什么意思:)
对不起,我不会再污染这篇文章了。我注意到了您之前的评论,但我没有注意到这句话,“请注意,JSF 自己的 <f:xxx><ui:xxx> 不扩展 UIComponent 的标签也是标签处理程序。”试图暗示 <ui:repeat> 也是一个标签处理程序,因为 <ui:xxx> 还包括 <ui:repeat>?这应该意味着 <ui:repeat><ui:xxx> 中扩展 UIComponent 的组件之一。因此,它不是标签处理程序。 (其中一些可能不会扩展 UIComponent。因此,它们是标签处理程序)是吗?
@Shirgill:没有 scope<c:set> 创建 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
M
Martijn Pieters

利用

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

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

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


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅