早在2000年,当我活跃在JavaServer Pages(JSP)邮件列表上时,我遇到了Craig McClanahan,他正在开发一个名为Struts的新生Web框架。 那时,当我从Swing转到服务器端Java编程时,我实现了一个小型框架,该框架将JSP视图的布局与其内容分开,其实质类似于Swing的布局管理器。 Craig问我是否要将我的模板库包含在Struts中,我很同意。 与Struts 1.0捆绑在一起的Struts模板库成为Struts流行的Tiles库的基础,该库最终成为顶级Apache框架。
今天,JSF 2的默认显示技术Facelets是一个很大程度上基于Tiles的模板框架。 JSF 2还提供了一种称为复合组件的强大机制,该机制以Facelets的模板功能为基础,因此您可以在没有Java代码和XML配置的情况下实现自定义组件。 在本文中,我将向您介绍模板组件和复合组件,并提供三个使您充分利用JSF 2的技巧:
秘诀1:保持干燥 提示2:乐于助人 提示3:思考乐高积木
Facelets和JSF 2
在标准化开源Facelets实现时,JSF 2专家组对基础Facelets API进行了一些更改,但保留了与标记库的向后兼容性。 这意味着使用Facelets的开源版本实现的现有视图应与JSF 2一起使用。
您可以在Rick Hightower的文章“ Facelets像手套一样适合JSF ”和“ Advanced Facelets编程 ”中找到有关Facelets的许多功能的更多信息。
秘诀1:保持干燥
在我作为软件开发人员的第一份工作中,我为基于UNIX®的计算机辅助设计和计算机辅助制造(CAD / CAM)系统实现了GUI。
最初一切都进行得不错,但是随着时间的流逝,我的代码变得越来越麻烦。 在发布时,该系统已经足够脆弱,以至于我害怕修复错误,并且发布引发了稳定的错误报告流。
如果我在该项目上遵循DRY原则(不要重复自己),那么我本可以为自己省很多麻烦。 DRY原理由Dave Thomas和Andy Hunt创造(请参阅参考资料 )指出:
每条知识都必须在系统中具有单一,明确,权威的表示形式。
我的CAD / CAM应用程序不是DRY,而是关注点之间的耦合过多,因此一个区域的更改导致了其他地方的意外更改。
JSF 1在多个方面都违反了DRY原理,例如,强迫您提供托管Bean的两种表示形式-一种以XML表示,另一种以Java代码表示。 由于需要多种表示形式,因此创建和更改托管Bean变得更加困难。 正如我在第1部分中向您展示的那样,JSF 2允许您使用注释而不是XML来配置托管Bean,从而为托管Bean提供单一的权威表示。
除了托管bean,即使是看似良性的做法(例如,在所有视图中都包含相同的样式表)也违反了DRY原则,并可能引起惊ster。 如果更改样式表的名称,则必须更改多个视图。 如果可以的话,最好封装样式表包含的内容。
DRY原则也适用于您的代码设计。 例如,如果有多个方法都包含用于遍历树的代码,则最好将树遍历算法封装在一个子类中。
在实现UI时保持DRY尤为重要,因为在UI中(可以说)大多数更改是在开发过程中发生的。
JSF 2模板
JSF 2支持DRY原理的众多方式之一是模板化 。 模板封装了应用程序视图之间共有的功能,因此您只需指定一次即可。 一个模板由多个组合使用,以在JSF 2应用程序中创建视图。
我在第1部分中介绍的places应用程序具有三个视图,如图1所示:
图1a。 位置应用程序的视图:登录名,源查看器和位置
图1b。 位置应用程序的视图:登录名,源查看器和位置
图1c。 位置应用程序的视图:登录名,源查看器和位置
与许多Web应用程序一样,places应用程序包含共享同一布局的多个视图。 通过JSF模板,您可以将该布局以及其他共享工件(如JavaScript和级联样式表(CSS))封装在模板中。 清单1是图1所示的三个视图的模板:
清单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:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>
<ui:insert name="windowTitle">
#{msgs.placesWindowTitle}
</ui:insert>
</title>
</h:head>
<h:body>
<h:outputScript library="javascript" name="util.js" target="head"/>
<h:outputStylesheet library="css" name="styles.css" target="body"/>
<div class="pageHeading">
<ui:insert name="heading">
#{msgs.placesHeading}
</ui:insert>
</div>
<div class="menuAndContent">
<div class="menuLeft">
<ui:insert name="menuLeft"/>
</div>
<div class="content" style="display: #{places.showContent}">
<ui:insert name="content"/>
</div>
<div class="menuRight">
<ui:insert name="menuRight">
<ui:include src="/sections/shared/sourceViewer.xhtml"/>
</ui:insert>
</div>
</div>
</h:body>
</html>
清单1中的模板为所有应用程序视图提供了以下基础结构:
HTML <head> , <body>和<title> 默认标题(可以被使用模板的合成所覆盖) CSS样式表 一些实用JavaScript <div>形式的布局及其对应CSS类 标题的默认内容(可以覆盖) 右键菜单的默认内容(可以覆盖)
如清单1所示,模板使用<ui:insert>标记将内容插入其布局。
如果您为<ui:insert>标记指定主体,就像我为清单1中的窗口标题,标题和右键菜单所做的那样,JSF会将标记的主体用作默认内容 。 使用模板的合成可以使用<ui:define>标记定义内容或覆盖默认内容,如清单2所示,清单2显示了登录视图的标记:
清单2.登录视图
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="/templates/masterLayout.xhtml">
<ui:define name="menuLeft">
<ui:include src="/sections/login/menuLeft.xhtml"/>
</ui:define>
<ui:define name="content">
<ui:include src="/sections/login/content.xhtml"/>
</ui:define>
</ui:composition>
登录视图使用模板的默认内容作为窗口标题,标题和右键菜单。 它仅定义特定于登录视图的功能:内容部分和左侧菜单。
通过为窗口标题,标题或右键菜单提供<ui:define>标记,我可以覆盖模板定义的默认内容。 例如,清单3显示了source-viewer视图( 图1中的中间图片):
清单3.源查看器视图
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="/templates/masterLayout.xhtml">
<ui:define name="content">
<ui:include src="/sections/showSource/content.xhtml"/>
</ui:define>
<ui:define name="menuLeft">
<ui:include src="/sections/showSource/menuLeft.xhtml"/>
</ui:define>
<ui:define name="menuRight">
<ui:include src="/sections/showSource/menuRight.xhtml"/>
</ui:define>
</ui:composition>
源查看器视图为内容部分和右菜单定义内容。 它还会覆盖左侧菜单的默认内容,该内容由清单1中的模板定义。
清单4显示了places视图( 图1的底部图片):
清单4. places视图
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="/templates/masterLayout.xhtml">
<ui:define name="menuLeft">
<ui:include src="/sections/places/menuLeft.xhtml"/>
</ui:define>
<ui:define name="content">
<ui:include src="/sections/places/content.xhtml"/>
</ui:define>
</ui:composition>
JSF 2模板
模板背后的概念很简单。 您定义一个模板,该模板封装了多个视图之间的通用功能。 每个视图都包含一个组成部分和一个模板。
JSF创建视图时,它将加载合成的模板,然后将合成定义的内容插入模板。
请注意清单2 ,清单3和清单4之间的相似之处。 这三个视图均指定其模板并定义内容。 还要注意,创建新视图非常容易,因为大多数视图基础结构都封装在模板和包含的文件中。
使用JSF模板的另一个有趣的方面是,清单2 ,清单3和清单4中的视图不会随时间变化很大,因此视图代码的很大一部分基本上不需要维护。
就像使用它们的视图一样,模板的变化也很少。 而且,由于您将几乎所有的通用功能封装在几乎无需维护的代码中,因此您可以集中精力查看视图的实际内容,例如登录页面左侧菜单中的内容。 下一个技巧就是关注视图的实际内容。
提示2:乐于助人
发布我的CAD / CAM GUI不久,我花了几个月的时间与一个名为Bob的开发人员一起从事一个无关的项目。 我们正在他的代码库上工作,而且令人惊讶的是,我们能够轻松地进行更改和修复错误。
我很快意识到,鲍伯的代码和我的代码之间最大的区别是,他编写了微小的方法(通常在5到15行代码之间),而他的整个系统都被这些方法拼凑在一起。 尽管在上一个项目中我一直在努力修改长方法,而这却引起了很多关注,但是鲍勃灵活地将小方法与原子功能结合在一起。 Bob的代码和我的代码之间在可维护性和可扩展性上的区别就像白天和黑夜一样,从那时起,我以微小的方法被出售。
Bob和我当时都不知道,但是我们正在使用Smalltalk中的一种称为Composed Method的设计模式(请参阅参考资料 ):
将您的软件划分为在一个抽象级别上执行一项可识别任务的方法。
使用组合方法模式的好处已得到充分证明。 (有关出色的详细说明,请参见Neal Ford的“ 演进式体系结构和紧急设计:组合方法和SLAP ”。)在这里,我将重点介绍如何在JSF视图中使用组合方法模式。
JSF 2鼓励您从较小的视图片段中构成视图。 模板封装了常见的功能,从而将视图分成较小的部分。 正如我在前面的清单中演示的那样,JSF 2还提供了<ui:include>标记,它使您可以将视图进一步划分为较小的功能。 例如,图2显示了places应用程序的登录页面的左侧菜单:
图2.登录页面的左侧菜单
清单5显示了定义菜单内容的文件:
清单5.登录视图左侧菜单的实现
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<div class="menuLeftText">
#{msgs.welcomeGreeting}
<div class="welcomeImage">
<h:graphicImage library="images" name="cloudy.gif"/>
</div>
</div>
</html>
清单5中的标记很简单,这使得文件易于阅读,理解,维护和扩展。 如果将相同的代码埋在包含实现登录视图所需的所有内容的单个XHTML长页面中,那么更改将很麻烦。
图3显示了places视图的左侧菜单:
图3.位置视图的左侧菜单
清单6显示了places视图的左侧菜单的实现:
清单6. places视图的左侧菜单的实现
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
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">
<div class="placesSearchForm">
<div class="placesSearchFormHeading">
#{msgs.findAPlace}
</div>
<h:form prependId="false">
<h:panelGrid columns="2">
#{msgs.streetAddress}
<h:inputText value="#{place.streetAddress}" size="15"/>
#{msgs.city} <h:inputText value="#{place.city}" size="10"/>
#{msgs.state} <h:inputText value="#{place.state}" size="3"/>
#{msgs.zip} <h:inputText value="#{place.zip}" size="5"/>
<h:commandButton value="#{msgs.goButtonText}"
style="font-family:Palatino;font-style:italic"
action="#{place.fetch}"/>
</h:panelGrid>
</h:form>
</div>
<util:icon image="#{resource['images:back.jpg']}"
actionMethod="#{places.logout}"
style="border: thin solid lightBlue"/>
</ui:composition>
清单6实现了一个表单,并使用了一个图标组件。 (我将讨论在该图标组件图标组件 ,不久,就目前而言,这足以知道页面作者可以用一个图标,图像和方法关联。)的注销图标的图像被显示在底部清单7显示了图3和注销图标的方法places.logout() 。
清单7. Places.logout()方法
package com.clarity;
...
@ManagedBean()
@SessionScoped
public class Places {
private ArrayList<Place> places = null;
...
private static SelectItem[] zoomLevelItems = {
...
public String logout() {
FacesContext fc = FacesContext.getCurrentInstance();
ELResolver elResolver = fc.getApplication().getELResolver();
User user = (User)elResolver.getValue(
fc.getELContext(), null, "user");
user.setName("");
user.setPassword("");
setPlacesList(null);
return "login";
}
}
对我来说, 清单6 (places视图的左侧菜单的实现)在大约30行标记处接近太多的代码阈值。 该清单有些难以理解,该代码中的两件事可以重构为它们自己的文件:表单和图标。 清单8显示了清单6的重构版本,该版本将表单和图标封装到了自己的XHTML文件中:
清单8.重构places视图的左侧菜单
<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>
清单9显示了addressForm.xhtml:
清单9. addressForm.xhtml
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:form prependId="false">
<h:panelGrid columns="2">
#{msgs.streetAddress}
<h:inputText value="#{place.streetAddress}" size="15"/>
#{msgs.city} <h:inputText value="#{place.city}" size="10"/>
#{msgs.state} <h:inputText value="#{place.state}" size="3"/>
#{msgs.zip} <h:inputText value="#{place.zip}" size="5"/>
<h:commandButton value="#{msgs.goButtonText}"
style="font-family:Palatino;font-style:italic"
action="#{place.fetch}"/>
</h:panelGrid>
</h:form>
</ui:composition>
清单10显示了logoutIcon.xhtml:
清单10. logoutIcon.xhtml
<ui:composition 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">
<util:icon image="#{resource['images:back.jpg']}"
actionMethod="#{places.logout}"
style="border: thin solid lightBlue"/>
</ui:composition>
当您从多个小文件组成视图时,您将获得Smalltalk的Composed Method模式的好处。 您还可以整理文件,以使对更改的React更加轻松。 例如,图4显示了组成places应用程序中三个视图的文件:
图4. Places应用程序的视图
我创建的三个目录-视图,部分和模板-包含用于实现places应用程序视图的大多数XHTML文件。 由于视图和模板目录中的文件很少更改,因此我可以集中精力放在sections目录中。 例如,如果要更改登录页面左侧菜单中的图标,我确切知道要去的地方:sections / login / menuLeft.xhtml。
您可以使用任何想要组织XHTML文件的目录结构。 逻辑结构使查找需要修改的代码变得容易。
除了遵守DRY原理并使用Composed Method模式之外,将功能封装在自定义组件中也是一个好主意。 组件是强大的重用机制,您应该利用这种功能。 与JSF 1不同,JSF 2使实现自定义组件变得容易。
提示3:思考乐高积木
小时候,我有两个最喜欢的玩具:化学玩具和乐高玩具。 两者都使我能够通过结合基本的构建块来创建事物,这些事物已经以软件开发的名义变成了终生的迷恋。
JSF的强项一直是其组件模型,但是直到现在为止还没有完全意识到这种强项,因为很难用JSF 1实现自定义组件。您必须编写Java代码,指定XML配置并充分了解JSF的生活。周期。 使用JSF 2,您可以实现自定义组件:
没有配置,XML或其他。 没有Java代码。 开发人员可以附加功能。 修改后即进行热部署。
对于本文的其余部分,我将引导您实现Places应用程序的三个自定义组件的实现:一个图标,一个登录面板以及一个显示地址地图和天气信息的面板。 但是首先,我将为您提供JSF 2复合组件的概述。
实施自定义组件
JSF 2结合了Facelets模板 ,资源处理(在第1部分中讨论)和简单的命名约定来实现复合组件 。 顾名思义,复合组件使您可以从现有组件组成一个组件。
您可以在XHTML的资源目录下的某个地方实现复合组件,并将它们纯粹按照约定链接到名称空间和标签。 图5显示了如何组织places应用程序的复合组件:
图5. Places应用程序的组件
要使用复合组件,您需要声明一个名称空间并使用标签。 命名空间始终为http://java.sun.com/jsf/composite加上组件在资源目录下所位于的目录的名称。 组件本身的名称是其XHTML文件的名称,不带.xhtml扩展名。 该约定消除了对任何配置的需求。 例如,要在places应用程序中使用login组件,请执行以下操作:
<html xmlns="http://www.w3.org/1999/xhtml"
...
xmlns:util="http://java.sun.com/jsf/composite/component/util">
...
<util:login.../>
...
<html>
要使用icon组件,您可以这样做:
<html xmlns="http://www.w3.org/1999/xhtml"
...
xmlns:util="http://java.sun.com/jsf/composite/components/util">
...
<util:icon.../>
...
<html>
最后,使用如下所示的place组件:
<html xmlns="http://www.w3.org/1999/xhtml"
...
xmlns:util="http://java.sun.com/jsf/composite/components/places">
...
<places:place.../>
...
<html>
icon组件:一个简单的复合组件
places应用程序使用图6所示的两个图标:
图6a。 地方信息应用程序的图标
图6b。 地方信息应用程序的图标
每个图标都是一个链接。 当用户单击图6中的左侧图标时,JSF将显示当前视图的标记,而激活右侧图标会使用户退出应用程序。
您可以为链接指定CSS类名和图像,也可以将方法附加到链接。 当用户单击关联的链接时,JSF会调用这些方法。
清单11显示了在places应用程序中如何使用icon组件来显示标记:
清单11.使用icon组件显示标记
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:util="http://java.sun.com/jsf/composite/components/util">
<util:icon actionMethod="#{sourceViewer.showSource}"
image="#{resource['images:disk-icon.jpg']}"/>
...
</html>
清单12显示了如何使用icon组件注销:
清单12.使用icon组件注销
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:util="http://java.sun.com/jsf/composite/components/util">
<util:icon actionMethod="#{places.logout}"
image="#{resource['images:back-arrow.jpg']}"/>
...
</html>
清单13显示了icon组件的代码:
清单13. icon组件
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<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:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<h:form>
<h:commandLink action="#{cc.attrs.actionMethod}" immediate="true">
<h:graphicImage value="#{cc.attrs.image}"
styleClass="icon"/>
</h:commandLink>
</h:form>
</composite:implementation>
</html>
像所有复合组件一样, 清单13中的icon组件包含两个部分: <composite:interface>和<composite:implementation> 。 <composite:interface>部分定义了可用于配置组件的接口。 icon组件具有两个属性: image (定义该组件的外观)和actionMethod (定义其行为)。
<composite:implementation>部分包含组件的实现。 它使用#{cc.attrs. ATTRIBUTE_NAME } #{cc.attrs. ATTRIBUTE_NAME }表达式,以访问在组件界面中定义的属性。 ( cc是JSF 2表达式语言中的保留关键字,代表复合组件。)
请注意, 清单13中的icon组件使用<h:graphicImage>的styleClass属性为其图像指定一个CSS类。 该CSS类的名称被硬编码为icon ,因此您只需指定具有该名称CSS类,JSF便会将该类用于应用程序中的所有图标。 但是,如果您想覆盖该CSS类名怎么办? 在那种情况下,我可以为CSS类名添加另一个属性,并提供一个默认值,当未指定该属性时,它将由JSF使用。 清单14显示了该属性的外观:
清单14.重构的icon组件
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<html xmlns="http://www.w3.org/1999/xhtml"
...
xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface>
...
<composite:attribute name="styleClass" default="icon" required="false"/>
...
</composite:interface>
<composite:implementation>
...
<h:graphicImage value="#{cc.attrs.image}"
styleClass="#{cc.attrs.styleClass}"/>
...
</composite:implementation>
</html>
在清单14中 ,我向图标组件的界面添加了一个名为styleClass属性,并在组件的实现中引用了该属性。 进行此更改后,您现在可以为图标的图像指定一个可选CSS类,如下所示:
<util:icon actionMethod="#{places.logout}"
image="#{resource['images:back-arrow.jpg']}"
styleClass="customIconClass"/>
如果您未指定styleClass属性,则JSF将使用默认值icon 。
login组件:完全可配置的组件
使用JSF 2,您可以实现完全可配置的复合组件。 例如,places应用程序包含一个login组件,如图7所示:
图7. places应用程序的login组件
清单15显示了places应用程序如何使用login组件:
清单15.使用login组件
<!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:comp="http://java.sun.com/jsf/composite/component/util">
<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.clarity.LoginActionListener"/>
</util:login>
...
</html>
清单15不仅参数化了login组件的属性(例如名称和密码提示),而且还将操作侦听器附加到组件的“ 登录”按钮上。 该按钮由login组件的界面公开,如清单16所示:
清单16. login组件
<!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:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<h:form id="form" prependId="false">
<div class="prompt">
#{cc.attrs.loginPrompt}
</div>
<panelGrid columns="2">
#{cc.attrs.namePrompt}
<h:inputText id="name" value="#{cc.attrs.managedBean.name}"/>
#{cc.attrs.passwordPrompt}
<h:inputSecret id="password" value="#{cc.attrs.managedBean.password}" />
</panelGrid>
<p>
<h:commandButton id="loginButton"
value="#{cc.attrs.loginButtonText}"
action="#{cc.attrs.loginAction}"/>
</p>
</h:form>
<div class="error" style="padding-top:10px;">
<h:messages layout="table"/>
</div>
</composite:implementation>
</html>
在登录组件的界面中,我以loginButton的名称公开了Log In按钮。 该名称以驻留在名为form的表单中的“ 登录”按钮为目标,因此, targets属性的值为: form:loginButton 。
清单17显示了与清单16中的Log In按钮关联的动作侦听器:
清单17. Log In按钮的动作监听器
package com.clarity;
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 ...........");
}
}
清单17中的动作侦听器仅用于说明目的-当用户登录时,我只是将一条消息写到servlet容器日志文件中。 但是您有一个主意:使用JSF 2,您可以实现完全可配置的组件,并且可以将功能附加到这些组件上,而无需单行Java代码或XML配置。 那是一些强大的赋。
place组件:嵌套复合组件
JSF 2使您无需任何Java代码或配置即可实现完全可配置的组件。 您还可以嵌套复合组件,这使您可以将复杂的组件分解为更小,更易于管理的部分。 例如,图8显示了place组件,其中显示了给定地址的地图和天气信息:
图8.places应用程序的place组件
清单18显示了places应用程序如何使用place组件:
清单18.使用place组件
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:places="http://java.sun.com/jsf/composite/components/places">
<h:form id="form">
<ui:repeat value="#{places.placesList}" var="place">
<places:place location="#{place}"/>
</ui:repeat>
</h:form>
</ui:composition>
清单19显示了place组件的代码:
清单19. place组件
<!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:composite="http://java.sun.com/jsf/composite"
xmlns:places="http://java.sun.com/jsf/composite/components/places">
<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="location" required="true"/>
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<div class="placeHeading">
<places:map title="Map"/>
<places:weather title="Weather"/>
</div>
</composite:implementation>
</html>
在清单19中 , place组件使用两个嵌套组件: <places:map>和<places:weather> 。 清单20显示了map组件:
清单20. map组件
<!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:composite="http://java.sun.com/jsf/composite">
<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="title"/>
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<div class="map">
<div style="padding-bottom: 10px;">
<h:outputText value="#{cc.attrs.title}"
style="color: blue"/>
</div>
<h:panelGrid columns="1">
<h:panelGroup>
<div style="padding-left: 5px;">
<i>
<h:outputText value="#{cc.parent.attrs.location.streetAddress}, "/>
</i>
<h:outputText value=" #{cc.parent.attrs.location.city}" />
<h:outputText value="#{cc.parent.attrs.location.state}"/><hr/>
</div>
</h:panelGroup>
<h:panelGrid columns="2">
<div style="padding-right: 10px;margin-bottom: 10px;font-size:14px">
#{msgs.zoomPrompt}
</div>
<h:selectOneMenu onchange="submit()"
value="#{cc.parent.attrs.location.zoomIndex}"
valueChangeListener="#{cc.parent.attrs.location.zoomChanged}"
style="font-size:13px;font-family:Palatino">
<f:selectItems value="#{cc.parent.attrs.location.zoomLevelItems}"/>
</h:selectOneMenu>
</h:panelGrid>
<h:graphicImage url="#{cc.parent.attrs.location.mapUrl}"
style="border: thin solid gray"/>
</h:panelGrid>
</div>
</composite:implementation>
</html>
复合组件重构
清单20 ( map组件的标记)对我来说有点长。 乍一看有些困难,其复杂性随后可能会带来问题。
您可以轻松地重构清单20为多个,更容易管理的文件,正如我前面没有当我在清单重构的地方视图的左侧菜单中的8 , 9 ,和10 。 在这种情况下,我将把重构作为练习留给您。
注意表达式#{cc.parent.attrs.location. ATTRIBUTE_NAME } 清单20中的 #{cc.parent.attrs.location. ATTRIBUTE_NAME } 。 您可以使用复合组件的parent属性来访问父组件的属性,这极大地简化了组件的嵌套。
但是您不必严格依赖嵌套组件中的父级属性。 就像我在清单19中的place组件中所做的那样,您可以将属性(例如地图的标题)从父级传递到其嵌套组件,就像将属性传递给任何其他组件(无论是否嵌套)一样。
这有点滑稽,但是清单21显示了weather部分:
清单21. weather组件
<!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="title"/>
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<div class="weather">
<div style="padding-bottom: 10px;">
<h:outputText value="#{cc.attrs.title}"
style="color: blue"/>
</div>
<div style="margin-top: 10px;width:250px;">
<h:outputText style="font-size: 12px;"
value="#{cc.parent.attrs.location.weather}"
escape="false"/>
</div>
</div>
</composite:implementation>
</html>
weather组件与map组件一样,使用父组件属性(天气Web服务中的天气HTML)和特定于组件的属性(标题)。 (请参阅第1部分,以了解应用程序如何获取特定位置的地图和天气信息。)
因此,在实现嵌套组件时,您可以选择。 您可以让嵌套组件依赖其父组件的属性,也可以要求父组件将属性显式传递给嵌套组件。 例如, 清单19中的place组件将 title属性显式传递给其嵌套组件,但是嵌套组件依赖于父级的属性,例如地图URL和weather HTML。
您是选择实现组件显式属性还是依赖于父级属性,这是耦合和便利之间的权衡。 在这种情况下, map和weather组件紧密依赖于其父组件( place组件),因为它们依赖于父组件的属性。 通过将所有map和weather组件的属性都指定为组件显式属性,我可以将map和weather组件与place组件分离。 但是在那种情况下,我失去了一些便利,因为place组件将需要将所有属性显式传递给map和weather组件。
下次...
在本文中,我向您展示了如何使用JSF 2来实现易于维护和通过模板组件和复合组件扩展的UI。 本系列的最后一篇文章将向您展示如何在复合组件中使用JavaScript,如何使用JSF 2的新事件模型以及如何利用JSF 2对Ajax的内置支持。
翻译自: https://www.ibm.com/developerworks/java/library/j-jsf2fu2/index.html