JSF2 Facelets 中的 JSTL ......有意义吗? [英] JSTL in JSF2 Facelets... makes sense?

查看:15
本文介绍了JSF2 Facelets 中的 JSTL ......有意义吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I would like to output a bit of Facelets code conditionally.

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

For that purpose, the JSTL tags seem to work fine:

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

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

However, I'm not sure if this is a best practice? Is there another way to achieve my goal?

推荐答案

简介

JSTL 标签都是 taghandlers 并且它们在视图构建时间期间执行,而 JSF 标签都是 UI 组件,它们在视图期间执行渲染时间.

Introduction

JSTL <c:xxx> tags are all taghandlers and they are executed during view build time, while JSF <h:xxx> tags are all UI components and they are executed during view render time.

请注意,从 JSF 自己的 标签中,只有那些 not 扩展自UIComponent 是还有标签处理程序,例如 等.code>UIComponent 也是 JSF UI 组件,例如 等.仅来自 JSF UI 组件的idbinding 属性也在视图构建期间进行评估.因此,以下关于 JSTL 生命周期的答案也适用于 JSF 组件的 idbinding 属性.

Note that from JSF's own <f:xxx> and <ui:xxx> tags only those which do not extend from UIComponent are also taghandlers, e.g. <f:validator>, <ui:include>, <ui:define>, etc. The ones which extend from UIComponent are also JSF UI components, e.g. <f:param>, <ui:fragment>, <ui:repeat>, etc. From JSF UI components only the id and binding attributes are also evaluated during view build time. Thus the below answer as to JSTL lifecycle also applies to the id and binding attributes of JSF components.

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

The view build time is that moment when the XHTML/JSP file is to be parsed and converted to a JSF component tree which is then stored as UIViewRoot of the FacesContext. The view render time is that moment when the JSF component tree is about to generate HTML, starting with UIViewRoot#encodeAll(). So: JSF UI components and JSTL tags doesn't run in sync as you'd expect from the coding. You can visualize it as follows: JSTL runs from top to bottom first, producing the JSF component tree, then it's JSF's turn to run from top to bottom again, producing the HTML output.

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

For example, this Facelets markup iterating over 3 items using <c:forEach>:

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

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

...creates during view build time three separate <h:outputText> components in the JSF component tree, roughly represented like this:

<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 输出:

...which in turn individually generate their HTML output during view render time:

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

请注意,您需要手动确保组件 ID 的唯一性,并且这些 ID 也在视图构建期间进行评估.

Note that you need to manually ensure the uniqueness of the component IDs and that those are also evaluated during view build time.

虽然这个 Facelets 标记使用 迭代超过 3 个项目,这是一个 JSF UI 组件:

While this Facelets markup iterating over 3 items using <ui:repeat>, which is a JSF UI component:

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

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

...already ends up as-is in the JSF component tree whereby the very same <h:outputText> component is during view render time being reused to generate HTML output based on current iteration round:

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

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

Note that the <ui:repeat> as being a NamingContainer component already ensured the uniqueness of the client ID based on iteration index; it's also not possible to use EL in id attribute of child components this way as it is also evaluated during view build time while #{item} is only available during view render time. Same is true for an h:dataTable and similar components.

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

As another example, this Facelets markup conditionally adding different tags using <c:if> (you can also use <c:choose><c:when><c:otherwise> for this):

<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 的情况下只将 组件添加到 JSF 组件树中:

...will in case of type = TEXT only add the <h:inputText> component to the JSF component tree:

<h:inputText ... />

虽然这个 Facelets 标记:

While this Facelets markup:

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

...无论条件如何,都将在 JSF 组件树中以上述方式结束.因此,当您拥有许多组件并且它们实际上基于静态"模型(即 field 至少在视图范围内不会改变)时,这可能最终会出现在臃肿"的组件树中).此外,当您处理子类时,您可能会遇到 EL 麻烦在 2.2.7 之前的 Mojarra 版本中具有附加属性.

...will end up exactly as above in the JSF component tree regardless of the condition. This may thus end up in a "bloated" component tree when you have many of them and they are actually based on a "static" model (i.e. the field does not ever change during at least the view scope). Also, you may run into EL trouble when you deal with subclasses with additional properties in Mojarra versions before 2.2.7.

它们不可互换.<c:set> 在 EL 作用域中设置一个变量,该变量只能在视图构建期间的标签位置之后 访问,但在视图渲染期间可以在视图中的任何位置访问时间. 将一个 EL 变量传递给通过 包含的 Facelet 模板code> 或 .较旧的 JSF 版本存在错误,即 <ui:param> 变量也可在相关 Facelet 模板之外使用,这绝不应依赖.

They are not interchangeable. The <c:set> sets a variable in the EL scope, which is accessible only after the tag location during view build time, but anywhere in the view during view render time. The <ui:param> passes an EL variable to a Facelet template included via <ui:include>, <ui:decorate template>, or <ui:composition template>. Older JSF versions had bugs whereby the <ui:param> variable is also available outside the Facelet template in question, this should never be relied upon.

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

The <c:set> without a scope attribute will behave like an alias. It does not cache the result of the EL expression in any scope. It can thus perfectly fine be used inside for example iterating JSF components. Thus, e.g. below will work fine:

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

它只是不适合例如在循环中计算总和.为此,请使用 EL3.0 流:

It's only not suitable for e.g. calculating the sum in a loop. For that instead use 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 属性设置为允许值 requestviewsession 之一时,或 application,那么它将在视图构建期间立即评估并存储在指定的范围内.

Only, when you set the scope attribute with one of allowable values request, view, session, or application, then it will be evaluated immediately during view build time and stored in the specified scope.

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

这只会被评估一次,并且在整个应用程序中都可以作为 #{dev} 使用.

This will be evaluated only once and available as #{dev} throughout the entire application.

在JSF迭代组件(如等)中使用JSTL可能只会导致意想不到的结果,或者当 JSTL 标记属性依赖于 JSF 事件的结果时,例如 preRenderView 或模型中提交的表单值,这些值在视图构建期间不可用.因此,仅使用 JSTL 标记来控制 JSF 组件树构建的流程.使用 JSF UI 组件来控制 HTML 输出生成的流程.不要将迭代 JSF 组件的 var 绑定到 JSTL 标记属性.不要依赖 JSTL 标记属性中的 JSF 事件.

Using JSTL may only lead to unexpected results when being used inside JSF iterating components such as <h:dataTable>, <ui:repeat>, etc, or when JSTL tag attributes depend on results of JSF events such as preRenderView or submitted form values in the model which aren't available during view build time. So, use JSTL tags only to control flow of JSF component tree building. Use JSF UI components to control flow of HTML output generation. Do not bind the var of iterating JSF components to JSTL tag attributes. Do not rely on JSF events in JSTL tag attributes.

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

Anytime you think you need to bind a component to the backing bean via binding, or grab one via findComponent(), and create/manipulate its children using Java code in a backing bean with new SomeComponent() and what not, then you should immediately stop and consider using JSTL instead. As JSTL is also XML based, the code needed to dynamically create JSF components will become so much better readable and maintainable.

重要的是要知道,早于 2.1.18 的 Mojarra 版本在引用 JSTL 标记属性中的视图范围 bean 时在部分状态保存方面存在错误.整个视图范围的 bean 将重新创建,而不是从视图树中检索(仅仅是因为在 JSTL 运行时完整的视图树尚不可用).如果您期望或通过 JSTL 标记属性在视图作用域 bean 中存储一些状态,那么它不会返回您期望的值,或者它将在视图之后恢复的真实视图作用域 bean 中丢失"树建好了.如果您无法升级到 Mojarra 2.1.18 或更新版本,解决方法是关闭 web.xml 中的部分状态保存,如下所示:

Important to know is that Mojarra versions older than 2.1.18 had a bug in partial state saving when referencing a view scoped bean in a JSTL tag attribute. The whole view scoped bean would be newly recreated instead of retrieved from the view tree (simply because the complete view tree isn't available yet at the point JSTL runs). If you're expecting or storing some state in the view scoped bean by a JSTL tag attribute, then it won't return the value you expect, or it will be "lost" in the real view scoped bean which is restored after the view tree is built. In case you can't upgrade to Mojarra 2.1.18 or newer, the work around is to turn off partial state saving in web.xml like below:

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

另见:

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆