我想有条件地输出一些 Facelets 代码。
为此,JSTL 标记似乎工作正常:
<c:if test="${lpc.verbose}"> ... </c:if>
但是,我不确定这是否是最佳做法?还有其他方法可以实现我的目标吗?
JSTL<c:xxx>标记都是标记处理程序,它们在 视图构建 期间执行,而 JSF<h:xxx>标记都是UI 组件,它们在 视图呈现 期间执行。
<c:xxx>
<h:xxx>
请注意,从 JSF 自己的<f:xxx>and<ui:xxx>标记中,只有那些 不 从其扩展UIComponent的也是标记处理程序,例如<f:validator>,等。从 JSF UI 组件中扩展的也是 JSF UI 组件,例如,<ui:include>等。从 JSF UI 组件中只有and属性是还在视图构建期间进行了评估。因此,以下关于 JSTL 生命周期的答案也适用于 JSF 组件的和属性。<ui:define>``UIComponent``<f:param>``<ui:fragment>``<ui:repeat>``id``binding``id``binding
<f:xxx>
<ui:xxx>
UIComponent
<f:validator>
<ui:include>
<ui:define>``UIComponent``<f:param>``<ui:fragment>``<ui:repeat>``id``binding``id``binding
视图构建时间是 XHTML/JSP 文件将被解析并转换为 JSF 组件树的时刻,然后将其存储UIViewRoot为FacesContext. 视图渲染时间是 JSF 组件树即将生成 HTML 的时刻,以UIViewRoot#encodeAll(). 所以:JSF UI 组件和 JSTL 标记不会像您对编码所期望的那样同步运行。您可以将其可视化如下:JSTL 首先从上到下运行,生成 JSF 组件树,然后轮到 JSF 再次从上到下运行,生成 HTML 输出。
UIViewRoot
FacesContext
UIViewRoot#encodeAll()
<c:forEach>
<ui:repeat>
例如,这个 Facelets 标记迭代 3 个项目,使用<c:forEach>:
<c:forEach items="#{bean.items}" var="item"> <h:outputText id="item_#{item.id}" value="#{item.value}" /> </c:forEach>
…在视图构建期间<h:outputText>在 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 在视图构建时也会进行评估。
<ui:repeat>虽然这个 Facelets 标记使用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>as 是一个NamingContainer组件,已经根据迭代索引保证了客户端 ID 的唯一性;也不可能以id这种方式在子组件的属性中使用 EL,因为它也在视图构建期间进行评估,而#{item}仅在视图渲染期间可用。对于一个h:dataTable和类似的组件也是如此。
NamingContainer
id
#{item}
h:dataTable
<c:if>
<c:choose>
rendered
作为另一个例子,这个 Facelets 标记有条件地添加不同的标签<c:if>(你也可以使用<c:choose><c:when><c:otherwise>它):
<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 组件树中:
type = TEXT
<h:inputText>
<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问题。
field
<c:set>
<ui:param>
它们不可互换。在 EL 范围内<c:set>设置一个变量,该变量仅可在视图构建期间在标记位置 之后 访问,但在视图渲染期间可在视图中的任何位置访问。将<ui:param>EL 变量传递给通过 、 或 包含的<ui:include>Facelet<ui:decorate template>模板<ui:composition template>。较旧的 JSF 版本存在一些错误,即该<ui:param>变量在所讨论的 Facelet 模板之外也可用,永远不应依赖这一点。
<ui:decorate template>
<ui:composition template>
<c:set>没有属性的scope行为就像别名一样。它不会在任何范围内缓存 EL 表达式的结果。因此,它可以完美地在内部使用,例如迭代 JSF 组件。因此,例如以下将正常工作:
scope
<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之一设置属性时,它将在视图构建期间立即评估并存储在指定范围内。request``view``session``application
request``view``session``application
<c:set var="dev" value="#{facesContext.application.projectStage eq 'Development'}" scope="application" />
这将只评估一次,并且#{dev}在整个应用程序中都可用。
#{dev}
使用 JSTL 可能只会导致在 JSF 迭代组件(例如 、 等)中使用时,或者当 JSTL 标记属性依赖于 JSF 事件的结果(例如,模型中提交的表单值在视图构建期间不可用)时会<h:dataTable>导致<ui:repeat>意外preRenderView结果. 因此,仅使用 JSTL 标签来控制 JSF 组件树构建的流程。使用 JSF UI 组件来控制 HTML 输出生成的流程。不要将var迭代 JSF 组件绑定到 JSTL 标记属性。不要依赖 JSTL 标记属性中的 JSF 事件。
<h:dataTable>
preRenderView
var
每当您认为需要通过 将组件绑定到支持 beanbinding或获取一个组件findComponent(),并使用支持 bean 中的 Java 代码创建/操作其子级时new SomeComponent(),您应该立即停止并考虑改用 JSTL。由于 JSTL 也是基于 XML 的,因此动态创建 JSF 组件所需的代码将变得更好的可读性和可维护性。
binding
findComponent()
new SomeComponent()
重要的是要知道,早于 2.1.18 的 Mojarra 版本在引用 JSTL 标记属性中的视图范围 bean 时存在部分状态保存错误。整个视图范围的 bean 将被 重新 创建,而不是从视图树中检索(仅仅是因为在 JSTL 运行时完整的视图树还不可用)。如果您期望或通过 JSTL 标记属性在视图范围 bean 中存储某些状态,那么它将不会返回您期望的值,或者它将在视图之后恢复的真实视图范围 bean 中“丢失”树建好了。如果您无法升级到 Mojarra 2.1.18 或更高版本,解决方法是关闭部分状态保存,web.xml如下所示:
web.xml
<context-param> <param-name>javax.faces.PARTIAL_STATE_SAVING</param-name> <param-value>false</param-value> </context-param>
至于您的具体功能需求,如果您想有条件地 渲染 JSF 组件,请改用renderedJSF HTML 组件上的属性, 特别是 如果#{lpc}表示 JSF 迭代组件的当前迭代项,例如<h:dataTable>或<ui:repeat>。
#{lpc}
<h:someComponent rendered="#{lpc.verbose}"> ... </h:someComponent>
或者,如果您想有条件地 构建 (创建/添加)JSF 组件,那么请继续使用 JSTL。new SomeComponent()这比在java中冗长地做的要好得多。
<c:if test="#{lpc.verbose}"> <h:someComponent> ... </h:someComponent> </c:if>