19.3. 复合构件

如果只用 Facelets 定义和使用模板,那么可能会有点大材小用了。虽然 Facelets 的模板化特性完整而且丰富,但并不是 Facelets 真正出色的地方。Facelets 把它的精华放在复合构件上,下面我们就该方面的内容进行详细的介绍。

19.3.1. 什么是复合构件

复合构件,正如其名字所示,让您能够从现有构件组装和定制一个新构件,能重复发挥出OperaMasks构件模型的优势。复合构件具有如下的特点:

  • 少量的配置;

  • 无需 Java 代码;

  • 开发人员可以方便向其附加功能;

  • 修改后执行热部署;

下面我们将重点放在创建和使用复合构件的步骤上。但在开始之前,先要确保能够清楚地理解是什么让这些方便的小代码段这么棒。首先我们来看看下面这段代码:

....
<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>
   <w: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>
....

上面代码展示了某清单页面的局部内容,用于生成列标题和升序/降序排列链接。但我们可以看到,存在多个地方的重复代码。当我们的列数增加,会如何呢?需要更多、更多的工作。

下面我们看看使用复合构件后的代码:

<my:column entity="#{cd}" fieldName="title" backingBean="#{CDManagerBean}"/>
<my:column entity="#{cd}" fieldName="artist" backingBean="#{CDManagerBean}"/>

似乎我们用2行代码就替代了 70 行或更多行代码!可以看出,my:column 即是一个复合构件。下面我们就来看看,如何定义这样一个复合构件。

19.3.2. 自定义复合构件

以下是创建复合构件的步骤:

  • 创建 复合构件标识文件;

  • 在 web.xml 中声明该标示;

  • 创建复合构件模板文件;

  • 用命名空间导入标识文件;

19.3.2.1. 创建 复合构件标识文件

复合构件的标识文件 是符合 facelet_taglib_1_0.dtd规范的文件。在概念上它与 JSP 中的 TLD 文件相似。下面是一个示例标示文件:

<!-- mycomponent.taglib.xml-->
<!DOCTYPE facelet-taglib PUBLIC
  "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<facelet-taglib>
  1<namespace>http://www.mycompany.com/aom</namespace>
  <tag>
    2<tag-name>column</tag-name>
    3<source>column.xhtml</source>
  </tag>
</facelet-taglib>
1

定义复合构件的命名空间,稍后需要通过它在其他 Facelets 页面中导入这个标记库;

2

定义复合构件的构件名称;

3

指定该复合构件的模板文件的相对路径。

19.3.2.2. 在 web.xml 中声明该标示

有了一个标记库是很好,但是要让它有用,还必须把它的存在告诉 Facelets我们新增加了一个构件。所以我们需要在 web.xml 文件中用 facelets.LIBRARIES 初始化参数做这件事,如下所示:

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

19.3.2.3. 创建复合构件模板文件

模板文件是一个标准的Facelets页面,具体的标签使用方式请参看上节。代码如下所示:

<!--column.xhtml -->
....
<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>
.....

19.3.2.4. 用命名空间导入标识文件

创建了标识文件并在 Facelets 标记库中定义了它之后,就可以使用它了。当Facelets页面需要使用该构件时,只需要在其命令空间内导入即可,如下所示:

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
  1xmlns:my="http://www.mycompany.com/aom" xmlns:ui="http://java.sun.com/jsf/facelets" renderKitId="AJAX">
....
  2<my:column entity="${cd}" fieldName="title"  
    backingBean="#{CDManagerBean}"/>
.....
1

导入命名空间,命名空间的值与前面步骤 1 中标识库中声明的命名空间元素一样;同时我们使用“my”作为该命名空间的前缀。

2

直接使用<命名空间前缀:构件名>进行使用该复合构件。