我想有条件地输出一点 Faclets 代码。
为此,JSTL 标记似乎工作得很好:
<c:if test="${lpc.verbose}"> ... </c:if>
然而,我不确定这是否是一个最佳实践? 还有其他方法来实现我的目标吗?
使用
<h:panelGroup rendered="#{lpc.verbose}"> ... </h:panelGroup>
JSTL <c:xxx>标记都是 跟屁虫,它们在 查看构建时间期间执行,而 JSF <h:xxx>标记都是 UI 组件,它们在 查看渲染时间期间执行。
<c:xxx>
<h:xxx>
请注意,从 JSF 自己的 <f:xxx>和 <ui:xxx>标记中,只有那些从 <ui:xxx>5扩展到 <ui:xxx>4的标记也是标记处理程序,例如 <f:validator>、 <ui:include>、 <ui:define>等。从 UIComponent扩展的也是 JSF UI 组件,例如 <f:param>、 <ui:fragment>、 <ui:repeat>等。在视图构建期间,只对 JSFUI 组件中的 <ui:xxx>0和 <ui:xxx>1属性进行评估。因此,以下关于 JSTL 生命周期的答案也适用于 JSF 组件的 <ui:xxx>0和 <ui:xxx>1属性。
<f:xxx>
<ui:xxx>
<f:validator>
<ui:include>
<ui:define>
UIComponent
<f:param>
<ui:fragment>
<ui:repeat>
视图构建时间就是解析 XHTML/JSP 文件并将其转换为一个 JSF 组件树的时刻,该组件树随后被存储为 FacesContext的 UIViewRoot。视图呈现时间是 JSF 组件树即将生成 HTML 的时刻,从 UIViewRoot#encodeAll()开始。因此: JSFUI 组件和 JSTL 标记并不像您期望的那样同步运行。您可以将其可视化如下: JSTL 首先从上到下运行,生成 JSF 组件树,然后轮到 JSF 再次从上到下运行,生成 HTML 输出。
FacesContext
UIViewRoot
UIViewRoot#encodeAll()
<c:forEach>
例如,使用 <c:forEach>迭代3个项目的 Facelets 标记:
<c:forEach items="#{bean.items}" var="item"> <h:outputText id="item_#{item.id}" value="#{item.value}" /> </c:forEach>
... 在视图构建期间在 JSF 组件树中创建三个独立的 <h:outputText>组件,大致表示如下:
<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>迭代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>
请注意,作为 NamingContainer组件的 <ui:repeat>已经确保了基于迭代索引的客户端 ID 的唯一性; 这也不可能在子组件的 id属性中使用 EL,因为它也是在视图构建期间进行评估的,而 #{item}只在视图呈现期间可用。对于 h:dataTable和类似的组件也是如此。
NamingContainer
id
#{item}
h:dataTable
<c:if>
<c:choose>
rendered
作为另一个例子,这个 Faclets 标记使用 <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 ... />
而这个 Faclets 标记:
<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>
它们是不可互换的。<c:set>在 EL 范围内设置一个变量,在视图构建期间只能访问 之后标记位置,但是在视图呈现期间可以访问视图中的任何位置。<ui:param>将 EL 变量传递给通过 <ui:include>、 <ui:decorate template>或 <ui:composition template>包含的 Facet 模板。旧版本的 JSF 存在 bug,因此 <ui:param>变量也可以在有问题的 Faclet 模板之外使用,这是绝对不能依赖的。
<ui:decorate template>
<ui:composition template>
没有 scope属性的 <c:set>将表现得像一个别名。它不在任何作用域中缓存 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>
只有在使用允许值 request、 view、 session或 application之一设置 scope属性时,才会在视图构建期间立即计算该属性,并将其存储在指定的作用域中。
request
view
session
application
<c:set var="dev" value="#{facesContext.application.projectStage eq 'Development'}" scope="application" />
这将只评估一次,并在整个应用程序中作为 #{dev}提供。
#{dev}
使用 JSTL 可能只会在 JSF 迭代组件(如 <h:dataTable>、 <ui:repeat>等)中使用时导致意想不到的结果,或者当 JSTL 标记属性依赖于 JSF 事件(如 preRenderView)的结果或者模型中提交的表单值(在视图构建时不可用)时导致意外的结果。因此,只使用 JSTL 标记来控制构建 JSF 组件树的流程。使用 JSFUI 组件控制 HTML 输出生成的流程。不要将迭代 JSF 组件的 var绑定到 JSTL 标记属性。不要依赖 JSTL 标记属性中的 JSF 事件。
<h:dataTable>
preRenderView
var
任何时候,如果您认为需要通过 binding将一个组件绑定到后台 bean,或者通过 findComponent()获取一个组件,并使用带有 new SomeComponent()的后台 bean 中的 Java 代码创建/操作它的子组件,那么您应该立即停止并考虑使用 JSTL 代替。由于 JSTL 也是基于 XML 的,因此动态创建 JSF 组件所需的代码将变得更具可读性和可维护性。
binding
findComponent()
new SomeComponent()
需要知道的是,当在 jSTL 标记属性中引用视图范围内的 bean 时,超过2.1.18的 Mojarra 版本在部分状态保存中存在 bug。整个视图范围内的 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>
@ViewScoped
要查看一些 JSTL 标记有用的实际示例(例如,在构建视图期间如何正确使用) ,请参见以下问题/答案:
至于具体的功能需求,如果您希望有条件地使用 呈现 JSF 组件,则在 JSF HTML 组件上使用 rendered属性,如果 #{lpc}表示 JSF 迭代组件(如 <h:dataTable>或 <ui:repeat>)的当前迭代项,则使用 尤其是。
#{lpc}
<h:someComponent rendered="#{lpc.verbose}"> ... </h:someComponent>
或者,如果您希望有条件地创建/添加 建造(create/add) JSF 组件,那么继续使用 JSTL。这比在 java 中冗长地执行 new SomeComponent()要好得多。
<c:if test="#{lpc.verbose}"> <h:someComponent> ... </h:someComponent> </c:if>
对于类似开关的输出,您可以使用 PrimeFaces 扩展中的 开关面。