19.2. Facelets模板

Facelets 是一门模板化语言,从构建之初就为构件化页面量身定制,它具备如下的特点:

19.2.1. 模板页面与适配页面

当我们描述Facelets框架中的模版功能时,通常会涉及到两种页面:模版页面与模版的适配页面。 任何页面文件都可以用来作为模版,但一个页面文件应具有什么特性才算是一个模版呢?很简单,只需要在页面中使用一个或多个<ui:insert/>标签来从外部来源中注入内容,该页面即称为模版。 关系另一端则是模板的适配页面,包括了使用<ui:component/>, <ui:composition/>, <ui:fragment/>, 或者<ui:decorate/>标签的页面文件。下面的章节将会详细介绍模板和模板的使用。

19.2.1.1. 插入整段模板内容

如果目标模版只使用一个<ui:insert/>(没有name属性),模版客户则可以避免在body中使用<ui:define>标签。模版客户的body将被注入到模版中这个“无名”<ui:insert/>标签

出现的地方。例如:

<!-- 模版页面文件:template.xhtml -->
...
<span class="repeatingBox">
<c:forEach begin="1" end="10">
    <ui:insert />
    <br />
</c:forEach>
</span>
...
<!-- 模版适配页面文件 -->
...
<ui:composition template="template.xhtml"> 
    I'm in the spin cycle <h:outputText value="#{random.name}" />!
</ui:composition>
...

上面的例子中,当访问模版适配页面时,<ui:composition>将被模版文件的内容所代替,而模版文件中的<ui:insert>部分,又会反过来被<ui:composition>的body内容替换——即:

...
<span class="repeatingBox">
<c:forEach begin="1" end="10">
    I'm in the spin cycle <h:outputText value="#{random.name}" />!
    <br />
</c:forEach>
</span>
...

19.2.1.2. 插入多处模板内容

除了我们刚刚看到的整段模板内容的插入,模板页面中还可以将其中的内容用标签分成多份,以插入到不同的地方。我们看下面的例子:

<!-- 模板页面文件:template.xhtml -->
...
  <div id="main" class="main">
   <div id="header" class="header">
    <ui:insert name="header"/>
   </div>
   <div id="content" class="content">
    <ui:insert name="content"/>
   </div>
   <div id="footer" class="footer">
    <ui:insert name="footer"/>
   </div>
  </div>
...
<!-- 模板适配页面 -->
...
<ui:composition template="template.xhtml">
 <ui:define name="header">
  This is header
 </ui:define>
 <ui:define name="content">
  ...
 </ui:define>
 <ui:define name="footer">
  This is footer
 </ui:define>
</ui:composition>
...

模版适配页面可以把它的body分离在多个<ui:define>标签中。例如“header,content,body”,通过这些标签可以一次性地指定模板中的哪部分将放进对应的内容。

19.2.1.3. 模板嵌套

模版允许多级嵌套。这意味着一个模版适配页面本身就可以作为另外一个页面的模板。例如:

<!-- 模板页面文件:template1.xhtml -->
...
  <div id="main" class="main">
   <div id="header" class="header">
    <ui:insert name="header"/>
   </div>
   <div id="content" class="content">
    <ui:insert name="content"/>
   </div>
   <div id="footer" class="footer">
    <ui:insert name="footer"/>
   </div>
  </div>
...
<!-- 模板页面文件:template2.xhtml -->
...
<ui:composition template="template1.xhtml">
 <ui:define name="header">
  This is fixed header
 </ui:define>
 <ui:define name="footer">
  This is fixed footer
 </ui:define>
</ui:composition>
...
<!-- 模板适配页面 -->
...
<ui:composition template="template2.xhtml">
 <ui:define name="content">
  ...
 </ui:define>
</ui:composition>
...

我们可以将模板的嵌套理解为一个栈的结构,该栈内持有了所有的<ui:composition>这类标签的实例,当一个包含<ui:insert>标签的模版被求值时,它将从栈内去遍历寻找匹配name属性的实例,直到找到为止。所以当用户访问上面的模板适配页面时,出现的是两套模板嵌套的结果,即:

...
  <div id="main" class="main">
   <div id="header" class="header">
    This is fixed header
   </div>
   <div id="content" class="content">
     ...
   </div>
   <div id="footer" class="footer">
    This is fixed footer
   </div>
  </div>
...

除了使用<ui:composition>标签进行模板的导入外,还可以使用<ui:include>进行页面的导入,详情请见Facelets构件文档中的说明。

19.2.1.4. 参数传递

基于前面关于Facelets模板功能的说明,大家已经熟悉如何在页面之间传递内容片段。除了传递内容片段外,Facelets还能使用param标签,进行参数的传递。先看下面的示例:

<!-- 模板页面文件:template.xhtml -->
...
  <span class="title">
     Hello,#{user}!
     Hello,<h:outputText value="#{current}"/>,This is the same!
  </span>
.....
<!-- 模板适配页面 -->
....
 <ui:include src="ddd.xhtml">
   <ui:param name="user" value="Tester"/>
   <ui:param name="current" value="Duke"/>
 </ui:include>
....

当我们访问模板适配页面时,<ui:param>会自动在模板页面内根据name的属性值进行匹配,匹配成功后,将相应的内容插入,例如上例中,将“Tester”替换#{user},“Duke”替换#{current},即:

 ....
 <span class="title">
     Hello,Tester!
     Hello,Duke,This is the same!
 </span>
....

除了在<ui:include>标签中使用<ui:param>进行参数传递外,还可以在任何使用define标签的地方(composition或decorate标签中)使用param标签,用于传递一些额外的参数或对象。有兴趣的读者可以下去尝试一下。