UML软件工程组织

AJAX和Web开发新技术:Dynamic Faces
作者: 刘彦青编译  出处: 天极开发

Project Dynamic Faces是数个扩展JavaServer Faces技术的的项目之一。Project Dynamic Faces是一个创新型的项目,提供了向基于JavaServer Faces技术的应用软件增添Ajax功能的方法。它使我们能够让应用软件已经在使用的任何JavaServer Faces组件支持Ajax功能。我们无需对组件进行修改就能够使它们支持Ajax,我们也无需对应用软件进行任何修改就可以使它具有Ajax的魔力。

要使应用软件具有Ajax魔力,我们必须首先确定应用中希望Ajax功能更新的网页部分。象基于JavaServer Faces技术的开发人员了解的那样,JavaServer Faces网页是由组件树表示的。利用Dynamic Faces,我们能够确定组件树中的哪个组件会受益于异步更新。就象使用Ajax更新代表网页的HTML DOM树的一部分那样,我们使用Dynamic Faces更新代表JavaServer Faces网页的组件树的一部分。因此,Dynamic Faces机制对于Ajax和JavaServer Faces开发人员而言是熟悉的。

更重要的是,Dynamic Faces使用JavaServer Faces组件模式,使我们能够以一种更有效的方式利用Ajax功能。由于组件模式的协作特性,一些网页组件上的JavaScript事件能够触发该网页上任何数量的其它组件的异步更新。Dynamic Faces使得这些异步更新只是向服务器发送的一次Ajax请求的结果,而不是导致每次异步更新的Ajax请求的结果。

Dynamic Faces还利用JavaServer Faces组件模式有效地管理客户机端和服务器端的状态。当Dynamic Faces更新客户机端上的组件状态时,它更新的只是已经改变的组件而不是整个树的状态。最好的一点是Dynamic Faces在后台完成所有这些操作,而且是以一种与JavaServer Faces技术的生命周期完全一致的方式完成的。

除了简化向应用软件增添Ajax功能外,Dynamic Faces还向我们提供了增添Ajax功能的方法的灵活性。这篇文章将讨论利用Dynamic Faces使应用软件更具交互性和活力的三种方法:

·利用Dynamic Faces提供的定制ajaxZone标签确定组件树中需要被Ajax化的部分。

·利用Dynamic Faces提供的JavaScript库向单个组件增添Ajax功能。

·在一个网页中增添支持Ajax的组件,例如jMaki widget。

在学习这些技术前,我们先来看看应用软件如何才能使用Dynamic Faces技术。

开发利用Dynamic Faces的应用软件

通过向一个标准的JavaServer Faces 1.2实现中增添Ajax功能,Dynamic Faces利用了JavaServer Faces技术的运行时间库的可扩展性。 Dynamic Faces的核心是定制的Lifecycle和ViewRoot实现。这二个实现是JavaServer Faces技术提供的标准Lifecycle和ViewRoot实现的扩展, 一个标准的Lifecycle对象代表JavaServer Faces生命周期的一个实例,一个标准的ViewRoot对象代表一个组件树的根。联合使用定制Lifecycle对象和定制ViewRoot对象,使JavaServer Faces生命周期能够处理Ajax事务,在无需对整个网页更新的情况下重新显示组件树的一部分。这些定制实现服从于不支持Ajax请求的标准实现。

为了使JavaServer Faces技术运行时间库知道定制Lifecycle对象的存在,我们必须在配置描述器中利用一个初始化参数向FacesServlet实例报告该对象。

<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<init-param>
<param-name>javax.faces.LIFECYCLE_ID</param-name>
<param-value>com.sun.faces.lifecycle.PARTIAL</param-name>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

此外,我们还必须将Dynamic Faces依赖的Java Archive(JAR)文件添加到应用软件的web archive(WAR)文件的lib目录中。因为Dynamic Faces是基于Java Platform Enterprise Edition 5(Java EE 5)的,我们所需要的几乎所有依赖关系都已经存在。最后一个依赖是Shale Remoting,Dynamic Faces利用它从Java类路径中加载JavaScript文件和其它资源。 Shale Remoting依赖于commons-logging,因此我们必须向应用软件提供commons-logging。

最后,我们必须在使用它的每个网页中说明该Dynamic Faces标签库。 对于符合标准的非XML语法的JavaServer Pages(JSP)网页而言,这种说明如下所示:

<%@ taglib prefix="jsfExt" uri="http://java.sun.com/jsf/extensions/dynafaces" %>

对于符合XML语法的JavaServer Pages(JSP)网页而言,这种说明如下所示:

<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"
xmlns:jsfExt="http://java.sun.com/jsf/extensions/dynafaces"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">

如果使用Facelets而非JSP,语法与JSP XML的语法非常相似,如下所示:

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

好了。我们可以开始利用Dynamic Faces向应用软件中增添Ajax功能了。

作为一种手动配置应用软件的替代性方案,我们可以使用Dynamic Faces下载包,其中包括面向JSP和Facelets的空白应用软件。如果使用现成的空白应用软件,所有的设置工作已经预先完成,我们就可以开始编写网页了。

 利用ajaxZone标签更新部分网页

确定网页上哪些组件将支持Ajax的一种方式是用Dynamic Faces提供的ajaxZone定制标签封装它们。当这样做时,我们就告诉Dynamic Faces只异步更新使用ajaxZone标签确定的组件树部分。

例如,假设我们有一个带有一系列按钮的网页,客户在网上购买汽车时,可以用这些按钮选择标准或豪华装修。当客户点击一个按钮时,一系列其它组件的值会发生变化,而不会造成整个网页被更新。当客户改变一个组件的值时,汽车的价格也会改变,但不会造成整个网页的更新。

图1显示的是cardemo应用软件中网页的一个屏幕快照,它使客户能够选择汽车的配置情况。


 图1:选择汽车的配置

图1显示了二个由ajaxZone标签划分的区域。 当客户点击区域2中的一个按钮时,该区域中组件的值会发生变化。当客户改变区域2中的一个组件的值时,区域1中Your Price输出组件的值也会随之变化。

要实现这样的功能,我们需要将所有组件封装在ajaxZone标签中,如下所示:

<jsfExt:ajaxZone id="zone1">
<h:panelGrid columns="2">
<h:outputText styleClass="subtitle"
value="#{bundle.basePriceLabel}"/>
<h:outputText
binding="#{carstore.currentModel.components.basePrice}"/>
<h:outputText styleClass="subtitle"
value="#{bundle.yourPriceLabel}"/>
<h:outputText value="#{carstore.currentModel.currentPrice}"/>
</h:panelGrid>
</jsfExt:ajaxZone>
<jsfExt:ajaxZone id="zone2"
action="#{carstore.currentModel.updatePricing}">
<h:commandButton id="Standard" value="#{bundle.Standard}"
styleClass="#{carstore.customizers.Standard.buttonStyle}"
actionListener="#{carstore.choosePackage}"/>
<h:commandButton id="Deluxe" value="#{bundle.Deluxe}"
styleClass="#{carstore.customizers.Deluxe.buttonStyle}"
actionListener="#{carstore.choosePackage}"/>
<h:outputText value="#{bundle.Engine}"
styleClass="optionLabel"/>
<h:selectOneMenu styleClass="optionValue"
binding="#{carstore.currentModel.components.engine}"/>
<h:outputText value="#{bundle.Speakers}"
styleClass="optionLabel"/>
<h:selectOneRadio styleClass="optionValue"
binding="#{carstore.currentModel.components.speaker}"/>
</jsfExt:ajaxZone>

前面的代码中包含名字分别为zone1和zone2的二个域。象代码显示的那样,zone 1中只有一个输出组件,因此它不会产生任何Ajax请求; Zone 2域中包含有输入和输出组件。当客户点击2个按钮中的1个,或者选择菜单或单选按钮列表中的一个选项时,zone2中的组件会启动一个Ajax请求。这一请求将引起方法表达式#{carstore.currentModel.updatePricing}中定义的操作被调用。

当使用域时,每个Ajax事务将使得网页中的所有域被更新。前面例子的效果是,当用户选择zone 2中的任何输入组件时,Ajax功能会自动地更新zone 1中的汽车价格数据。

值得指出的是,我们无需编写一行JavaScript代码就能够实现这一例子,我们也无需任何定制组件。这一应用软件使用了我们熟知的普通而简单的JavaServer Faces组件,但它们已经能够支持Ajax功能了。

ajaxZone标签向网页创作者提供了一种使用Dynamic Faces的简单、熟悉、直观的方式。在最简单的例子中,ajaxZone标签能够向网页创作者提供所需要的功能。ajaxZone标签支持许多使我们能够进一步定制其操作的其它属性,ajaxZone文档中包含有其属性的完整清单。

下面的部分将讨论使用Dynamic Faces的另一种方法,它使我们能够细粒度地控制网页中组件的Ajax化。

 使用Dynamic Faces fireAjaxTransaction方法

为了对与Ajax相关的任务进行细粒度的控制,我们可以使用Dynamic Faces提供的内置JavaScript库。通过使用现有组件标签中合适的DynaFaces.fireAjaxTransaction JavaScript函数,我们可以对网页中组件异步更新方式有更细粒度的组件级控制。

例如,假设我们希望网页中的一些组件对一种类型的JavaScript事件━━例如onclick作出响应,并希望该网页中的其它组件对其它类型的JavaScript事件作出响应。又假设我们希望生成一个Ajax请求的每个组件能够引起组件树的不同部分被异步更新。为了完成这些任务,我们需要使用fireAjaxTransaction函数。

为了使用fireAjaxTransaction函数,需要完成下面的准备工作:

·在一个组件标签中增添一个JavaScript事件属性,例如onclick。

·将该属性的值设置成DynaFaces.fireAjaxTransaction函数。

·向该函数传递一系列参数。

下面的代码是一个简单的Hello World例子中一个网页的一部分,用户可以输入他或她的名字,点击一个按钮,应用程序会用一条包含用户名字的问候语响应用户的输入。

...
<f:view>
...
<h:form id="form" prependId="false">
...
<h:graphicImage value="wave.med.gif"/>
<p>
Hello, my name is Duke. What is your name?
<p>
<h:inputText id="input" value="#{testBean.name}"/>
<h:commandButton id="button"
actionListener="#{testBean.changeText}"
onclick="DynaFaces.fireAjaxTransaction(this,
{execute: 'input', 'button',
render: 'input', 'text'}); return false;"
value="click"/>
<p>
<h:outputText id="text" value="#{testBean.text}"/>
</h:form>
...
</f:view>

在上面的例子中,inputText标签代表一个输入域。当用户在该输入域中输入内容,并点击commandButton标签表示的按钮,就会出现下面的情况:

1、DynaFaces.fireAjaxTransaction函数执行,使得Dynamic Faces向服务器发送一个Ajax请求。

2、服务器返回一个Dynamic Faces JavaScript库处理的特别XML响应。

3、合适的库函数用新的值更新HTML DOM树。

为了告诉fireAjaxTransaction函数如何生成Ajax请求,我们向它传输一系列参数。在上面的例子中,我们向fireAjaxTransaction函数传递了2个参数。下面是调用fireAjaxTransaction函数的代码:

onclick="DynaFaces.fireAjaxTransaction(this,
{execute: 'input', 'button',
render: 'input', 'text'}); return false;"

This参数指的是代表触发该事件的按钮的标签,其它参数由指示Dynamic Faces如何处理该请求的选项组成。在这个例子中,选项是execute和render。

Execute和render选项指的是JavaServer Faces生命周期的部分,如图2所示:


 图2: Dynamic Faces如何利用execute和render选项划分JavaServer Faces技术的生命周期

Execute是在postback期间执行的生命周期部分。它包含有处理模式对象的数据转换、验证、更新阶段; Render根据对网页的请求显示该网页。

在对fireAjaxTransaction函数的调用中使用的execute选项列出了在JavaServer Faces生命周期的execute部分期间必须处理的组件的ID,Hello World例子中的这行代码如下所示。

execute: 'input', 'button'

接受用户名的input组件必须执行生命周期的execute部分,因为它的数据必须被保存到模式对象中。Button组件也应当执行生命周期的execute部分,因为Invoke Application阶段是生命周期execute片断的一部分。

当生命周期的render片断显示一个使用Dynamic Faces的网页时,作为一次Ajax请求的结果,它只显示该网页上被选定的组件。我们使用render选项显示要重新显示的组件的ID,Hello World例子中的render选项如下所示:

render: 'input', 'text'

在这一例子中,作为Ajax请求的结果,生命周期的render片断会重新显示网页上的input和text组件。当用户点击该按钮时,代表input组件的输入字段和代表text组件的输出文本会被再次显示。输入字段会被重新显示,清除在点击该按钮前用户输入的值。输出文本会被重新显示,显示包含在点击该按钮前用户在输入域中输入的值的信息。该网页上的其它组件无需被重新显示。

除了execute和 render选项外,我们还可以使用其它选项,进一步定制Dynamic Faces处理事件的方式。需要记住的是,使用fireAjaxTransaction函数使我们能够对网页中的哪些组件会得益于Ajax有更多的控制。事实上,fireAjaxTransaction函数让我们能够能够使网页中的任何组件支持Ajax,而无需编写JavaScript或任何其它代码。

 联合使用Dynamic Faces和jMaki

至此,我们已经学习了如何使用Dynamic Faces重新显示支持Ajax的JavaServer Faces组件。但是,如何增添曾经在基于JavaServer Faces的应用软件中看到的支持Ajax的widget呢?

我们可以利用Project jMaki将喜欢的widget封装在JavaServer Faces组件中。这样,我们既能够享受到JavaServer Faces组件模式的好处,也能够获得使用被封装为JavaServer Faces组件的widget的灵活性。 同时,我们无须编写为现有组件实现Ajax功能的JavaScript代码,以及为widget创建JavaServer Faces组件所要求的Java平台代码。

如何联合使用jMaki和Dynamic Faces呢?对于网页创作者而言,这非常简单,就是将与jMaki widget相关的标签拖到网页中。
为了联合使用jMaki widget和Dynamic Faces,widget开发人员需要对jMaki widget的组件文件作一些小小的修改。这些修改使jMaki widget能够充分利用JavaServer Faces技术提供的组件状态管理系统,正确地转换Dynamic Faces要求的header。修改的细节超出了三篇文章的讨论范围,读者可以参阅相关资料。

Dynamic Faces开发团队已经完成了转换3个与Dynamic Faces.联合使用的jMaki widget所需要的工作。被转换的jMaki widget是script.aculo.us in-place editor widget、Dojo fisheye widget、Dojo inline-editor widget。

除了转换fisheye widget,开发团队还修改了jMaki API,使widget能够触发一个JavaServer Faces价值修改事件,如下图所示:

<a:ajax name="dojo.fisheye"
value="#{fishEyeBean.selectedIndex}"
valueChangeListener="#{fishEyeBean.valueChanged}"
args="{items:[
{iconSrc:'images/150x126_jalopy.jpg',caption:'Jalopy',index:0},
{iconSrc:'images/150x126_luxury.jpg',caption:'Luxury',index:1},
{iconSrc:'images/150x126_roadster.jpg',caption:'Roadster',index:2},
{iconSrc:'images/150x126_suv.jpg',caption:'SUV',index:3}
]}"
/>

Dynamic Faces和jMaki开发团队正在加紧工作,确保所有的jMaki widgets支持Dynamic Faces。

现在,让我们来站在网页创作者的角度来讨论这一问题。要想搞明白如何联合使用jMaki script.aculo.us in-place editor widget和 Dynamic Faces,我们在一个JavaServer Faces数据表组件中包含该widget,使我们能够编辑该表中一个单元的值。

在JSP网页中,我们必须说明要求的标签库,以及Dynamic Faces和jMaki标签,如下所示:

<%@taglib prefix="f"
uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h"
uri="http://java.sun.com/jsf/html"%>
<%@taglib prefix="jsfExt"
uri="http://java.sun.com/jsf/extensions/dynafaces"%>
<%@taglib prefix="a"
uri="http://java.sun.com/jmaki-jsf" %>

下面的代码添加的是<jsfExt:scripts />标签:

<f:view>

<html>
<head>
<title>Table with jMaki</title>
<jsfExt:scripts />
</head>
<body>

这一标签显示Dynamic Faces所要求的JavaScript文件的<script>元素。

最后,通过包含一个指定被封装为jMaki widget 的in-place editor的 jMaki ajax标签,我们就将该widget添加到了网页上。

<h:form>

<h:dataTable
...
rows="10" binding="#{ResultSetBean.data}"
value="#{ResultSetBean.list}"
var="customer">
<h:column>
<f:facet name="header">
<h:outputText value="Account Id"/>
</f:facet>
<h:outputText id="accountId"
value="#{customer.accountId}"/>
</h:column>

<h:column>
<f:facet name="header">
<h:outputText value="Customer Name"/>
</f:facet>
<a:ajax name="scriptaculous.inplace"
value="#{customer.name}"/>
</h:column>
</h:dataTable>
...

</h:form>
</body>
</html>
</f:view>

图3显示的是上述网页:
 
 图3:用户修改一个单元的值之前

图4显示的是当用户点击Customer Name列中一个单元的链接时的情况:


 图4: 用户点击Customer Name列中一个链接后的情况

需要注意的是,当用户点击Customer Name列中的一个元素时,客户名字元素会被一个输入组件、一个"OK"按钮、一个"Cancel"链接所取代,使用户能够编辑当前的客户名字。如果用户点击"Cancel"而不是"OK",该单元就会被重新显示为原来的值;如果用户输入一个值,并点击"OK"按钮,新的值就会被利用Ajax技术发送到服务器,使该模式更新为新的值。 然后,被编辑的单元就会被重新显示。 图5显示的是是单元被修改后的网页:


 图5:在用户向服务器提交新的值后,网页重新显示为新的值

结论

Project Dynamic Faces向我们提供了一种向基于JavaServer Faces的应用软件中添加Ajax功能的灵活、有效的方式,而无需放弃JavaServer Faces组件模式的任何优势。在内置的JavaScript库、Ajax实现、Dynamic Faces提供的组件交互模式的帮助下,我们会发现利用Dynamic Faces添加Ajax功能更容易了。基于JavaServer Faces的应用软件能够得益于jMaki widget提供的更多的灵活性。

 

版权所有:UML软件工程组织