模板和复合组件是 Java™Server Faces (JSF) 2 的两个功能强大的特性,借助这两个特性,您就可以实现易于修改和扩展的用户界面。
JSF2 现在的默认显示技术 — Facelets — 就是一个模板框架,在很大程度上基于的是 Tiles。JSF 2 还提供了一个功能强大的机制,称为复合组件,该机制构建在 Facelets 的模板特性之上,因此,在无需任何 Java 代码和 XML 配置的情况下就可以实现定制组件。
一、JSF 2 模板
JSF 2 在很多方面都支持 DRY(Don't Repeat Yourself) 原则,其中之一就是通过模板。模板能够封装在应用程序视图中十分常见的功能,因此该功能只需被指定一次。在 JSF 2 应用程序中,一个模板可供多个组装(compositions)用于创建视图。
应用程序包含多个具有相同布局的视图。JSF 模板功能让您能够在一个模板内封装该布局 — 及其他共享文件,比如 JavaScript 和 Cascading Style Sheets(CSS)。
(1)、模板:/templates/masterLayout.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:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<f:view contentType="text/html; charset=utf-8"/>
<h:head>
<h:outputScript library="js" name="util.js" target="head"/>
<h:outputStylesheet library="css" name="public.css" target="body"/>
<title>${title}</title>
</h:head>
<body>
<div id="mainbox">
<div id="header">
<ui:insert name="head">
<ui:include src="/sections/shared/header.xhtml"/>
</ui:insert>
</div>
<div id="main">
<ui:insert name="content"/>
</div>
<div id="footer">
<ui:insert name="foot">
<ui:include src="/sections/shared/footer.xhtml"/>
</ui:insert>
</div>
</div>
</body>
</html>
说明:
模板通过 <ui:insert> 标记将内容插入到布局中。如为 <ui:insert> 标记指定了主体,JSF 会将此标记的主体作为默认内容。借助 <ui:define> 标记,使用此模板的那些封装可以定义内容或者覆盖默认内容。
(2)、使用模板
a、header:/sections/share/header.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:util="http://java.sun.com/jsf/composite/components/util">
<ui:composition>
<util:icon id="waveImg" image="#{resource['images/wave.med.gif']}" actionMethod="#{hello.homePage}"/>
</ui:composition>
</html>
b、footer:/sections/share/footer.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:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<f:view contentType="text/html; charset=utf-8"/>
<div id="footer_content">
<div id="site_nav">
<ul>
<li><a href="#">广告服务</a></li>
<li><a href="#">黑板报</a></li>
<li><a href="#">关于我们</a></li>
<li><a href="#">联系我们</a></li>
<li class="last"><a href="#">友情链接</a></li>
</ul>
</div>
<div id="copyright">
©2000-2010 ColorWolf.com. All rights reserved. [ 蜀ICP备0289090980号 ]
</div>
</div>
</html>
c、hello:/views/hello.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:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition template="/templates/masterLayout.xhtml">
<ui:param name="title" value="JavaServer Faces 2.0.2 Demo"/>
<ui:define name="content">
<div style="margin: 5px; color: green;font-weight: bold;">
欢迎您,<h:outputText value="#{ hello.loginName }"/>
</div>
<h:form id="helloForm" >
<h:inputText id="name" class="oneInput" value="#{hello.name}"/>
<h:commandButton id="submit" class="NormalButton" action="response" value="确 定"/>
</h:form>
</ui:define>
</ui:composition>
</html>
如果您想在hello.xhtml中,改变一下footer部分,可以用<ui:define name="foot">来完成,在<ui:composition>中增加:
<ui:define name="foot">
<ui:include src="/sections/shared/footer2.xhtml"/>
</ui:define>
说明:
模板功能背后的概念十分简单。定义一个模板来封装在多个视图中常见的功能。每个视图由一个组装和一个模板组成。
当 JSF 创建视图时,它加载组装的模板,然后将由组装所定义的内容插入模板。
JSF 2 鼓励
使用较小的视图段组装视图。模板封装了常见功能,进而将视图分成了更小的块。JSF 2 还提供了一个 <ui:include> 标记,这个标记可以让您将视图进一步分成更小的功能块。
使用组合的方式,遵循 DRY 原则。就像下面这样用:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets">
<div class="placesSearchForm">
<div class="placesSearchFormHeading">
#{msgs.findAPlace}
</div>
<ui:include src="addressForm.xhtml">
<ui:include src="logoutIcon.xhtml">
</div>
</ui:composition>
二、复合组件
JSF 2 综合了 Facelets 模板、资源处理和一个简单的命名约定来实现复合组件。复合组件,正如其名字所示,让您能够从现有组件组装一个新组件。
一般情况下,是在 resources 目录下的 XHTML 内实现复合组件,并将它们完全通过约定链接到一个名称空间和标记。
要使用复合组件,需要声明一个名称空间并使用标记。此名称空间通常为 http://java.sun.com/jsf/composite 外加目录名,这个目录就是 resources 目录下组件所在之处。组件名本身是其 XHTML 文件的名字,只不过没有 .xhtml 扩展名。这种约定消除了对配置的需要。
(1)、icon 组件:一个简单的复合组件
a、组件定义文件:(/resources/components/util/icon.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:h="http://java.sun.com/jsf/html"
xmlns:composite="http://java.sun.com/jsf/composite">
<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="image"/>
<composite:attribute name="actionMethod"
method-signature="java.lang.String action()"/>
<composite:attribute name="styleClass" default="icon" required="false"/>
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<h:form>
<h:commandLink action="#{cc.attrs.actionMethod}" immediate="true">
<h:graphicImage value="#{cc.attrs.image}"
styleClass="#{cc.attrs.styleClass}"/>
</h:commandLink>
</h:form>
</composite:implementation>
</html>
说明:
icon 组件包含两节:<composite:interface> 和 <composite:implementation>。<composite:interface> 节定义了一个界面,可用来配置此组件。icon 组件具有两个属性:image 和 actionMethod,前者定义了组件的外观,后者定义了组件的行为。
<composite:implementation> 节包含组件的实现。它使用 #{cc.attrs.ATTRIBUTE_NAME} 表达式来访问组件的界面内定义的属性。(cc 是 JSF 2 表达式语言中的保留关键字,代表的是复合组件。)
icon 组件用 <h:graphicImage> 的 styleClass 属性为其图像指定了一个 CSS 类。它是一个可选的 CSS 类,使用组件时,可以另指定名字覆盖默认的icon。
b、使用icon组件
<!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:util="http://java.sun.com/jsf/composite/components/util">
<ui:composition>
<util:icon id="waveImg" image="#{resource['images/wave.med.gif']}" actionMethod="#{hello.homePage}"/>
</ui:composition>
</html>
(2)、login 组件:一个完全可配置的组件
a、组件定义:/resources/components/util/login.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:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:composite="http://java.sun.com/jsf/composite">
<!-- INTERFACE -->
<composite:interface>
<composite:actionSource name="loginButton" targets="form:loginButton"/>
<composite:attribute name="loginButtonText" default="Log In" required="true"/>
<composite:attribute name="loginPrompt"/>
<composite:attribute name="namePrompt"/>
<composite:attribute name="passwordPrompt"/>
<composite:attribute name="loginAction"
method-signature="java.lang.String action()"/>
<composite:attribute name="managedBean"/>
<composite:attribute name="textInput" default="oneInput" required="false"/>
<composite:attribute name="btnInput" default="NormalButton" required="false"/>
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<h:form id="form" prependId="false">
<div class="prompt">
#{cc.attrs.loginPrompt}
</div>
<h:panelGroup>
<h:panelGrid columns="2" cellpadding="5" cellspacing="2" style="text-align:left;height:50px;">
#{cc.attrs.namePrompt}
<h:inputText id="name" value="#{cc.attrs.managedBean.name}" styleClass="#{cc.attrs.textInput}"/>
#{cc.attrs.passwordPrompt}
<h:inputSecret id="password" value="#{cc.attrs.managedBean.password}" styleClass="#{cc.attrs.textInput}"/>
</h:panelGrid>
<h:panelGrid columns="2" cellpadding="5" cellspacing="2" style="text-align:center;width:200px;">
<h:commandButton id="loginButton"
value=" #{cc.attrs.loginButtonText} "
action="#{cc.attrs.loginAction}" styleClass="#{cc.attrs.btnInput}"/>
<input type="reset" class="NormalButton" value=" 取 消 "/>
</h:panelGrid>
</h:panelGroup>
</h:form>
<div class="error" style="padding-top:10px;">
<h:messages layout="table"/>
</div>
</composite:implementation>
</html>
在 login 组件的界面,我已经在 loginButton 名称下公开了 Log In 按钮。该名称所针对的是位于 form 表单内的 Log In 按钮,因此 targets 属性的值为:form:loginButton。
Log In 按钮相关联的动作侦听器:
package com.logcd.listener;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
public class LoginActionListener implements ActionListener {
public void processAction(ActionEvent e)
throws AbortProcessingException {
System.out.println("logging in ...........");
}
}
b、使用组件
<!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:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:util="http://java.sun.com/jsf/composite/components/util">
<ui:composition template="/templates/masterLayout.xhtml">
<ui:param name="title" value="JavaServer Faces 2.0.2 Demo"/>
<ui:define name="content">
<div style="margin: 8px 0px;">
<util:login loginPrompt="#{msgs.loginPrompt}"
namePrompt="#{msgs.namePrompt}"
passwordPrompt="#{msgs.passwordPrompt}"
loginAction="#{user.login}"
loginButtonText="#{msgs.loginButtonText}"
managedBean="#{user}">
<f:actionListener for="loginButton"
type="com.logcd.listener.LoginActionListener"/>
</util:login>
</div>
</ui:define>
</ui:composition>
</html>
要在faces-config.xml中加载资源文件:
<resource-bundle>
<base-name>Messages</base-name><!--根为classpath-->
<var>msgs</var>
</resource-bundle>
说明:
你还可以实现实现嵌套组件,即在定义组件时,使用已定义过的组件。表达式 #{cc.parent.attrs.location.ATTRIBUTE_NAME} 的使用。可以使用一个复合组件的 parent 属性来访问父组件的属性,这一点极大地方便了组件的嵌套。
但是,无需严格依赖于嵌套组件中的父属性,可以将属性从父组件传递给其内嵌套的组件,与向其他任何组件(不管嵌套与否)传递属性无异。
是选择实现组件-显式属性,还是选择依赖于父属性,这是耦合和方便性之间的权衡问题。