facelets_Facelets像手套一样适合JSF

本文探讨了Facelets在JavaServer Faces(JSF)开发中的应用,重点介绍了其模板功能和合成组件的优势。Facelets简化了JSF组件的创建与重用,允许开发者构建更加模块化和可维护的Web应用程序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近,在从事Java™Server Faces(JSF)项目时,我很高兴第一次使用Facelets。 我最喜欢Facelets的地方是它使我可以创建可重复使用的合成组件。 从那以后,能够接受一个页面(如JSP)并将其转换为组件一直是我的JSF开发的一大福音。 我的结论呢? 如果您不使用Facelets,那么您将无法充分利用JSF。

JSF和JavaServer Pages技术之间的不匹配是JSF开发中的一个严重问题。 问题是如何将JSP的动态内容集成到JSF的基于组件的模型中。 JSP仅专注于生成动态输出,而JSF则要求JSP协调构建组件模型。 之所以会出现断言,是因为该任务超出了JSP的初衷。

大多数JSF开发人员只是临时学习如何解决此类问题,但这就像用枕头敲打枕头,这样就不会伤到头。 Facelets是一种更为全面的解决方案:一种针对JSF组件模型的模板语言。

Facelets具有几个引人注目的功能:

  • 模板化(例如图块)
  • 组成成分
  • 自定义逻辑标签
  • 表达功能
  • 设计者友好的页面开发
  • 创建组件库

这些功能比您想象的更相关-集成。 在本文中,我将讨论前两个:模板和合成组件。 我使用一个基于为我的非信徒JSF系列开发的Web应用程序示例,将其更新为使用Facelets视图而不是Tiles。 在进一步阅读之前,您应该下载示例代码 。 如果您想继续进行讨论,则还需要安装Facelets

Facelets概述

您可能犯的最大错误之一就是假设Facelets只是Tiles的替代品。 Facelets不仅限于此:它是一种思考JSF的新方式。

JSP是一种生成servlet的模板语言。 JSP的主体等效于Servlet的doGet()doPost()方法(即,它成为jspService()方法)。 JSF定制标记(例如f:viewh:form )只是对JSF组件的调用,以将其呈现为当前状态。 JSF组件模型的生命周期与JSP生成的servlet的生命周期无关。 这种独立是混乱的源头。

与JSP不同,Facelets是一种模板语言,它是从头开始构建的,并牢记了JSF组件的生命周期。 使用Facelets,您可以生成用于构建组件树而不是servlet的模板。 这样可以实现更大的重用性,因为您可以将其他组件组成的组件组合在一起。

Facelets消除了编写自定义标签以使用JSF组件的需要。 Facelets本机使用JSF定制组件。 桥接JSF和Facelets几乎不需要什么特殊编码:您要做的就是在Facelet标记库文件中声明JSF组件。 您可以直接在Facelets模板语言中使用JSF组件,而无需进行任何其他开发。

Facelets模板框架

Facelets的类似挂毯(见相关信息 ),它提供了对部分建设面向的模板框架。 但是,对于那些来自JSP的人来说,Facelets似乎比Tapestry友好得多。 它使您可以使用已经熟悉的JSTL样式标签和JSTL / JSF / JSP样式表达语言。 大大减少的学习曲线意味着您可以更快地跳入开发。

Facelets允许您定义可以直接包含在页面中或可以轻松添加到Facelet标记库中的组件。 如此令人惊奇的是,您能在Facelets中快速定义自定义标签(组成组件和类似于JSP自定义标签的标签)。 使用这些组件,Facelets还允许您定义站点模板(和较小的模板)。 这与使用Tiles非常相似,但要减去定义文件。 您还可以在自定义JSF组件内使用Facelets,因为Facelets API提供了易于集成的接口。

从瓷砖到小平面

正如我提到的,我在这里使用的示例Web应用程序是基于我为非信徒系列JSF创建的。 它是创建,读取,更新和删除(CRUD)清单,用于管理在线CD商店的库存。 它包括一个允许用户将新CD输入系统的表格,以及一个单选按钮列表,使他们可以选择音乐类别。 当用户选择类别时,您将触发一些JavaScript以将表单立即发布回服务器。 该应用程序还包括一个CD列表,用户可以从中按标题或艺术家对CD进行排序。 图1是应用程序类的UML图:

图1.在线CD商店示例的类图
示例应用程序类

图2让您看一下商店的CD清单页面:

图2.在线CD商店的列表页面
光盘清单

原始应用程序从Tiles获得了视图支持,而我将使用Facelets构建该应用程序。 首先,将示例中的Tiles支持替换为Facelets,然后开始编写合成组件。 不过,在进行任何操作之前,您需要先安装Facelets。

安装Facelets

安装Facelets的步骤很容易执行。 请注意,我假设您已经下载并安装了示例应用程序

  1. 下载Facelets发行版并解压缩。
  2. 将jsf-facelets.jar复制到您的WEB-INF / lib目录中(部署应用程序时,它必须最终在WEB-INF / lib目录中)。
  3. 将Facelet初始化参数添加到web.xml文件。
  4. 将FaceletViewHandler添加到faces-config.xml文件。

步骤1和2是基本步骤。 我将详细介绍其他两个。

添加初始化参数

此步骤假定您已经安装了可运行的JSF应用程序(例如, 在线CD存储示例 ),并且正在通过添加以下参数来编辑现有的web.xml页面:

<context-param>
	<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
	<param-value>.xhtml</param-value>
</context-param>

这告诉JSF假设使用xhtml的前缀,Facelet的渲染器可以解释该前缀。

Facelets具有许多参数,因此请参阅参考资料以获取完整列表。 如果您对示例有疑问,请参考DEVELOPMENT init参数,这对于调试非常有用。 将REFRESH_PERIOD参数设置为low在开发过程中也很有帮助。

添加FaceletViewHandler

为了使Facelets模板生效,您需要将有关Facelets视图处理程序的信息告知JSF。 JSF ViewHandler是一个插件,用于处理针对不同的响应生成技术(包括Facelets)的JSF请求处理生命周期的“渲染响应”和“还原视图”阶段。 (任何认为JSF不可扩展的人都会被告知错误!)您可以通过将以下视图处理程序添加到faces-config.xml中来将Facelets插入JSF:

<application>
    <locale-config>
      <default-locale>en</default-locale>
    </locale-config>
	<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
  </application>

使用Facelets进行模板化

我将首先向您介绍Facelets模板框架,因为它相对容易理解。 创建和使用Facelets模板的步骤如下:

  1. 创建一个layout.xhtml页面。
  2. 通过定义Facelet的名称空间来导入Facelets的使用。
  3. 使用ui:insert标记定义页面的逻辑区域。
  4. 使用纯文本和ui:include标记定义合理的默认值。

我将以在线CD商店的“列表”页面为布局示例,一步一步地完成这些步骤。

步骤1.创建layout.xhtml页面

layout.xhtml页面只是使用以下doctype声明的普通XHTML文本文件:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
</html>

无需更多详细信息!

步骤2.定义Facelets的名称空间

要将Facelets标记用于模板,您需要使用XML命名空间将其导入,如下所示:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets">
...

注意ui名称空间的定义。

步骤3.使用ui:insert标记定义页面的逻辑区域

接下来,您定义布局的逻辑区域,例如标题,标题,导航,内容等。 这是一个如何定义标题的示例:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<head>
  <title><ui:insert name="title">Default title</ui:insert></title>
  <link rel="stylesheet" type="text/css" href="./css/main.css"/>
</head>
...

注意使用ui:insert标记来定义标题的逻辑区域。 ui:insert元素中的文本“默认标题”定义了模板用户未通过标题时的文本内容。 您还可以将以下内容编写为:

<title>#{title}</title>

步骤4.使用纯文本和ui:includes定义默认值

默认情况下,您可以传递的内容不限于纯文本。 例如,研究来自layout.xhtml的以下代码片段:

<div id="header">
    <ui:insert name="header">
    	<ui:include src="header.xhtml"/>
    </ui:insert>
</div>

在这里,我使用ui:insert标记定义逻辑区域,并使用ui:include标记插入默认区域。 默认情况下,使用布局的页面使用header.xhtml的内容作为标题文本,但是由于标题是ui:insert定义的逻辑区域,因此使用此模板的页面也可以传递不同的标题。 对于具有前端(例如,带有购物车的目录)和后端管理(例如,用于添加新产品)的应用程序,后端站点在标题或导航中可能具有不同的链接。 ui:include标记可轻松将默认标头替换为新标头。

清单1显示了示例应用程序的Listing页面list.xhtml的完整代码:

清单1.完整的list.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<head>
  <title><ui:insert name="title">Default title</ui:insert></title>
  <link rel="stylesheet" type="text/css" href="./css/main.css"/>
</head>

<body>

<div id="header">
    <ui:insert name="header">
    	<ui:include src="header.xhtml"/>
    </ui:insert>
</div>


<div id="left">
  <ui:insert name="navigation" >
    <ui:include src="navigation.xhtml"/>
  </ui:insert>
</div>


<div id="center">
  <br />
  <span class="titleText"> <ui:insert name="title" /> </span>
  <hr />
  <ui:insert name="content">
  	<div>
    <ui:include src="content.xhtml"/>  
    </div>
  </ui:insert>
</div>

<div id="right">
  <ui:insert name="news">
    <ui:include src="news.xhtml"/>
  </ui:insert>
</div>

<div id="footer">
  <ui:insert name="footer">
    <ui:include src="footer.xhtml"/>  
  </ui:insert>
</div>

</body>

</html>

现在您知道如何定义布局,我将向您展示如何使用它!

使用Facelets模板

要调用模板,请使用ui:composition标签。 要将参数传递给模板,请使用ui:define标记,它们是ui:composition标记的子元素。 在清单2中,我调用了在线CD商店示例的布局页面:

清单2.调用布局页面
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">


<ui:composition template="/WEB-INF/layout/layout.xhtml">
  <ui:define name="title">CD form</ui:define>
  <ui:define name="content">

	<!-- use the form tag to set up this form -->
	<h:form id="cdForm">

		...
		...
...

	</h:form>
  </ui:define>
</ui:composition>
</html>

请注意,上述调用中包括以下名称空间:

  • xmlns:h =“ http://java.sun.com/jsf/html”
  • xmlns:f =“ http://java.sun.com/jsf/core”

使用Facelets,您不必依赖JSF标记库,因此要使用核心和HTML JSF组件,必须通过上述名称空间导入它们。

使用html标记可能看起来很奇怪。 毕竟,清单2中所示的布局页面正在调用一个已经具有html标签的模板。 那是否意味着您将获得两个html标签? 碰巧的是, ui:composition标记外部的所有内容都会被忽略,因此html标记所做的只是使HTML片段可由HTML编辑器查看。 它不影响运行时行为。

位置决定一切

当页面调用布局模板时,只需指定模板的位置,如下所示:

<ui:composition template="/WEB-INF/layout/layout.xhtml">

这个标记调用清单1所示的模板,因此我要做的就是将参数传递给模板。 然后,在组合标志内,我可以传递标题等简单文本:

<ui:define name="title">CD form</ui:define>

或整个组件树:

<ui:define name="content">

	<!-- use the form tag to setup this form -->
	<h:form id="cdForm">

		...
		...
...

	</h:form>
  </ui:define>

注意,在我可能已经定义和传递的许多逻辑区域中,cdForm.xhtml仅传递了两个:内容和标题。

组成成分

如果仅使用Facelets来定义和使用模板,则可能会有些失望。 尽管Facelets模板功能齐全且功能丰富,但是它没有Tiles这样的框架那么多的功能,这对于定义默认值,相关模板的层次结构等非常有用。

模板并不是Facelets真正发挥作用的地方:Facelets在合成组件方面发挥了最大的作用。 (有趣的是,合成组件也为Facelets模板提供了一些好处;例如,您可以在Facelets中省略f:verbatim标签和其他h:outputText标签,因为所有内容都被视为组件树中的组件。稍后将对此进行更多介绍。)

在本文的其余部分,我将重点介绍创建和使用合成组件所涉及的步骤。 但是,在执行此操作之前,请确保您清楚了解这些方便的小代码剪裁为何如此出色。

打破DRY原则

您是否曾经编写过类似于清单3中所示片段的代码?

清单3.组成组件之前的寿命
<h:dataTable id="items" value="#{CDManagerBean.cds}" var="cd"
	rowClasses="oddRow, evenRow" headerClass="tableHeader">

<!--  Title -->
<h:column>
	<f:facet name="header">
		<h:panelGroup>
			<h:outputText value="Title" />
				
                 <f:verbatim>[</f:verbatim>

			<h:commandLink styleClass="smallLink"
                              action="#{CDManagerBean.sort}">
				<h:outputText id="ascTitle" value="asc" />
				<f:param name="by" value="title"/>
				<f:param name="order" value="asc"/>
			</h:commandLink>

			<h:outputText value="," />
			<!-- Sort descending -->
			<h:commandLink styleClass="smallLink"
                         action="#{CDManagerBean.sort}">
				<h:outputText id="decTitle" value="dec" />
				<f:param name="by" value="title"/>
				<f:param name="order" value="dec"/>
			</h:commandLink>
			     <f:verbatim>]</f:verbatim>
		</h:panelGroup>
	</f:facet>

	<h:outputText value="#{cd.title}" />
</h:column>

<!--  Artist -->
<h:column>
	<f:facet name="header">
		<h:panelGroup>
			<h:outputText value="Artist" />
			   <f:verbatim>[</f:verbatim>

		      <h:commandLink styleClass="smallLink"
                           action="#{CDManagerBean.sort}">
			     <h:outputText id="ascArtist" value="asc" />
				<f:param name="by" value="artist"/>
				<f:param name="order" value="asc"/>
			   </h:commandLink>

			<h:outputText value="," />
					<!-- Sort descending -->
			<h:commandLink styleClass="smallLink"
  				           action="#{CDManagerBean.sort}">
				<h:outputText id="decArtist" value="dec" />
				<f:param name="by" value="artist"/>
				<f:param name="order" value="dec"/>
			</h:commandLink>
			     <f:verbatim>]</f:verbatim>
		</h:panelGroup>
	</f:facet>
	<h:outputText value="#{cd.artist}" />
</h:column>

清单.xhtml中的代码为示例应用程序的“清单”页面生成列标题以及升序和降序链接。 请注意,我必须在多个位置复制代码以输出多个列。 (您还会在上面的示例中注意到,我在${..}#{..}之间切换;这可能会令人困惑,但是它们却做同样的事情!)

所有重复的用于渲染“标题”和“艺术家”列的代码都违反了DRY原理-即, 不要重复自己 。 那你怎么了? 好吧,想象一下,如果您的清单中平均有5列,而应用程序中有20个不同的清单。 使用清单3中的方法,您将必须重复相同的35行代码100次,总共需要3,500行代码! 维护所有的代码将是一个痛苦,但如果你决定改变上市或呈现什么( 哇!)添加一个通用的方法做过滤列表? 太多太多的工作了。

现在将清单3与该清单进行比较:

清单4.创建字段的新方法
<h:dataTable id="items" value="#{CDManagerBean.cds}" var="cd"
		rowClasses="oddRow, evenRow" headerClass="tableHeader">
			
 <a:column entity="${cd}" fieldName="title" backingBean="${CDManagerBean}"/>
 <a:column entity="${cd}" fieldName="artist" backingBean="${CDManagerBean}"/>

看起来我只用4条代码就替换了70条或更多行代码! 如您所料, a:column是一个合成组件。 在Facelets中定义这样的组件很容易,如清单5所示:

清单5. column.xhtml呈现带有排序链接的列
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:z="http://www.qualcomm.com/jsf/core"
      xmlns:c="http://java.sun.com/jstl/core"
      xmlns:fn="http://java.sun.com/jsp/jstl/functions">

THIS TEXT WILL BE REMOVED
<ui:composition>
	<!--  The label attribute is optional. Generate it if it is missing. -->
	<c:if test="${empty label}">
	    <c:set var="label" value="${fieldName}" />
	</c:if>

	<!--  The sort attribute is optional. Set it to true if it is missing. -->
	<c:if test="${empty sort}">
	    <c:set var="sort" value="${true}" />
	</c:if>

	<h:column>
	  <f:facet name="header">
	    <h:panelGroup>
	      ${label} 
	      <c:if test="${sort}">
	          [
	        <h:commandLink styleClass="smallLink"
	      action="#{backingBean.sort}">
	          <h:outputText value="asc" />
	          <f:param name="by" 
              value="${fieldName}"/>
	          <f:param name="order" value="asc"/>
	        </h:commandLink>
					,
	        <!-- Sort descending -->
	        <h:commandLink styleClass="smallLink"
              action="#{backingBean.sort}">	
	          <h:outputText value="asc" />
	          <f:param name="by"                
              value="${fieldName}"/>
	          <f:param name="order" value="dec"/>
	        </h:commandLink>
					]
	      </c:if>			
	    </h:panelGroup>
	  </f:facet>
	  <!--  Display the field name -->
	  <h:outputText value="${entity[fieldName]}"/>
        </h:column>
</ui:composition>
THIS TEXT WILL BE REMOVED AS WELL
</html>

罚款点

在介绍更高级的示例之前,我想提请您注意一些事项。 首先,请注意清单5中如何以通用方式引用值绑定:

<h:outputText value="${entity[fieldName]}"/>

其次,当我调用此合成组件时,我将把entityfieldName作为属性传递,如下所示:

<a:column entity="${cd}" fieldName="title" backingBean="${CDManagerBean}"/>

Facelets使用的EL规范使您可以使用点( . )表示法或较少使用的Map表示法来引用字段。 例如,如果按上述方式调用, ${entity[fieldName]}将等于CDManager.title 。 还要注意,我不需要f:verbatim标记或辅助h:outputText 。 这对于您编写的任何Facelets页面都是如此。 Facelets知道JSF组件树,它的唯一目的就是构建该组件树。 与使用JSP和Tiles相比,这是Facelets的另一个优势。

编写完成后,就可以在许多其他地方使用column.xhtml合成组件。 作为一般规则:如果您违反了DRY原理,请考虑使用合成成分。

创建一个组件

您已经使用column.xhtml示例快速浏览了组成组件。 现在,让我们逐步完成创建一个步骤的过程。 以下是创建合成组件的步骤:

  1. 创建一个Facelets标签库。
  2. 在web.xml中声明标签库。
  3. 使用名称空间导入标记文件。

步骤1.创建Facelets标记文件

一个TAGFILE是遵循facelet_taglib_1_0.dtd的文件。 它在概念上类似于JSP中的TLD文件。 清单6是一个示例标记库文件:

清单6.标签库文件-arcmind.taglib.xml
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC
  "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
  "facelet-taglib_1_0.dtd">
<facelet-taglib>
      <namespace>http://www.arc-mind.com/jsf</namespace>
	<tag>
		<tag-name>field</tag-name>
		<source>field.xhtml</source>
	</tag>
	<tag>
		<tag-name>column</tag-name>
		<source>column.xhtml</source>
	</tag>
	<tag>
		<tag-name>columnCommand</tag-name>
		<source>columnCommand.xhtml</source>
	</tag>
</facelet-taglib>

arcmind.taglib.xml文件声明了三个标签: fieldcolumn (您已经看过一个!)和columnCommand 。 您所要做的就是使用tag-name和实现文件的位置指定tag-name 。 实现文件名是相对的。 您将在示例Web应用程序的WEB-INF \ facelets \ tags文件中找到所有这些代码,包括DTD。

请确保注意在上面的tag元素之前声明的namespace元素:您稍后将需要它来从另一个Facelets页面使用此标签库。

步骤2.在web.xml中声明标签库

拥有标签库虽然不错,但要使其有用,就必须告诉Facelets它存在。 您可以使用web.xml文件中的facelets.LIBRARIES init参数来执行此操作,如下所示:

<context-param>
	<param-name>facelets.LIBRARIES</param-name>
	<param-value>
		/WEB-INF/facelets/tags/arcmind.taglib.xml
	</param-value>
</context-param>

传递facelets.LIBRARIES作为以分号分隔的列表,您可以根据需要定义尽可能多的标记文件。

步骤3.使用名称空间导入标记文件

创建标记文件并将其定义在Facelets标记库中之后,就可以使用它了。 您可以通过将标记文件声明为XML名称空间来使用标记文件,如下所示:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:a="http://www.arc-mind.com/jsf">

...
...

<a:column entity="${cd}" fieldName="title"  
  backingBean="${CDManagerBean}"/>
<a:column entity="${cd}" fieldName="artist" 
  backingBean="${CDManagerBean}"/>
<a:column entity="${cd}" fieldName="price" 
  backingBean="${CDManagerBean}" sort="${false}"/>
<a:columnCommand label="Edit" action="editCD"

              backingBean="${CDManagerBean}"/>

注意命名空间定义如下:

xmlns:a="http://www.arc-mind.com/jsf"

名称空间值与我在步骤1中在标记库中声明的名称空间元素相同。

高级技巧和提示

这几乎涵盖了合成组件的基础知识。 您可以使用到目前为止显示的内容创建可重用的组件。 在我自己的Facelets使用中,我发现了一些使合成组件更有用的小技巧,并且在某些情况下可以解决小问题。 例如,考虑cdForm.xhtml模板中的以下代码片段:

清单7. cdForm.xhtml的片段
<h:form id="cdForm">

  <h:inputHidden id="cdid" value="#{CDManagerBean.cd.id}" />

  <h:panelGrid id="formGrid" columns="3" rowClasses="row1, row2">

	<!-- Title                                   -->
	<h:outputLabel id="titleLabel" for="title" styleClass="label"
                     value="Title" />
	<h:inputText id="title" value="#{CDManagerBean.cd.title}"
				     required="true" />
	<h:message id="titleMessage" for="title" styleClass="errorText"/>

	<!-- Artist                                   -->
	<h:outputLabel  id="artistLabel" for="artist" styleClass="label" 
                     value="Artist" />
	<h:inputText id="artist" value="#{CDManagerBean.cd.artist}"
				     required="true" />
	<h:message id="titleMessage" for="artist"  
                     styleClass="errorText"/>

	<!-- Price                                   -->
	<h:outputLabel  id="priceLabel" for="price" styleClass="label" value="Price" />
	<h:inputText id="price" value="#{CDManagerBean.cd.price}"
				     required="true">
		<f:validateDoubleRange minimum="15.0" maximum="100.0" />
	</h:inputText>
	<h:message id="priceMessage" for="price" styleClass="errorText"/>

上面的页面在概念上与清单3相似,因为它留出了一些Facelets的余地,并且保留了字段组成部分来消除重复的代码。 根据此代码,创建用于显示字段的合成组件应该很容易,但是有一点障碍。 你看到了吗? 查看表单的price字段:它包含一个验证器。

现在,如何将验证器传递给合成组件?

传递子元素

这是关于Facelets的一个肮脏的小秘密:合成组件基本上是一种模板。 这样,您可以使用带有特定ui:insertui:define标记传递模板参数,或者可以将正文作为默认的ui:insert传递。

清单8就是字段组件(field.xhtml)的这种实现:

清单8. field.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:z="http://www.qualcomm.com/jsf/core"
      xmlns:c="http://java.sun.com/jstl/core"
      xmlns:fn="http://java.sun.com/jsp/jstl/functions"
      xmlns:t="http://myfaces.apache.org/tomahawk">

THIS TEXT WILL BE REMOVED
<ui:composition>

	      <!--  The label is optional. 
                Generate it if it is missing. -->
		<c:if test="${empty label}">
			<c:set var="label" value="${fieldName}" />
		</c:if>
	 
	 	<!-- The required attribute is optional, 
                 initialize it to true if not found. -->
		<c:if test="${empty required}">
			<c:set var="required" value="true" />
		</c:if>
	

		<h:outputLabel id="${fieldName}Label" 
                      value="${label}" for="#{fieldName}" />			

		<h:inputText id="#{fieldName}" value="#{entity[fieldName]}" 
			             required="${required}">
			  <ui:insert />
		</h:inputText>

		<!--  Display any error message that are found -->
		<h:message id="${fieldName}Message" 
			style="color: red; text-decoration: overline" 
			for="#{fieldName}" />

</ui:composition>
THIS TEXT WILL BE REMOVED AS WELL

</html>

到目前为止,清单8中的解决方法应该或多或少是您所期望的。 注意,在h:inputText内部使用了未命名的ui:insert标记。 一旦掌握了它,就可以使用这个合成组件,如清单9所示:

清单9.字段标签组成组件
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:a="http://www.arc-mind.com/jsf">

...
<h:form id="cdForm">

	<!-- Title, Artist, Price -->
	<a:field fieldName="title" entity="#{CDManagerBean.cd}" />
	<a:field fieldName="artist" entity="#{CDManagerBean.cd}" />
	<a:field fieldName="price" entity="#{CDManagerBean.cd}" >
		<f:validateDoubleRange minimum="15.0" maximum="100.0" />
	</a:field>

...

price的字段标签作为匿名插入内容通过了验证程序。 由于其他字段未定义主体,因此匿名插入不引入任何内容作为默认内容。

采取行动

有时您可能希望传递一个动作绑定来创建诸如工具栏和导航列表之类的元素。 问题是使用标准表达语言无法实现,但是有一种解决方法! 可以引用对象中的字段的方式相同,也可以引用对象中的方法。 因此,要创建一个创建动作绑定的组件,可以执行以下操作(来自columnCommand.xhtml):

<h:commandLink id="#{action}" value="#{label}"  
                              action="#{backingBean[action]}"/>

研究动作属性的value 。 请注意,我以与先前引用实体中的字段相同的方式访问方法。 我可以使用以下语法调用此组件:

<a:columnCommand label="Edit" action="editCD" 
backingBean="${CDManagerBean}"/>

调用时,此调用将CDManagerBeaneditCD()方法CDManagerBean到链接。 清单10显示了columnCommand.xhtml的完整清单:

清单10. columnCommand.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:z="http://www.qualcomm.com/jsf/core"
      xmlns:c="http://java.sun.com/jstl/core">

THIS TEXT WILL BE REMOVED
<ui:composition>

	<!--  The label is optional. Generate it if it is missing. -->
	<c:if test="${empty label}">
			<c:set var="label" value="${action}" />
	</c:if>
	
	<h:column>
		<f:facet name="header">
			<h:panelGroup>
				<h:outputText value="#{label}" />
			</h:panelGroup>
		</f:facet>
		<h:commandLink id="#{action}" value="#{label}" 
                                     action="#{backingBean[action]}"/>
	</h:column>
		
</ui:composition>
THIS TEXT WILL BE REMOVED AS WELL

</html>

小面的缺点

我已经清楚地显示了使用Facelets的好处,即组件组成和模板框架,其通用语言是组件,而不是Servlets输出。 但是采用Facelets有一些缺点。 一方面,IDE对Facelets的支持很少。 只有一个Eclipse IDE实施完全支持Facelets(商业化;请参阅参考资料 ),并且它似乎不支持代码完成。

也没有IDE支持调试Facelets(即设置断点等)。 为了进行调试,您需要阅读Facelets手册,打开其JDK 1.4样式的日志记录,并相应地设置其init参数以进行开发。

从好的方面来说,我发现使用Facelets API非常自然和直观。 调试起初有点不可思议,但最终却可以进行。 Facelets发行版附带的演示应用程序没有自定义标签或功能的示例,但核心项目代码有,因此请以其为指导。

如果使用新的JSF组件库,则必须有一个Facelets标记库文件,该文件公开了该库。 对于主要组件库(例如Oracle和Tomahawk),存在一些标记库,但是即使是那些标记库也需要进行调整。 我必须调整Tomahawk标记库,才能在应用程序中获取Tomahawk日历组件。 当然,编写导出组件的标签库文件有些容易,但这是另一个麻烦。 如果要使用新的自定义组件库,则必须编写一个标记库文件。

由于其他实现方面的问题,Facelets似乎只能与MyFaces 1.1.1和Sun的1.2 JSF参考实现一起使用(Sun的JSF RI 1.2尚未正式发布)。 您不能将Facelets与1.1 RI一起使用。 尽管可以将MyFaces与IBM WebSphere一起使用,但是您不能将Facelets与IBM的实现一起使用。 (如果使用最新版本的Facelets,则必须使用MyFaces 1.1.2的夜间构建,但尚未发布。)

还要注意,MyFaces 1.1和JSF RI 1.2的基本机制不同。 尽管如此,Facelets尝试以当前形式容纳这两种实现(MyFaces 1.1.2和JSF RI 1.2的夜间版本),这似乎占了最近在Facelets上花费的大部分时间。 两者合规并固化得更多,这将是很好的选择,这需要更少的时间使Facelets在两种环境下均能正常工作,因此可以花更多的时间来改进Facelets。

结论

即使有一些缺点,我还是强烈建议您下载Facelets并尽快开始使用它。 Facelets是JSF的未来,或者应该是JSF的未来,使用Facelets可使您在任何JSF风暴中保持干燥。 如果您不能在当前项目中使用它,请在下一个项目中牢记。

我已经使用Facelets为内部CRUD框架创建了一个组成组件,自定义Facelet标签和函数的完整库。 我发现了许多用于构建合成组件的技巧和技术(例如,一个字段标签,它根据绑定到该组件的值绑定的类型自动生成一个复选框,日历组件或文本字段)可以在像这样的介绍性文章中介绍。 取而代之的是,我专注于使您起步并使用合成组件的基础知识。 通过这里的学习,您可以仅使用最少的自定义函数和自定义Facelets标签来创建一些令人惊奇的组件。

致谢

特别感谢Facelets的创建者Jacob Hookom对本文的评论和投入,并感谢Athenz的精心和有见地的编辑。


翻译自: https://www.ibm.com/developerworks/java/library/j-facelets/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值