JSF2 Facelets中的JSTL …有意义吗?

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

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

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

但是,我不确定这是否是最佳做法? 有另一种方法来实现我的目标?

介绍

JSTL <c:xxx>标记都是标记处理程序 ,它们在视图构build时执行,而JSF <h:xxx>标记都是UI组件 ,它们在视图渲染时执行。

请注意,从JSF自己的<f:xxx><ui:xxx>标记中,只有那些UIComponent扩展的标记也是标记处理程序,例如<f:validator><ui:include><ui:define>等。从UIComponent扩展的也是JSF UI组件,例如<f:param><ui:fragment><ui:repeat>等等。从JSF UI组件中,只有idbinding属性在view build time 。 因此,JSTL生命周期的下面的答案也适用于JSF组件的idbinding属性。

视图构build时间是XHTML / JSP文件被parsing并转换为JSF组件树,然后存储为FacesContext UIViewRootFacesContext 。 视图呈现时间是JSF组件树即将生成HTML的时刻,从UIViewRoot#encodeAll() 。 因此:JSF UI组件和JSTL标记不会像编码期望的那样同步运行。 您可以按如下方式对其进行可视化:JSTL首先从上到下运行,生成JSF组件树,然后JSF再次从上到下运行,生成HTML输出。

<c:forEach> vs <ui:repeat>

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

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

…在视图构build期间在JSF组件树中创build三个单独的<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的唯一性,并在视图构build时间期间对其进行评估。

虽然这个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组件树的forms结束了,因此,在视图渲染时间中,非常相同的<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,因为在视图构build时也会对其进行评估,而#{item}仅在视图渲染时可用。

<c:if> / <c:choose> vs rendered

作为另一个例子,这个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至less在视图范围内永远不会改变)。 另外,当你在2.2.7之前的Mojarra版本中处理具有附加属性的子类时,你可能遇到EL 麻烦 。

<c:set> vs <ui:param>

它们不可互换。 <c:set>在EL范围中设置一个variables,只能在视图构build期间的标签位置之后访问,而在视图渲染期间视图中的任何位置。 <ui:param>将ELvariables传递给通过<ui:include><ui:decorate template><ui:composition template>包含的Facelet模板。 较老的JSF版本有错误,即<ui:param>variables在Facelet模板之外也是可用的,这绝不应该被依赖。

没有scope属性的<c:set>将performance得像一个别名。 它不会在任何范围内cachingELexpression式的结果。 因此,它可以很好地用于例如迭代JSF组件。 因此,例如下面将工作正常:

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

它仅适用于例如循环计算总和。 为此而使用EL 3.0stream :

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

只有当您将scope属性设置为允许值requestviewsessionapplication ,才会在视图构build时立即对其进行求值并存储在指定范围内。

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

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

使用JSTL来控制JSF组件树的构build

当在JSF迭代组件(如<h:dataTable><ui:repeat>等)中使用JSTL时,或者当JSTL标记属性依赖于JSF事件的结果(例如preRenderView或提交的表单值)时,只会导致意外的结果在查看构build时间期间不可用的模型。 因此,仅使用JSTL标签来控制JSF组件树的构buildstream程。 使用JSF UI组件来控制HTML输出生成的stream程。 不要将迭代JSF组件的variables绑定到JSTL标签属性。 不要依赖JSTL标签属性中的JSF事件。

任何时候你认为你需要通过绑定绑定一个组件到backing bean,或者通过findComponent()获取一个组件,并且使用new SomeComponent()来创build/操作它的子代在一个backing bean中的Java代码,那么你应该立即停止并考虑使用JSTL。 由于JSTL也是基于XML的,因此dynamic创buildJSF组件所需的代码将变得更加易读易维护。

重要的是要知道,旧版本的Mojarra版本在JSTL标签属性中引用视图范围的bean时,在保存部分状态时存在一个错误。 整个视图范围的bean将被重新创build,而不是从视图树中检索(仅仅因为完整的视图树在JSTL运行的点上还不可用)。 如果您期待或存储视图范围bean中的某个状态由JSTL标记属性,那么它将不会返回您所期望的值,或者它将在视图后恢复的实际视图范围的bean中“丢失”树build成。 如果你不能升级到Mojarra 2.1.18或更新版本,解决方法是closuresweb.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标签有用的一些真实世界示例(即,在构build视图时真正正确使用),请参阅以下问题/答案:

  • 如何使JSF复合组件的网格?
  • 在JSF中dynamic创build表列
  • 如何自定义布局h:selectOneRadio
  • JSF中的条件variables定义
  • 如何使复合组件类似于<h:selectOneRadio />
  • JSF 2 – 在f:ajax上具有可选侦听器属性的组合组件
  • 导致堆栈溢出exception的嵌套JSF复合组件

简而言之

至于具体的function需求,如果要有条件地呈现 JSF组件,则应该使用JSF HTML组件上的rendered属性, 特别是如果#{lpc}表示JSF迭代组件的当前迭代项,例如<h:dataTable><ui:repeat>

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

或者,如果要有条件地构build (创build/添加)JSF组件,则继续使用JSTL。 这比在java中使用new SomeComponent()要好得多。

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

也可以看看:

  • 有条件地显示JSF组件
  • JSTL c:如果在JSF h:dataTable中不起作用
  • 在<ui:repeat>中指定元素的条件渲染? <c:if>似乎不起作用

使用

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

对不起,单独的答案,但我不能评论上面的答案。

对于类似开关的输出,您可以使用从primefaces-extensions开关。