实现图形JSF组件
很简单地构建一个纯HTML无法轻松实现的图形Web应用程序组件
作者:Marc Durocher
http://dev2dev.bea.com.cn/techdoc/wlworkshop/2005070704.html

<table width="675" border="0"> <tr> <td width="271"> </td> <td width="394"> 开发人员认为,如果有合适的工具来创建交互式Web界面,他们就能将时间集中在核心需求和定制上,并在规定时间内及时得交付应用程序。与其他技术如JavaServer Pages或Apache Struts 相比,JavaServer Faces (JSF)技术为创建交互式Web应用程序带来了很多便利。JSF在程序逻辑和GUI表示之间划出一条清晰的界限,提高了对Web程序的维护能力,并为Web用户界面组件的开发和重用提供了一个框架。

  如今,许多Web应用程序开发人员都在转而使用JSF,但是他们发现,预先定制的JSF UI组件受到基本DHTML窗口部件的限制。监管或业务流程监控之类的高级应用程序需要能与JSF框架兼容的高级可视化组件。JSF框架的标准化使它易于开发能够重用的自定义Web GUI组件。另外,Web组件开发商现在能提供更复杂的组件,并承诺Web应用程序开发人员能够轻松地使用这些组件。此类JSF用户界面组件必须集成并部署到JSF运行时框架中去,并在其中完全展开,还必须在设计时很好地集成到提供JSF支持的IDE中去。

</td> </tr> </table>
开发人员认为,如果有合适的工具来创建交互式Web界面,他们就能将时间集中在核心需求和定制上,并在规定时间内及时得交付应用程序。与其他技术如JavaServer Pages或Apache Struts 相比,JavaServer Faces (JSF)技术为创建交互式Web应用程序带来了很多便利。JSF在程序逻辑和GUI表示之间划出一条清晰的界限,提高了对Web程序的维护能力,并为Web用户界面组件的开发和重用提供了一个框架。

  如今,许多Web应用程序开发人员都在转而使用JSF,但是他们发现,预先定制的JSF UI组件受到基本DHTML窗口部件的限制。监管或业务流程监控之类的高级应用程序需要能与JSF框架兼容的高级可视化组件。JSF框架的标准化使它易于开发能够重用的自定义Web GUI组件。另外,Web组件开发商现在能提供更复杂的组件,并承诺Web应用程序开发人员能够轻松地使用这些组件。此类JSF用户界面组件必须集成并部署到JSF运行时框架中去,并在其中完全展开,还必须在设计时很好地集成到提供JSF支持的IDE中去。

  尽管JSF带来了标准用户界面框架,但对于开发第一个自定义JSF组件而言,还是存在几个缺陷和漏洞。让我们看看怎样创建一个纯HTML无法轻松创建的图形JSF组件。图形JSF组件的特点不仅要求生成DHTML,而且还需要对图像生成和客户端交互提供补充支持。我们将以一个图形组件的例子来阐述这些特点。该图形组件能够提供曲线图,并为各种客户端导航和交互提供便利。我们还会了解到将该图形组件集成到JSF-enabled IDE中所需要的步骤。通过理解图形组件的设计方法,您将会更好地理解如何实现JSF组件,而这应该能使您开发出定制的JSF图形组件。

什么是JSF?

  JSF是一种能够简化Web应用程序表示层结构的标准服务器端框架。定义JSF框架的JSR 127(参见参考资料)带有一个能提供基本UI组件(如输入栏和按纽)的参考实现。您可以将可重用用户界面组件集中起来创建Web页,将这些组件绑定到应用数据源上,并用服务器端事件控制程序处理客户端事件。根据说明书介绍,组件供应商能编写与JSF运行时框架集成的组件,并将其集成到在设计时与JSF兼容的IDE中去。

  从很大程度上讲,JSF组件同在HTML 2.0技术要求下可用的HTML组件和标签直接相符合。对许多Web应用程序而言,这套相对简单的组件是够用的。然而,许多应用程序如监管或监控程序需要更复杂的数据显示与交互,比如制表、制图和映射。由于JSF组件在HTML中直接提交复杂图形小部件的能力有限,所以设计这些高级组件的能力并不突出。解决方案要求服务器端组件向客户传输图像,却会给自身带来问题,因为在基本HTML图像上进行交互要受到限制。最后,使用JavaScript时,必须能调用客户端交互来使用户能对数据进行导航和交互。

  让我们看看开发一个简单的、将CSS输入HTML页面的JSF组件需要哪些步骤。当开发高级JSF图形组件时,这一简单组件的描述和代码样本会作为背景。图1显示了如何使用即将开发的组件,并显示将要得到的结果。使用这种组件的好处是能够通过改变某个JSF动作的组件值,来改变整个页面的外观。

图1:显示了我们如何使用一个非常简单的JSF组件将CSS输入某个HTML页面并得出结果。

开发组件

  JSF组件包含若干个Java类和配置文件。为创建一个自定义JSF组件,您需要开发一个扩展JSF基本组件类的Java类;为默认呈现软件包开发呈现程序;开发一个将在JSP页面中用于描述标签的Java类;编写一个标签库定义(TLD)文件;编写JSF配置文件。让我们更深入地了解这5个步骤。

  开发组件Java类。组件类负责管理代表组件状态的属性。因此,我们必须根据组件的行为(如输入组件或输出组件),给组件选择适当的基类(参见清单1)。这里描述的组件可进行扩展javax.faces.component.UIOutput,以显示指向某个样式表文件的URL,或内联式样式表的内容。该组件可用于在JSF动作中将某个样式表转换成另一个样式表。关联属性规定着值的类型:要么是一个URL,要么是内联样式。该组件还必须能够在向服务器发送请求期间,使用经过JSF框架处理的对象,来存储并修复自己的状态。组件的状态由重建对象所需的重要属性值组成。JSF框架自动调用saveState()和restoreState()方法,我们可以在组件中实现这两种方法来达到这一目标。

清单1. 组件类管理显示组件状态的属性。可依据组件的行为,为其选择一个适当的基类。在本例中,该组件扩展javax.faces.component.UIOutput,以显示指向某个样式表文件的URL,或者某个内联式样式表的内容。

import javax.faces.component.*;

public class CSSComponent extends UIOutput {

private Boolean link;

public String getFamily() {

return "faces.CSSFamily";

}

public boolean isLink() {

if (link != null)

return link.booleanValue();

ValueBinding vb = getValueBinding("link");

if (vb != null) {

Boolean bvb = (Boolean) vb.getValue(

FacesContext.getCurrentInstance());

if (bvb != null)

return bvb.booleanValue();

}

return false;

}

public void setLink(boolean link) {

this.link = new Boolean(link);

}

public Object saveState(FacesContext context) {

return new Object[] { super.saveState(context),

link };

}

public void restoreState(FacesContext context,

Object stateObj) {

Object[] state = (Object[]) stateObj;

super.restoreState(context, state[0]);

link = (Boolean) state[1];

}

}

开发呈现程序。呈现程序有两个作用。第一,呈现程序负责发送适当的HTML程序段,该程序段能在客户端中呈现组件。通常情况下,这个HTML程序段由一些适于呈现整个Web浏览器的HTML标签组成。此JSF生存周期称作编码阶段或呈现—响应阶段。该响应阶段还能发送增强客户端交互性的JavaScript代码。

  呈现程序的第二个作用是解析来自客户端的数据,从而对服务器端的组件状态进行更新(如用户在文本字段输入的文本)。标准呈现程序软件包具有强制性,但也可以提供其他呈现程序软件包,用于提供可替换的客户端表示法或SVG之类的语言(参见参考资料)。通过检验组件的连接属性,您实现的呈现程序(参见清单2)将选择在HTML页面中发送的CSS样式。

清单2. 标准呈现程序软件包具有强制性,但是,您可以使用其他呈现程序软件包,来提供可替换的客户端表示法或语言。通过检验组件的连接属性,您实现的呈现程序将选择在HTML页面中发出的CSS样式。

import javax.faces.component.UIComponent;

import javax.faces.context.FacesContext;

import javax.faces.context.ResponseWriter;

import javax.faces.render.Renderer;

public class CSSRenderer extends Renderer {

public void encodeEnd(FacesContext context,

UIComponent component)

throws IOException {

super.encodeEnd(context, component);

if (component instanceof CSSComponent) {

CSSComponent cssComponent  =

(CSSComponent) component;

String css = (String)cssComponent.getValue();

boolean isLink = cssComponent.isLink();

if (css != null)

if (isLink) 

context.getResponseWriter().write(

"<link> type='text/css' rel='stylesheet'

href='" + css + "'/>");

else

context.getResponseWriter().write(

"