这节内容的目的不是给大家开发一个可用的构件,而是教大家如何开发一个自己的构件,或者如何将其它的js构件封装成一个原生构件。学习完本节内容后,大家就可以将用jQuery、Ext、YUI等js库写的构件变成自己的构件,可以像OperaMasks构件一样来使用它。
在学习本节内容时,大家不必着急按着这个步骤来编写代码,因为其中大部分工作都会由OperaMasks Studio来完成。学习时主要要看懂其中的原理,具体的开发过程及Operamasks Studio的使用请看下一节:第 28.3.1.2 节 “使用OperaMasks来开发FusionCharts Column2D构件”。
此示例的构件工程及测试案例可以到http://wiki.operamasks.org/pages/viewpage.action?pageId=9437386下载。
本节将会讲解一个FusionCharts free的Column2D构件效果的原生构件开发过程。FusionCharts free 是一个跨平台,跨浏览器的flash图表构件解决方案,能够被 ASP.NET, ASP, PHP, JSP, ColdFusion, Ruby on Rails, 简单 HTML 页面甚至PPT调用,其官方网站为http://www.fusionCharts.com。先看一下最终要的效果:
我们先不考虑构件封装,先看一下上面图中的效果用HTML代码怎么显示一个FusionCharts Column2D图表。
<html>
<head>
<script language="JavaScript" src="fusionCharts/FusionCharts.js"></script>
</head>
<body>
<div id="chartdiv"></div>
<script type="text/JavaScript">
var myChart = new FusionCharts("../FusionCharts/Column2D.swf", "myChartId", "600", "500",0 ,0, "FFFFFF","noscale","EN");
myChart.setDataURL("data.xml");
myChart.render("chartdiv");
</script>
</body>
</html>图表使用data.xml来指定数据来源,它的内容如下
<graph baseFont='宋体' baseFontSize='12' xAxisName='单位' yAxisName='用电量(万度)' formatNumberScale='0' bgcolor="FFFFFF">
<set name='物价局' value='322' color='AFD8F8' />
<set name='交通局' value='950' color='CC3333' />
<set name='市容局' value='957' color='F6BD0F' />
</graph>可以看到代码其实比较简单,FusionCharts所有构件显示的原理基本都是用一个swf来显示一个XML文件的数据成一个Flash图表。
这里的可变与不可变是相对于一个构件来说的,我们考虑一下如果我们要定义另外一个FusionCharts Column2D图表,那些代码中有哪些是可变的?哪些又是不变的?我们先将可变的部分用变量表示出来:
<html>
<head>
<script language="JavaScript" src="fusionCharts/FusionCharts.js"></script>
</head>
<body>
<div id="{container_div_id}"></div>
<script type="text/JavaScript">
var {jsvar} = new FusionCharts("../FusionCharts/Column2D.swf", "{chart_id}", "{width}", "{height}",
{debugModel}, {registerWithJS}, "{bgcolor}", "{scaleMode}", "{lang}");
{jsvar}.setDataURL("{dataURL}");
{jsvar}.render("{container_div_id}");
</script>
</body>
</html>注意:对于一个FusionCharts Column2D图表来说,"../FusionCharts/Column2D.swf"是不变的。如果是另一个FusionCharts图表,则它可变。我们要开发一个FusionCharts Column2D构件,它属于不可变部分。
通过上面的第二步,我们基本可以确定,我们定义一个FusionCharts Column2D构件,大约需要有11个属性:container_div_id、jsvar、chart_id、width、height、debugModel、registerWithJS、bgcolor、scaleMode、lang、dataURL。
再仔细考虑一下,container_div_id也略显多余,它无非就是图表的容器,我们有了构件的chart_id,我们可以根据chart_id来决定容器的id,这样用户可以少设置一个属性。可以约定一下:{container_div_id}={chart_id}+"_container",这样用户只需要一个chart_id就行了。另外还需要提供一些属性来设置那个div的样式(比如加边框、水平对齐等),当然也可以使用CSS来设置,所以再加两个属性style和styleClass。另外,所有构件除了有id属性外,还应该有binding、renderered属性。
通过这些考虑,我们已经可以写出构件的Base类了。代码如下:
publicabstract class UIColumn2DChartBase
extends UIComponentBase {
protected String jsvar;
protected int width=500;//默认宽度500 protected int height=400;//默认高度400 protected boolean debug = false;//默认为false protected boolean registerWithJS = false;//默认为false protected String bgcolor; protected String scaleMode; protected String lang; protected String style; protected String styleClass;
protected String dataURL; }
其实构件类中有很多内容的(getter/setter、saveState/resoreState等),要手工编写比较复杂。OperaMasks Studio为这个需求提供了支持,用户只需要保存Base类,这个类的所有内容会自动生成,用户不需要修改任何代码。这也是我们为什么是将构件类分为Base类和构件类的原因,Base类中所有代码都是无法自动生成的,构件类中所有代码都是可以自动生成的。
生成的构件类也完全满足我们的要求,接下来可以开始写渲染器类了。
把所有的资源文件(如js文件、CSS文件、图片文件、音乐文件等)放到META-INF\resource目录中(可以在里面建立子目录)。这个构件中我们使用了2个资源Column2D.swf和FusionCharts.js,将这两个文件放到META-INF\resource里面的fusionchart目录中。其中JS文件需要起别名(CSS和JS需要,其它文件不需要),在“构件工程资源管理器”中为此资源起一个别名(假设叫AJavaer.FusionChart)。
我们在前面已经讲了一个AJAX渲染器必须直接或间接继承自AjaxRendererBase类,而且里面有许多可以覆写的方法,现在要做的是确定应该在哪里方法里干什么。
再回头看一下第一步中的那个HTML代码:
<html>
<head>
<script language="JavaScript" src="fusionCharts/FusionCharts.js"></script>
</head>
<body>
<div id="chartdiv"></div>
<script type="text/JavaScript">
var myChart = new FusionCharts("../FusionCharts/Column2D.swf", "myChartId", "600", "500",0 ,0, "FFFFFF","noscale","EN");
myChart.setDataURL("data.xml");
myChart.render("chartdiv");
</script>
</body>
</html>首先需要关注的是在head中引入了一个FusionCharts.js文件,通过查看AjaxRendererBase类各方法的说明,发现应该使用public String[] getDependedJSPackages(FacesContext context, UIComponent component);这个方法。
@Override
public String[] getDependedJSPackages(FacesContext context, UIComponent component) {
return new String[]{"AJavaer.FusionChart"};
}接下来要关注的是那个div,这是HTML代码,我们应该在encodeHtmlBegin()、encodeHtmlChildren()、encodeHtmlEnd()这三个方法中的某一个中写,具体哪一个要看它是显示在子构件的HTML代码的前面还是后面。我们要开发的Column2D构件是不要放子构件的,所以它放在encodeHtmlBegin()或encodeHtmlEnd()中都可以,但是根据FusionCharts官方说法,这里可以放一些说明文字,比如“图形将出现这个DIV里,到时这里的字将被图形替代。”。
我们应该将放<div id="chartdiv">放在encodeHtmlBegin()中。将</div>放在encodeHtmlEnd()中。
@Override
public void encodeHtmlBegin(FacesContext context, UIComponent component) throws IOException {
//这里输出一个div出来,它的id=构件id+"_container";
//如果构件设置了style或styleClass属性,也应该渲染到这个div上
}@Override
public void encodeHtmlEnd(FacesContext context, UIComponent component) throws IOException {
//这里输出一个"</div>"出来;
}接下来要关注的就是一段JavaScript代码。那段JavaScript中有一个jsvar变量myChart,一般jsvar在初始化时声明,所以我们可以在encodeInitScriptBegin中声明这个jsvar。
@Override
public void encodeInitScriptBegin(FacesContext context, ResourceManager rm,
UIComponent component) throws IOException {
//声明一个jsvar
}根据AjaxRendererBase各方法的说明,其它的js代码应该在encodeResourceBegin()、encodeResourceChildren()、encodeResourceEnd()的某个方法中渲染。放在encodeResourceChildren()中显然是不合适的,因为这个方法是构件对子构件的渲染。本示例中放在encodeResourceBegin()和encodeResourceEnd()中都是可以的,我们放在encodeResourceBegin()中算了。
@Override
public void encodeResourceBegin(FacesContext context, ResourceManager rm,
UIComponent component) throws IOException {
//渲染JavaScript代码
//因为这个渲染是许多FusionCharts构件公共的,所以这里可能会根据构件Base类中
//的getType()方法的返回值不同,渲染不同的swf文件名
}根据上面第六步的分析,我们的渲染器Column2DAjaxRenderer应该继承自AjaxRendererBase类,并覆盖其中的getDependedJSPackages()、encodeHtmlBegin()、encodeHtmlEnd()、encodeInitScriptBegin()、encodeResourceBegin()这五个方法。这个渲染器中的代码基本都要用户自己编写。具体代码请看下一节内容。
至此构件基本开发完成了,还要进行一些配置,配置命名空间、tag名、tag名与构件类对应信息、构件与渲染器对应信息等。由于这些功能全部能由OperaMasks Studio完成,就不详细讲了,具体可以看OperaMasks Studio生成的文件的内容。
上面一节讲得比较复杂,为的是让大家掌握这个步骤,掌握其中的原理。现在我们来看一下如何使用OperaMasks Studio来开发这个构件。
在JavaEE资源管理器中空白地方右键--“新建(W)”--“Apusic工程”。

会弹出如下窗口,选择“构件工程”,然后点“下一步(N)”。

在弹出的窗口中输入“工程名称”,“OperaMasks SDK版本”选择“OperaMasks 3.0 Libraries”,如果需要使用js或css资源文件,则要选中“创建Resource-dependence.xml”前面的checkbox,如果不需要则可以去掉选择(个人经验是先选中,如果构件开发完了后还没用到js或css资源文件时再删除生成的resource-dependences.xml文件即可。因为空上文件里有一些默认代码,如果这里没有选择的话,当构件开发到一半时想使用js或css资源,需要新建这个文件,里面的内容不知道怎么写)。

然后点“下一步(N)”后会出来如下界面,输入命名空间相关的一些信息。

然后点“完成(F)”,就会自动一个构件工程,里面默认生成了一些文件。

还需要把javaee.jar包配置到构建路径中,在工程上右键--“构建路径(B)”--“配置构建路径(C)”。

在弹出的窗口中,切换到“库(L)”标签,点击“添加外部jar(X)”。在弹出的窗口中选择javaee.jar和jaxb-api.jar(要使用这个jar包中的API将Chart对象转换为xml片段)所在的路径(Apusic Server根目录的common文件夹中有这2个文件),然后“打开(O)”,效果如下图。然后“确定”,构件工程就建好了。

双击打开构件工程中“http://ajavaer.org/fusionchart”目录下的“fc.taglib.xml”文件,内容如下。里面默认配置了一个newTag1构件,选择这个构件删除它。

点击“添加”按钮添加一下新的构件,输入如下信息,然后保存。

点击“发布”按钮来生成构件Base类、构件类、渲染器类及一些配置信息。弹出下图的窗口时点击“是(Y)”。

发布后会生成以下3个文件。

双击打开构件Base类UIColumn2DBase,里面的内容如下:
package org.ajavaer.fusionchart.component;
import org.operamasks.faces.tools.annotation.ComponentMeta;
@ComponentMeta(tagName="column2D", family="org.ajavaer.fusionchart", rendererType="org.ajavaer.fusionchart.component.Column2D")
public abstract class UIColumn2DBase extends UIComponentBase {
}根据前面的说明,应该在这个类中声明构件类应该有的属性,修改后的内容应该如下:
package org.ajavaer.fusionchart.component;
import javax.faces.component.UIComponentBase;
import org.operamasks.faces.tools.annotation.ComponentMeta;
@ComponentMeta(tagName = "column2D", family = "org.ajavaer.fusionchart", rendererType = "org.ajavaer.fusionchart.component.UIColumn2D")
public abstract class UIColumn2DBase extends UIComponentBase {
protected String jsvar;
protected int width = 500;// 默认宽度500
protected int height = 400;// 默认高度400
protected boolean debug = false;// 默认为false
protected boolean registerWithJS = false;// 默认为false
protected String bgcolor;
protected String scaleMode;
protected String lang;
protected String style;
protected String styleClass;
protected String dataURL;
}修改完成保存该类,Operamasks Studio会自动生成对应的构件类UIColumn2D.java,其代码如下:
/**
* UIColumn2D.java
* DO NOT EDIT THIS FILE
* This file was automatically generated by org.operamasks.faces.tools.apt.ComponentAnnotationProcessorFactory
* at Mon Apr 12 13:53:48 CST 2010
*/
package org.ajavaer.fusionchart.component;
import org.ajavaer.fusionchart.component.UIColumn2DBase;
import org.operamasks.faces.component.GenComponentUtils;
import java.lang.reflect.Method;
import java.util.Map;
@javax.annotation.Generated(value="org.operamasks.faces.tools.apt.ComponentAnnotationProcessorFactory", date="2010-04-12T13:53:48Z")
public class UIColumn2D extends UIColumn2DBase {
public static final String COMPONENT_FAMILY = "org.ajavaer.fusionchart";
public static final String COMPONENT_TYPE = "org.ajavaer.fusionchart.component.UIColumn2D";
private final static Map<String,Method> operationMethodsMap = GenComponentUtils.createOperationMethodsMap();
public UIColumn2D() {
super.setRendererType("org.ajavaer.fusionchart.component.UIColumn2D");
}
protected Map<String,Method> getOperationMethodsMap() {
return operationMethodsMap;
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
public java.lang.String getJsvar() {
if (this.jsvar != null) {
return this.jsvar;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this, "jsvar");
if(exprValue != null){
return (java.lang.String)exprValue;
}
return null;
}
public void setJsvar(java.lang.String value) {
this.jsvar = value;
}
private boolean width_set;
public boolean isWidthSet(){
return width_set;
}
public int getWidth() {
if (this.width_set) {
return this.width;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this, "width");
if(exprValue!=null){
return (Integer)exprValue;
}
return this.width;
}
public void setWidth(int value) {
this.width = value;
this.width_set = true;
}
private boolean height_set;
public boolean isHeightSet(){
return height_set;
}
public int getHeight() {
if (this.height_set) {
return this.height;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this, "height");
if(exprValue!=null){
return (Integer)exprValue;
}
return this.height;
}
public void setHeight(int value) {
this.height = value;
this.height_set = true;
}
private boolean debug_set;
public boolean isDebugSet(){
return debug_set;
}
public boolean getDebug() {
if (this.debug_set) {
return this.debug;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this, "debug");
if(exprValue!=null){
return (Boolean)exprValue;
}
return this.debug;
}
public boolean isDebug() {
return getDebug();
}
public void setDebug(boolean value) {
this.debug = value;
this.debug_set = true;
}
private boolean registerWithJS_set;
public boolean isRegisterWithJSSet(){
return registerWithJS_set;
}
public boolean getRegisterWithJS() {
if (this.registerWithJS_set) {
return this.registerWithJS;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this, "registerWithJS");
if(exprValue!=null){
return (Boolean)exprValue;
}
return this.registerWithJS;
}
public boolean isRegisterWithJS() {
return getRegisterWithJS();
}
public void setRegisterWithJS(boolean value) {
this.registerWithJS = value;
this.registerWithJS_set = true;
}
public java.lang.String getBgcolor() {
if (this.bgcolor != null) {
return this.bgcolor;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this, "bgcolor");
if(exprValue != null){
return (java.lang.String)exprValue;
}
return null;
}
public void setBgcolor(java.lang.String value) {
this.bgcolor = value;
}
public java.lang.String getScaleMode() {
if (this.scaleMode != null) {
return this.scaleMode;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this, "scaleMode");
if(exprValue != null){
return (java.lang.String)exprValue;
}
return null;
}
public void setScaleMode(java.lang.String value) {
this.scaleMode = value;
}
public java.lang.String getLang() {
if (this.lang != null) {
return this.lang;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this, "lang");
if(exprValue != null){
return (java.lang.String)exprValue;
}
return null;
}
public void setLang(java.lang.String value) {
this.lang = value;
}
public java.lang.String getStyle() {
if (this.style != null) {
return this.style;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this, "style");
if(exprValue != null){
return (java.lang.String)exprValue;
}
return null;
}
public void setStyle(java.lang.String value) {
this.style = value;
}
public java.lang.String getStyleClass() {
if (this.styleClass != null) {
return this.styleClass;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this, "styleClass");
if(exprValue != null){
return (java.lang.String)exprValue;
}
return null;
}
public void setStyleClass(java.lang.String value) {
this.styleClass = value;
}
public java.lang.String getDataURL() {
if (this.dataURL != null) {
return this.dataURL;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this, "dataURL");
if(exprValue != null){
return (java.lang.String)exprValue;
}
return null;
}
public void setDataURL(java.lang.String value) {
this.dataURL = value;
}
@Override
public Object saveState(javax.faces.context.FacesContext context) {
return new Object[] {
super.saveState(context),
this.jsvar,
this.width,
this.width_set,
this.height,
this.height_set,
this.debug,
this.debug_set,
this.registerWithJS,
this.registerWithJS_set,
this.bgcolor,
this.scaleMode,
this.lang,
this.style,
this.styleClass,
this.dataURL
};
}
@Override
public void restoreState(javax.faces.context.FacesContext context, Object state) {
Object[] values = (Object[])state;
super.restoreState(context, values[0]);
this.jsvar = (java.lang.String)values[1];
this.width = (Integer)values[2];
this.width_set = (Boolean)values[3];
this.height = (Integer)values[4];
this.height_set = (Boolean)values[5];
this.debug = (Boolean)values[6];
this.debug_set = (Boolean)values[7];
this.registerWithJS = (Boolean)values[8];
this.registerWithJS_set = (Boolean)values[9];
this.bgcolor = (java.lang.String)values[10];
this.scaleMode = (java.lang.String)values[11];
this.lang = (java.lang.String)values[12];
this.style = (java.lang.String)values[13];
this.styleClass = (java.lang.String)values[14];
this.dataURL = (java.lang.String)values[15];
}
}
里面的代码不用做任何改动。
上面已经说了,这个构件我们需要两个资源Column2D.swf和FusionCharts.js,我们把它们放在META-INF\resource中任意位置,这里我放在了fusionchart目录中。

前面说过了对于使用的js和css资源,我们要给它起个别名,接下来为FusionCharts.js起个别名。双击“资源描述符”(也可以双击“META-INF\resource\resource-dependences.xml”),出来如下界面。

选择上图中“JS”,然后点“添加”,然后输入如下“资源名”,即我们要的别名。“资源文件”这里可以点击“浏览...”选择我们刚才放到META-INF\resource\fusionchart中的FusionCharts.js文件,然后“确定”,并按CTRL+S保存我们的配置。

上面已经说过了,这个构件需要一个渲染器类,并且我们也分析了应该覆盖AjaxRendererBase的哪些方法,现在我们来覆盖它们。双击打开org.ajavaer.fusionchart.component包中Column2DAjaxRenderer.java文件,将代码改为这样:
package org.ajavaer.fusionchart.component;
import java.io.IOException;
import java.util.Formatter;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import org.operamasks.faces.developer.util.FacesUtils;
import org.operamasks.faces.render.common.AjaxRendererBase;
import org.operamasks.faces.render.html.HtmlResponseWriter;
import org.operamasks.faces.render.resource.ComponentResource;
import org.operamasks.faces.render.resource.ResourceManager;
import org.operamasks.faces.util.HtmlEncoder;
public class Column2DAjaxRenderer extends AjaxRendererBase {
@Override
public String[] getDependedJSPackages(FacesContext context, UIComponent component) {
// 我们需要使用别名为"AJavaer.FusionChart"的FusionCharts.js文件
return new String[] {"AJavaer.FusionChart"};
}
@Override
public void encodeHtmlBegin(FacesContext context, UIComponent component)
throws IOException {
UIColumn2D chartComp = (UIColumn2D) component;
HtmlResponseWriter responseWriter = (HtmlResponseWriter) context.getResponseWriter();
//得到构件的客户端id
String clientId = chartComp.getClientId(context);
responseWriter.startElement("div", chartComp);//渲染一个div
responseWriter.writeAttribute("id", clientId, null);//为div添加id属性
//将构件的style属性写入div的style属性中
String style=chartComp.getStyle();
if (style != null && style.trim().length() > 0) {
responseWriter.writeAttribute("style", style, null);//为div添加style属性
}
//将构件的styleClass属性写入div的class属性中
String styleClass=chartComp.getStyleClass();
if (styleClass != null && styleClass.trim().length() > 0) {
responseWriter.writeAttribute("class", styleClass, null);
}
}
@Override
public void encodeHtmlEnd(FacesContext context, UIComponent component)
throws IOException {
//输出</div>
HtmlResponseWriter responseWriter = (HtmlResponseWriter) context.getResponseWriter();
responseWriter.endElement("div");
}
@Override
public void encodeInitScriptBegin(FacesContext context, ResourceManager rm,
UIComponent component) throws IOException {
//声明一个jsvar变量
ComponentResource resource = ComponentResource.getResourceInstance(rm);
resource.allocVariable(component);
}
@Override
public void encodeResourceBegin(FacesContext context, ResourceManager rm,
UIComponent component) throws IOException {
UIColumn2D chartComp = (UIColumn2D) component;
String jsvar = FacesUtils.getJsvar(context, chartComp);
StringBuffer buf = new StringBuffer();
Formatter fmt = new Formatter(buf);
//取出各个属性的值
String swfUrl = rm.getResourceURL("/fusionchart/Column2D.swf");
String clientId = chartComp.getClientId(context);
String width = String.valueOf(chartComp.getWidth());
String height = String.valueOf(chartComp.getHeight());
String debug = chartComp.isDebug() ? "1" : "0";
String registerWithJS = chartComp.isRegisterWithJS() ? "1" : "0";
String bgColor = chartComp.getBgcolor();
if(bgColor==null)
bgColor="null";
String scaleMode = chartComp.getScaleMode();
if(scaleMode==null)
scaleMode="null";
String lang = chartComp.getLang();
if(lang==null)
lang="null";
//渲染js代码
fmt.format("%s=new FusionCharts(%s,%s,%s,%s,%s,%s,%s,%s,%s);\n",
jsvar,
HtmlEncoder.enquote(swfUrl),
HtmlEncoder.enquote(chartComp.getId()),
HtmlEncoder.enquote(width),
HtmlEncoder.enquote(height),
HtmlEncoder.enquote(debug),
HtmlEncoder.enquote(registerWithJS),
HtmlEncoder.enquote(bgColor),
HtmlEncoder.enquote(scaleMode),
HtmlEncoder.enquote(lang));
String dataUrl = chartComp.getDataURL();
fmt.format("%s.setDataURL(%s);\n",jsvar, HtmlEncoder.enquote(dataUrl));
fmt.format("%s.render(%s);\n",jsvar, HtmlEncoder.enquote(clientId));
//将拼凑的js代码输出到响应中
ComponentResource resource = ComponentResource.getResourceInstance(rm);
resource.addInitScript(fmt.toString());
}
}构件已经开发好了,我们怎么使用或测试它呢?跟使用其它jar包一样。首先需要一个Apusic标准工程,并且有web模块(这些内容请参考第 11.2 节 “建立一个新的Apusic标准工程”)。
只需要将构件工程导出的jar包复制到测试工程的web模块的WebContent\WEB-INF\lib中即可。然后就可以开始写xhtml页面和LiteBean了。

测试页面Column2D.xhtml代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<f:view xmlns:f="http://java.sun.com/jsf/core"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:w="http://www.apusic.com/jsf/widget"
xmlns:fc="http://org.ajavaer.fusionchart"
renderKitId="AJAX">
<w:page title="FusionCharts Demos">
<fc:column2D id="chart1" width="500" height="400"
dataURL="data.xml" />
</w:page>
</f:view>不需要后台LiteBean,其中data.xml与前文提到的一样。运行后的效果与我们需要的完全一样。

至此这个构件基本满足了我们的要求。下一节我们将对这个构件进行改进。
上文中开发的fc:column2D构件基本可以使用了,但是这种通过一个xml文件来为图表构件指定数据在多数情况下并不好用,一般用户需要的是可以显示动态数据(比如数据库中的数据)的图表构件,虽然我们可以通过Java代码将动态数据生成一个xml文件,然后让图表构件使用那个xml文件来达到要求。但是这种写法十分别扭,而且生成的xml文件名难以确定,而且生成的文件如果长时间存在于服务器端,将极大地占用硬盘资源。我们希望我们的图表构件能够显示内存xml数据,而且用户使用这个图表构件时根本不应该感觉到位xml的存在。这种功能能不能实现呢?
其实在OperaMasks SDK中已经有很多这样的构件,比如w:tree、w:dataGrid、w:editDataGrid、w:dataView等。这种构件在使用时一般只要给它定义一个dataProvider,并在dataProvider中返回一个对象或对象集合(数组或List)就可以了。而且这些dataProvider取数的构件还有一个共同的特点:它们都是采用Delegate二次取数的方式进行构件数据和渲染的。下面我们将对上一节中开发的fc:column2D构件进行修改,让它使用dataProvider提供数据,并用Delegate进行二次取数渲染。
在正式修改之前,我们先定义一个dataProvider————FusionChartDataProvider,该类是一个接口,里面只有一个getChart()方法,该方法返回一个Chart类型的对象,该对象代表FusionChart所需要的xml数据。代码如下:
package org.ajavaer.fusionchart.component;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import org.ajavaer.fusionchart.modal.Chart;
public interface FusionChartDataProvider {
public Chart getChart(FacesContext context, UIComponent component);
}其中org.ajavaer.fusionchart.modal.Chart类是根据FusionCharts官方提供的data.xml的xsd文件生成的Java POJO类,具体代码就不给了,可以下载本章《构件开发》示例工程,里面有相应的源代码。
接下来修改构件Base类,去掉dataURL属性,并添加dataProvider属性。修改后的代码如下:
package org.ajavaer.fusionchart.component;
import javax.faces.component.UIComponentBase;
import org.operamasks.faces.tools.annotation.ComponentMeta;
@ComponentMeta(tagName="column2D", family="org.ajavaer.fusionchart", rendererType="org.ajavaer.fusionchart.component.Column2D")
public abstract class UIColumn2DBase extends UIComponentBase {
protected String jsvar;
protected int width = 500;// 默认宽度500
protected int height = 400;// 默认高度400
protected boolean debug = false;// 默认为false
protected boolean registerWithJS = false;// 默认为false
protected String bgcolor;
protected String scaleMode;
protected String lang;
protected String style;
protected String styleClass;
private FusionChartDataProvider dataProvider;
public org.ajavaer.fusionchart.component.FusionChartDataProvider getDataProvider() {
if (this.dataProvider != null) {
return this.dataProvider;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this,
"dataProvider");
if (exprValue != null) {
return (org.ajavaer.fusionchart.component.FusionChartDataProvider) exprValue;
}
return null;
}
public void setDataProvider(org.ajavaer.fusionchart.component.FusionChartDataProvider value) {
this.dataProvider = value;
}
}修改完后保存,OperaMasks Studio会自动生成对应的构件类UIColumn2D,不需要修改。
接下来修改渲染器类,将原来根据构件的dataURL属性渲染js部分代码改为使用二次取数方式,修改后的代码如下:
package org.ajavaer.fusionchart.component;
import java.io.IOException;
import java.util.Formatter;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import org.operamasks.faces.developer.util.FacesUtils;
import org.operamasks.faces.render.common.AjaxRendererBase;
import org.operamasks.faces.render.delegate.ViewDelegate;
import org.operamasks.faces.render.html.HtmlResponseWriter;
import org.operamasks.faces.render.resource.ComponentResource;
import org.operamasks.faces.render.resource.ResourceManager;
import org.operamasks.faces.util.HtmlEncoder;
public class Column2DAjaxRenderer extends AjaxRendererBase {
public static final String DELEGATE_PARAM = "__fusionchart__";
@Override
public String[] getDependedJSPackages(FacesContext context, UIComponent component) {
return new String[]{"AJavaer.FusionChart"};
}
@Override
public void encodeHtmlBegin(FacesContext context, UIComponent component)
throws IOException {
UIColumn2D chartComp = (UIColumn2D) component;
HtmlResponseWriter responseWriter = (HtmlResponseWriter) context.getResponseWriter();
//得到构件的客户端id
String clientId = chartComp.getClientId(context);
responseWriter.startElement("div", chartComp);//渲染一个div
responseWriter.writeAttribute("id", clientId, null);//为div添加id属性
//将构件的style属性写入div的style属性中
String style=chartComp.getStyle();
if (style != null && style.trim().length() > 0) {
responseWriter.writeAttribute("style", style, null);//为div添加style属性
}
//将构件的styleClass属性写入div的class属性中
String styleClass=chartComp.getStyleClass();
if (styleClass != null && styleClass.trim().length() > 0) {
responseWriter.writeAttribute("class", styleClass, null);
}
}
@Override
public void encodeHtmlEnd(FacesContext context, UIComponent component)
throws IOException {
//输出</div>
HtmlResponseWriter responseWriter = (HtmlResponseWriter) context.getResponseWriter();
responseWriter.endElement("div");
}
@Override
public void encodeInitScriptBegin(FacesContext context, ResourceManager rm,
UIComponent component) throws IOException {
//声明一个jsvar变量
ComponentResource resource = ComponentResource.getResourceInstance(rm);
resource.allocVariable(component);
}
@Override
public void encodeResourceBegin(FacesContext context, ResourceManager rm,
UIComponent component) throws IOException {
UIColumn2D chartComp = (UIColumn2D) component;
String jsvar = FacesUtils.getJsvar(context, chartComp);
StringBuffer buf = new StringBuffer();
Formatter fmt = new Formatter(buf);
//取出各个属性的值
String swfUrl = rm.getResourceURL("/fusionchart/Column2D.swf");
String clientId = chartComp.getClientId(context);
String width = String.valueOf(chartComp.getWidth());
String height = String.valueOf(chartComp.getHeight());
String debug = chartComp.isDebug() ? "1" : "0";
String registerWithJS = chartComp.isRegisterWithJS() ? "1" : "0";
String bgColor = chartComp.getBgcolor();
String scaleMode = chartComp.getScaleMode();
String lang = chartComp.getLang();
//渲染js代码
fmt.format("%s=new FusionCharts(%s,%s,%s,%s,%s,%s,%s,%s,%s);\n",
jsvar,
HtmlEncoder.enquote(swfUrl),
HtmlEncoder.enquote(chartComp.getId()),
HtmlEncoder.enquote(width),
HtmlEncoder.enquote(height),
HtmlEncoder.enquote(debug),
HtmlEncoder.enquote(registerWithJS),
bgColor == null ? "null": HtmlEncoder.enquote(bgColor),
scaleMode == null ? "null" : HtmlEncoder.enquote(scaleMode),
lang == null ? "null" : HtmlEncoder.enquote(lang));
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
//这个dataUrl中包含ViewDelegate.VIEW_DELEGATE_REQUEST说明是二次取数
//这个dataUrl中还传递了一个参数DELEGATE_PARAM,值为构件的id,二次取数时会使用这个参数
String dataUrl = request.getRequestURL() + "?"+DELEGATE_PARAM+"=" + chartComp.getId() +
"%26"+ ViewDelegate.VIEW_DELEGATE_REQUEST + "=%26" + FacesUtils.STREAM_RESPONSE + "=";
fmt.format("%s.setDataURL(%s);\n",jsvar, HtmlEncoder.enquote(dataUrl));
fmt.format("%s.render(%s);\n",jsvar, HtmlEncoder.enquote(clientId));
//将拼凑的js代码输出到响应中
ComponentResource resource = ComponentResource.getResourceInstance(rm);
resource.addInitScript(fmt.toString());
}
}可以看到,dataUrl发生了变化,原来从构件的dataURL中读取xml文件URL的代码改为Delegate方式。
再看一下Delegate的代码:
package org.ajavaer.fusionchart.component;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Map;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import org.ajavaer.fusionchart.component.modal.Chart;
import org.ajavaer.fusionchart.component.modal.ChartObjectFactory;
import org.operamasks.faces.application.ViewBuilder;
import org.operamasks.faces.component.ajax.AjaxUpdater;
import org.operamasks.faces.developer.util.FacesUtils;
import org.operamasks.faces.render.ajax.AjaxUpdaterRenderer;
import org.operamasks.faces.render.delegate.ViewDelegate;
public class FusionChartDelegate implements ViewDelegate {
public void delegate(FacesContext context) throws IOException {
//从二次取数url中取出DELEGATE_PARAM参数,参数结果是构件的id
//具体见AjaxFusionChartRenderer.encodeResourceBegin()方法中代码
ExternalContext ectx = context.getExternalContext();
Map<String, String> paramMap = ectx.getRequestParameterMap();
String fusionChartId = paramMap.get(Column2DAjaxRenderer.DELEGATE_PARAM);
//如果没有找到参数或参数为null,不知道要对哪个构件进行代理,则返回
if (fusionChartId == null) {
return;
}
UIComponent component = null;
//找到参数表示的构件(UIComponent对象)
component = FacesUtils.getForComponent(context, fusionChartId, context.getViewRoot());
//如果没有找到,看看是不是在哪个ajax:updater引入的页面里面
if (component == null) {
try {
component = findFromUpdater(context, fusionChartId,UIColumn2D.class);
} catch (Exception ex) {
// ignore;
}
}
//如果找到构件,而且是FusionChart构件,则开始取数
if (component != null && (component instanceof UIColumn2D)) {
UIColumn2D chartComp = (UIColumn2D) component;
//得到用户定义的dataProvider
FusionChartDataProvider dataProvider = chartComp.getDataProvider();
if (dataProvider != null) {
//从dataProvider中取数
Chart chart = dataProvider.getChart(context, component);
//设置响应头
HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
response.setHeader("Connection", "close");
//设置响应类型为text/xml、编码为UTF-8
response.setContentType( "text/xml; charset=UTF-8" );
OutputStream out = response.getOutputStream();
out.write( new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF} );
try {
JAXBContext jc = JAXBContext.newInstance(ChartObjectFactory.class);
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
//将dataProvider中取出的数据转换为xml并输出
m.marshal(chart, out);
out.flush();
//二次请求的响应完成
context.responseComplete();
} catch (PropertyException e) {
e.printStackTrace();
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
}
/**
* 从该页面所有的ajax:updater引入的页面中查找一个构件类型为 cls,且id为fusionChartId的构件
*/
public UIComponent findFromUpdater(FacesContext context,
String fusionChartId, Class<UIColumn2D> cls)
throws IOException {
//得到页面中所有构件的Iterator,不包含UIViewRoot根节点
Iterator<UIComponent> kids = FacesUtils.createChildrenIterator(context.getViewRoot(), false);
//从页面中所有AjaxUpdater中找这个构件
while (kids.hasNext()) {
UIComponent kid = kids.next();
if (kid instanceof AjaxUpdater) {
AjaxUpdater updater=(AjaxUpdater) kid;
//如果ajax:updater引入了一个其它页面,则开始查找
if (updater.getSubviewId() != null) {
//生成AjaxUpdater引入的页面的视图树
AjaxUpdaterRenderer renderer = (AjaxUpdaterRenderer) FacesUtils.getRenderer(context, updater);
ViewHandler vh = context.getApplication().getViewHandler();
if (vh instanceof ViewBuilder) {
renderer.buildSubviewTree(context, (ViewBuilder) vh,updater);
}
//从生成的视图树中找到构件类为cls的构件
UIColumn2D fusionChart = FacesUtils.findComponent(updater, cls);
//如果找到的构件的id为fusionChartId,则返回这个构件
if (fusionChart != null&& fusionChartId.equals(fusionChart.getId())) {
return fusionChart;
}
}
}
}
return null;
}
}首先它是ViewDelegate接口的实现,所以需要实现ViewDelegate接口的public void delegate(FacesContext context) throws IOException方法。这个FusionChartDelegate的原理是从二次取数的URL中读取参数————构件id,找到这个构件并调用它的dataProvider的public Chart getChart(FacesContext context, UIComponent component)方法,得到要显示的数据对象,然后用jaxb将这个对象转换为xml并写入响应中,从而达到了二次取数的目的和使用内存xml文件的目的。
OperaMasks引擎怎么知道二次取数时要使用这个Delegate,而不使用其它Delegate?这个要通过配置文件配置一下,在META-INF中新建一个文件,文件名为view_delegate.cfg(必须用这个名字,否则OperaMasks引擎不会读取这个文件的内容),其内容为我们定义的那个Delegate的完整限定名:
org.ajavaer.fusionchart.component.FusionChartDelegate
至此,修改工作完成。我们可以将修改后的工程导出为“构件jar文件”。然后修改一下测试案例代码看一下它是否能正常工作。将页面Column2D.xhtml代码改为:
<?xml version="1.0" encoding="UTF-8"?>
<f:view xmlns:f="http://java.sun.com/jsf/core"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:w="http://www.apusic.com/jsf/widget"
xmlns:ajax="http://www.apusic.com/jsf/ajax"
xmlns:layout="http://www.apusic.com/jsf/layout"
xmlns:fc="http://org.ajavaer.fusionchart" renderKitId="AJAX">
<w:page title="FusionCharts Demos">
<fc:column2D id="chart1" width="500" height="400"
dataProvider="#{Column2DBean.chartDataProvider}" />
</w:page>
</f:view>将LiteBean代码改为:
package org.operamasks;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import org.ajavaer.fusionchart.component.FusionChartDataProvider;
import org.ajavaer.fusionchart.modal.Chart;
import org.ajavaer.fusionchart.modal.ChartObjectFactory;
import org.ajavaer.fusionchart.modal.Dset;
import org.operamasks.faces.annotation.Accessible;
import org.operamasks.faces.annotation.ManagedBean;
import org.operamasks.faces.annotation.ManagedBeanScope;
@ManagedBean(name = "Column2DBean", scope = ManagedBeanScope.REQUEST)
public class Column2DBean {
//下面这个属性为Column2D构件提供数据
@Accessible
private FusionChartDataProvider chartDataProvider = new FusionChartDataProvider() {
public Chart getChart(FacesContext context, UIComponent component) {
ChartObjectFactory fac = ChartObjectFactory.getInstance();
// 可以用DSL风格创建对象
Chart chart = fac.createChart().baseFont("宋体").baseFontSize(12).xAxisName("单位")
.yAxisName("用电量(万度)").formatNumberScale(false);
Dset dset = fac.createDset().name("物价局").value(322f).color("AFD8F8");
chart.getSetOrVLine().add(dset);
dset = fac.createDset().name("交通局").value(950f).color("CC3333");
chart.getSetOrVLine().add(dset);
// 也可以用swing风格创建对象
dset = fac.createDset();
dset.setName("市容局");
dset.setValue(957f);
dset.setColor("F6BD0F");
chart.getSetOrVLine().add(dset);
return chart;
}
};
}这个代码中定义了一个FusionChartDataProvider类型的chartDataProvider属性。并在页面中用EL使用这个chartDataProvider属性。运行一下测试页面,效果如下:

对于开发一个构件来说,上面的fc:column2D已经比较好了,也没有改进的空间。但是在另外一些情况下,也许还可能对它进行修改。本节我们要再开发另一个FusionChart构件————Area2D构件。
其HTML代码如下:
<html>
<head>
<script language="JavaScript" src="fusionCharts/FusionCharts.js"></script>
</head>
<body>
<div id="chartdiv"></div>
<script type="text/JavaScript">
var myChart = new FusionCharts("../FusionCharts/Area2D.swf", "myChartId", "600", "500",0 ,0, "FFFFFF","noscale","EN");
myChart.setDataURL("data.xml");
myChart.render("chartdiv");
</script>
</body>
</html>data.xml与column2D构件的一样。
我们发现,这个构件与fc:column2D构件几乎一样,唯一的区别是那个swf文件的url不同,column2D构件使用的是Column2D.swf,而这个构件使用的是Area2D.swf。
我们可以想象,这个构件的Base类会与fc:column2D构件的Base类一模一样,我们也使用dataProvider方式来为这个图表构件提供数据。而且渲染器类也基本与fc:column2D构件的渲染器一样,唯一的区别在于渲染器使用的swf不同。
那么我们可以对构件Base类进行改进、使用一个公共的构件Base类,这个公共Base类继承自UIComponentBase,然后fc:column2D和fc:area2D的Base类继承自这个公共的Base类生成的构件类(继承自公共构件类而不继承自公共Base类的作用是让一部分代码由OperaMasks Studio自动生成,减少开发的工作量)。
同样我们可以对组渲染器类进行修改,有2种办法:第一种办法,建一个公共渲染器类,然后添加抽象方法public String getSwfUrl(),fc:column2D和fc:area2D构件的渲染器类继承这个公共渲染器类并实现其中的抽象方法,然后正确的swf文件路径;另一种办法是所有的构件使用一个渲染器类,这种swf路径的差别在构件类中体现。第一种办法的优点是保持思路和结构清晰,而且swf的不同本来就是渲染器要决定的事,相比第二种办法交给构件类来决定要好些;第二种办法的优点是只不管多少个构件,只需要一个渲染器类就够了。
第一种办法还有一个优点是能较好地适应变化,如果某天要对fc:area2D构件进行改进,改为使用JFreeChart构件,改动比较方便,对一个构件的改动不会影响到其它构件。我们可以使用第一种办法来改进。
除此以外,fc:area2D构件还依赖于Area2D.swf这个资源文件,我们将它复制到与Column2D.swf相同的目录中。再看一下修改后的构件类和渲染器类。
公共Base类代码如下(注意:它继承自UIComponentBase类,而且dataProvider属性进行了特殊处理):
package org.ajavaer.fusionchart.component;
import javax.faces.component.UIComponentBase;
import org.operamasks.faces.component.GenComponentUtils;
public abstract class UIAbstractFusionChartBase extends UIComponentBase {
protected String jsvar;
protected int width = 500;// 默认宽度500
protected int height = 400;// 默认高度400
protected boolean debug = false;// 默认为false
protected boolean registerWithJS = false;// 默认为false
protected String bgcolor;
protected String scaleMode;
protected String lang;
protected String style;
protected String styleClass;
private FusionChartDataProvider dataProvider;
public FusionChartDataProvider getDataProvider() {
if (this.dataProvider != null) {
return this.dataProvider;
}
Object exprValue = GenComponentUtils.getValueFromExpression(this, "dataProvider");
if (exprValue != null) {
return (FusionChartDataProvider) exprValue;
}
return null;
}
public void setDataProvider(FusionChartDataProvider value) {
this.dataProvider = value;
}
}fc:column2D的Base类代码如下:
package org.ajavaer.fusionchart.component;
import org.operamasks.faces.tools.annotation.ComponentMeta;
@ComponentMeta(tagName="column2D", family="org.ajavaer.fusionchart", rendererType="org.ajavaer.fusionchart.component.Column2D")
public abstract class UIColumn2DBase extends UIAbstractFusionChart {
}fc:area2D的Base类代码如下:
package org.ajavaer.fusionchart.component;
import org.operamasks.faces.tools.annotation.ComponentMeta;
@ComponentMeta(tagName="area2D", family="org.ajavaer.fusionchart", rendererType="org.ajavaer.fusionchart.component.Area2D")
public abstract class UIArea2DBase extends UIAbstractFusionChart {
}这3个Base类对应的构件类由Operamasks Studio自动生成,不需要修改,代码就不在这里给出了。
再来修改渲染器类,注意一定要将fc:area2D构件依赖的Area2D.swf放到META-INF中。将原来的渲染器代码放到公共渲染器类中,对于swf文件路径的处理使用抽象方法。修改后的代码如下:
package org.ajavaer.fusionchart.component; import java.io.IOException; import java.util.Formatter; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.servlet.http.HttpServletRequest; import org.operamasks.faces.developer.util.FacesUtils; import org.operamasks.faces.render.common.AjaxRendererBase; import org.operamasks.faces.render.delegate.ViewDelegate; import org.operamasks.faces.render.html.HtmlResponseWriter; import org.operamasks.faces.render.resource.ComponentResource; import org.operamasks.faces.render.resource.ResourceManager; import org.operamasks.faces.util.HtmlEncoder; publicabstract class FusionChartAjaxRenderer extends AjaxRendererBase { public static final String DELEGATE_PARAM = "__fusionchart__"; @Override public String[] getDependedJSPackages(FacesContext context, UIComponent component) { return new String[]{"AJavaer.FusionChart"}; } @Override public void encodeHtmlBegin(FacesContext context, UIComponent component) throws IOException { UIAbstractFusionChart chartComp = (UIAbstractFusionChart) component; HtmlResponseWriter responseWriter = (HtmlResponseWriter) context.getResponseWriter(); //得到构件的客户端id String clientId = chartComp.getClientId(context); responseWriter.startElement("div", chartComp);//渲染一个div responseWriter.writeAttribute("id", clientId, null);//为div添加id属性 //将构件的style属性写入div的style属性中 String style=chartComp.getStyle(); if (style != null && style.trim().length() > 0) { responseWriter.writeAttribute("style", style, null);//为div添加style属性 } //将构件的styleClass属性写入div的class属性中 String styleClass=chartComp.getStyleClass(); if (styleClass != null && styleClass.trim().length() > 0) { responseWriter.writeAttribute("class", styleClass, null); } } @Override public void encodeHtmlEnd(FacesContext context, UIComponent component) throws IOException { //输出</div> HtmlResponseWriter responseWriter = (HtmlResponseWriter) context.getResponseWriter(); responseWriter.endElement("div"); } @Override public void encodeInitScriptBegin(FacesContext context, ResourceManager rm, UIComponent component) throws IOException { //声明一个jsvar变量 ComponentResource resource = ComponentResource.getResourceInstance(rm); resource.allocVariable(component); } @Override public void encodeResourceBegin(FacesContext context, ResourceManager rm, UIComponent component) throws IOException { UIAbstractFusionChart chartComp = (UIAbstractFusionChart) component; String jsvar = FacesUtils.getJsvar(context, chartComp); StringBuffer buf = new StringBuffer(); Formatter fmt = new Formatter(buf); //根据构件的不同,渲染不同的swf文件名 String swfUrl = rm.getResourceURL(
getSwfURL()); String clientId = chartComp.getClientId(context); String width = String.valueOf(chartComp.getWidth()); String height = String.valueOf(chartComp.getHeight()); String debug = chartComp.isDebug() ? "1" : "0"; String registerWithJS = chartComp.isRegisterWithJS() ? "1" : "0"; String bgColor = chartComp.getBgcolor(); String scaleMode = chartComp.getScaleMode(); String lang = chartComp.getLang(); fmt.format("%s=new FusionCharts(%s,%s,%s,%s,%s,%s,%s,%s,%s);\n", jsvar, HtmlEncoder.enquote(swfUrl), HtmlEncoder.enquote(chartComp.getId()), HtmlEncoder.enquote(width), HtmlEncoder.enquote(height), HtmlEncoder.enquote(debug), HtmlEncoder.enquote(registerWithJS), bgColor == null ? "null": HtmlEncoder.enquote(bgColor), scaleMode == null ? "null" : HtmlEncoder.enquote(scaleMode), lang == null ? "null" : HtmlEncoder.enquote(lang)); HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); //这个dataUrl中包含ViewDelegate.VIEW_DELEGATE_REQUEST说明是二次取数 //这个dataUrl中还传递了一个参数DELEGATE_PARAM,值为构件的id String dataUrl = request.getRequestURL() + "?"+DELEGATE_PARAM+"=" + chartComp.getId() + "%26"+ ViewDelegate.VIEW_DELEGATE_REQUEST + "=%26" + FacesUtils.STREAM_RESPONSE + "="; fmt.format("%s.setDataURL(%s);\n",jsvar, HtmlEncoder.enquote(dataUrl)); fmt.format("%s.render(%s);\n",jsvar, HtmlEncoder.enquote(clientId)); //将拼凑的js代码输出到响应中 ComponentResource resource = ComponentResource.getResourceInstance(rm); resource.addInitScript(fmt.toString()); }
protected abstract String getSwfURL(); }
修改后fc:column2D构件的渲染器类Column2DAjaxRenderer的代码如下:
package org.ajavaer.fusionchart.component;
public class Column2DAjaxRenderer extends FusionChartAjaxRenderer {
@Override
protected String getSwfURL() {
return "/fusionchart/Column2D.swf";
}
}同样,fc:area2D构件的渲染器类Area2DAjaxRenderer的代码如下:
package org.ajavaer.fusionchart.component;
public class Area2DAjaxRenderer extends FusionChartAjaxRenderer {
@Override
protected String getSwfURL() {
return "/fusionchart/Area2D.swf";
}
}至此,构件开发工作完成。再来将这个工程打包成“构件jar文件”,放到测试工程的WEB-INF\lib中,然后测试一下这两个构件。
fc:column2D构件的测试页面及LiteBean代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<f:view xmlns:f="http://java.sun.com/jsf/core"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:w="http://www.apusic.com/jsf/widget"
xmlns:fc="http://org.ajavaer.fusionchart" renderKitId="AJAX">
<w:page title="FusionCharts Demos">
<fc:column2D id="chart1" width="500" height="400"
dataProvider="#{Column2DBean.chartDataProvider}" />
</w:page>
</f:view>package org.operamasks;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import org.ajavaer.fusionchart.component.FusionChartDataProvider;
import org.ajavaer.fusionchart.modal.Chart;
import org.ajavaer.fusionchart.modal.ChartObjectFactory;
import org.ajavaer.fusionchart.modal.Dset;
import org.operamasks.faces.annotation.Accessible;
import org.operamasks.faces.annotation.ManagedBean;
import org.operamasks.faces.annotation.ManagedBeanScope;
@ManagedBean(name = "Column2DBean", scope = ManagedBeanScope.REQUEST)
public class Column2DBean {
//下面这个属性为Column2D构件提供数据
@Accessible
private FusionChartDataProvider chartDataProvider = new FusionChartDataProvider() {
public Chart getChart(FacesContext context, UIComponent component) {
ChartObjectFactory fac = ChartObjectFactory.getInstance();
// 可以用DSL风格创建对象
Chart chart = fac.createChart().baseFont("宋体").baseFontSize(12).xAxisName("单位")
.yAxisName("用电量(万度)").formatNumberScale(false);
Dset dset = fac.createDset().name("物价局").value(322f).color("AFD8F8");
chart.getSetOrVLine().add(dset);
dset = fac.createDset().name("交通局").value(950f).color("CC3333");
chart.getSetOrVLine().add(dset);
// 也可以用swing风格创建对象
dset = fac.createDset();
dset.setName("市容局");
dset.setValue(957f);
dset.setColor("F6BD0F");
chart.getSetOrVLine().add(dset);
return chart;
}
};
}运行后效果如下:

fc:area2D构件的测试页面及LiteBean代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<f:view xmlns:f="http://java.sun.com/jsf/core"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:w="http://www.apusic.com/jsf/widget"
xmlns:fc="http://org.ajavaer.fusionchart" renderKitId="AJAX">
<w:page title="FusionCharts Demos">
<fc:area2D id="chart1" width="500" height="400"
dataProvider="#{Area2DBean.chartDataProvider}" />
</w:page>
</f:view>package org.operamasks;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import org.ajavaer.fusionchart.component.FusionChartDataProvider;
import org.ajavaer.fusionchart.modal.Chart;
import org.ajavaer.fusionchart.modal.ChartObjectFactory;
import org.ajavaer.fusionchart.modal.Dset;
import org.operamasks.faces.annotation.Accessible;
import org.operamasks.faces.annotation.ManagedBean;
import org.operamasks.faces.annotation.ManagedBeanScope;
@ManagedBean(name = "Area2DBean", scope = ManagedBeanScope.REQUEST)
public class Area2DBean {
//下面这个属性为Column2D构件提供数据
@Accessible
private FusionChartDataProvider chartDataProvider = new FusionChartDataProvider() {
public Chart getChart(FacesContext context, UIComponent component) {
ChartObjectFactory fac = ChartObjectFactory.getInstance();
// 可以用DSL风格创建对象
Chart chart = fac.createChart().baseFont("宋体").baseFontSize(12).xAxisName("单位")
.yAxisName("用电量(万度)").formatNumberScale(false);
Dset dset = fac.createDset().name("物价局").value(322f).color("AFD8F8");
chart.getSetOrVLine().add(dset);
dset = fac.createDset().name("交通局").value(950f).color("CC3333");
chart.getSetOrVLine().add(dset);
// 也可以用swing风格创建对象
dset = fac.createDset();
dset.setName("市容局");
dset.setValue(957f);
dset.setColor("F6BD0F");
chart.getSetOrVLine().add(dset);
return chart;
}
};
}
运行后效果如下:

此示例最终的构件工程及测试案例可以到http://wiki.operamasks.org/pages/viewpage.action?pageId=9437386下载。