您的位置:首页 > Web前端 > JavaScript

JSF 组件开发

2007-03-06 12:45 204 查看
组件模型的关键考验就是:能否从第三方供应商购买组件,并把它们插入应用程序?与可购买可视 Swing 组件一样,也可以购买 Java ServerFaces (JSF) 组件!需要一个好玩的日历?可以在开源实现和商业组件之间选择。可以选择购买一个,而不是自行开发复杂的基于 Web 的 GUI 组件。

JSF 拥有一个与 AWT 的 GUI 组件模型类似的组件模型。可以用 JSF 创建可重用组件。但不幸的是,存在一个误解:用 JSF 创建组件很困难。不要相信这些从未试过它的人们的 FUD!开发 JSF 组件并不困难。由于不用一遍又一遍重复相同的代码,可以节约时间。一旦创建了组件,就可以容易地把组件拖到任何 JSP、甚至任何 JSF 表单中,如果正在处理的站点有 250 个页面,这就很重要了。JSF 的大多数功能来自基类。因为所有的繁重工作都由 API 和基类完成,所以 JSF 把组件创建变得很容易。

贯穿这个系列,我一直在试图帮助您克服造成许多 Java 开发人员逃避使用 JSF 技术的 FUD。我讨论了对这项技术的基本误解,介绍了它的底层框架和它最有价值的开发特性。有了这些基础工作之后,我认为您已经可以采取行动,开发自己的定制 JSF 组件了。使用 JSF 的东西,我敢保证要比您想像的要更加容易,而且从节约的时间和精力上来说,回报如此之多,多得不能忽略。

这篇文章中的示例是用 JDK 1.5 和 Tomcat 开发的。请单击页面顶部的 示例代码 下载示例源代码。注意,与以前的文章不同,这篇文章没有关联的 build 文件,因为我特意把它留给您作为一个练习了。只要设置 IDE 或编译器,把 /src 中的类编译到 /webapp/WEB-INF/classes,并在 /webapp/WEB-INF/lib 中包含所有 JAR 文件(以及 servlet-api.jar 和 jsp-api.jar,它们包含在 Tomcat 中)。

JSF 组件模型

JSF 组件模型与 AWT GUI 组件模型类似。它有事件和属性,就像 Swing 组件模型一样。它也有包含组件的容器,容器也是组件,也可以由其他容器包含。从理论上说,JSF 组件模型分离自 HTML 和 JSP。JSF 自带的标准组件集里面有 JSP 绑定,可以生成 HTML 渲染。

JSF 组件的示例包括日历输入组件和 HTML 富文本输入组件。您可能从来没时间去编写这样的组件,但是如果它们已经存在,那会如何呢?通过把常用功能变成商品,组件模型降低了向 Web 应用程序添加更多功能的门槛。

组件的功能通常围绕着两个动作:解码和编码数据。解码 是把进入的请求参数转换成组件的值的过程。编码 是把组件的当前值转换成对应的标记(也就是 HTML)的过程。

JSF 框架提供了两个选项用于编码和解码数据。使用直接实现 方式,组件自己实现解码和编码。使用委托实现 方式,组件委托渲染器进行编码和解码。如果选择委托实现,可以把组件与不同的渲染器关联,会在页面上以不同的方式渲染组件;例如多选列表框和一列复选框。

因此,JSF 组件由两部分构成:组件和渲染器。JSF 组件 类定义 UI 组件的状态和行为;渲染器 定义如何从请求读取组件、如何显示组件 —— 通常通过 HTML 渲染。渲染器把组件的值转换成适当的标记。事件排队和性能验证发生在组件内部。

在图 1 中可以看到数据编码和解码出现在 JSF 生命周期中的什么阶段(到现在,我希望您已经熟悉 JSF 生命周期了)。

图 1. JSF 生命周期和 JSF 组件





提示!

在许多情况下,可以在保持组件本身不变的情况下,通过改变渲染而简化开发过程。在这些情况下,可以编写定制渲染器而不是定制组件。

更多组件概念

所有 JSF 组件的基类是 UIComponent。在开发自己的组件时,需要继承 UIComponentBase,它扩展了 UIComponent 并提供了 UIComponent 中所有抽象方法的默认实现。

组件拥有双亲和标识符。每个组件都关联着一个组件类型,组件类型用于在 face 的上下文配置文件(faces-config.xml)中登记组件。可以用 JSF-EL (表达式语言)把 JSF 组件绑定到受管理的 bean 属性。可以把表达式关联到组件上的任何属性,这样就允许用 JSF-EL 设置组件的属性值。在创建使用 JSF-EL 绑定的组件属性时,需要创建值绑定表达式。在调用绑定属性的 getter 方法时,除非 setter 方法已经设置了值,否则 getter 方法必须用值绑定获得值。

组件可以作为 ValueHolder 或 EditableValueHolder。ValueHolder 与一个或多个 Validator 和 Converter 相关联;所以 JSF UI 组件也与 Validator 和 Converter 关联(请参阅 参考资料 获得更多关于 JSF 验证和转换的内容。)

像表单字段组件这样的组件拥有一个 ValueBinding,它必须绑定到 JavaBean 的读写属性。组件可以调用 getParent 方法访问它们的双亲,也可以调用 getChildren 方法访问它们的子女。组件也可以有 facet 组件,facet 组件是当前组件的子组件,可以调用 getFacets 方法访问它,这个方法返回一个映射。Facets 是著名的子组件。

这里描述的许多组件的概念将会是接下来展示的示例的一部分,所以请记住它们!





回页首

JSF 样式的 Hello World!

我们用一个又好又容易的示例来开始 JSF 组件的开发:我将展示如何渲染 Label 标记(示例:<label>Form Test</label>)。

下面是我要采取的步骤:

扩展 UIComponent

创建一个类,扩展 UIComponent

保存组件状态

用 faces-config.xml 登记组件

定义渲染器或者内联地实现它

覆盖 encode

覆盖 decode

用 faces-config.xml 登记渲染器

创建定制标记,继承 UIComponentTag

返回渲染器类型

返回组件类型

设置可能使用 JSF 表达式的属性

Label 示例将演示 JSF 组件开发的以下方面:

创建组件

直接实现渲染器

编码输出

把定制标记与组件关联

返回 图 1,可以看到在这个示例中会有两个生命周期属性在活动。它们是 Apply Request Value 和 Render Response。

在图 2 中,可以看到在 JSP 中如何使用 Label 标记的(<label>Form Test</label>)。

图 2. 在 JSP 中使用 JSF 标记



第 1 步:扩展 UIComponent

第一步是创建一个组件,继承 UIOutput,后者是 UIComponent 的子类。 除了继承这个类之外,我还添加了组件将会显示的 label 属性,如清单 1 所示:

清单 1. 继承 UIComponent 并添加 label

import java.io.IOException; import javax.faces.component.UIOutput; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; public class LabelComponent extends UIOutput{ private String label; public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } ...


接下来要做的是保存组件状态。JSF 通常通过会话、隐藏表单字段、cookies 等进行实际的存储和状态管理。(这通常是用户配置的设置)。要保存组件状态,需要覆盖组件的 saveState 和 restoreState 方法,如清单 2 所示:

清单 2. 保存组件状态

@Override public Object saveState(FacesContext context) { Object values[] = new Object[2]; values[0] = super.saveState(context); values[1] = label; return ((Object) (values)); } @Override public void restoreState(FacesContext context, Object state) { Object values[] = (Object[])state; super.restoreState(context, values[0]); label = (String)values[1]; }


可以注意到,我使用的是 JDK 1.5。我对编译器进行了设置,所以我必须指定 override 注释,以便指明哪些方法要覆盖基类的方法。这样做可以更容易地标识出 JSF 的钩子在哪。

创建组件的最后一步是用 faces-config.xml 登记它,如下所示:

<faces-config> <component> <component-type>simple.Label</component-type> <component-class> arcmind.simple.LabelComponent </component-class> </component> ...


第 2 步:定义渲染器

下面要做的是内联地定义渲染器的功能。稍后我会介绍如何创建独立的渲染器。现在,先从编码 Label 组件的输出、显示 label 开始,如清单 3 所示:

清单 3. 编码组件的输出

public class LabelComponent extends UIOutput{ ... public void encodeBegin(FacesContext context) throws IOException { ResponseWriter writer = context.getResponseWriter(); writer.startElement("label", this); writer.write(label); writer.endElement("label"); writer.flush(); } ... }


注意,响应写入器(javax.faces.context.ResponseWriter)可以容易地处理 HTML 这样的标记语言。清单 3 的代码输出 <label> 元素体内的 label 的值。

下面显示的 family 属性用来把 Label 组件与渲染器关联。虽然目前 Label 组件还不需要这个属性(因为还没有独立的渲染器),但是在这篇文章后面,在介绍如何创建独立渲染器的时候,会需要它。

public class LabelComponent extends UIOutput{ ... public String getFamily(){ return "simple.Label"; } ... }


插曲:研究 JSF-RI

如果正在使用来自 Sun Microsystems 的 JSF 参考实现(不是 MyFaces 实现),那么就不得不在组件创建代码中添加下面一段:

public void encodeEnd(FacesContext context) throws IOException { return; } public void decode(FacesContext context) { return; }


Sun 的 JSF RI 期望,在组件没有渲染器的时候,渲染器会发送一个空指针异常。MyFaces 实现不要求处理这个需求,但是在代码中包含以上方法依然是个好主意,这样组件既可以在 MyFaces 环境中工作也可以在 JSF RI 环境中工作了。



MyFaces 更好!

如果正在使用 Sun JSF RI 或其他替代品,那么请帮自己一个忙,转到 MyFaces。虽然 MyFaces 不总是 更好的实现,但是目前它是。它的错误消息要比 Sun JSF RI 的好,而这个框架相比之下更严格。

第 3 步:创建定制标记

JSF 组件不是天生绑定到 JSP 上的。要连接起 JSP 世界和 JSF 世界,需要能够返回组件类型的定制标记(然后在 faces-context 文件中登记)和渲染器,如图 3 所示。

图 3. 连接 JSF 和 JSP



注意,由于没有独立的渲染器,所以可以给 getRendererType() 返回 null 值。还请注意,必须已经把 label 属性的值从定制标记设置到组件上,如下所示:

[LabelTag.java] public class LabelTag extends UIComponentTag { … protected void setProperties(UIComponent component) { /* you have to call the super class */ super.setProperties(component); ((LabelComponent)component).setLabel(label); }


记住,Tag 设置从 JSP 到 Label 组件的绑定,如图 4 所示。

图 4. 绑定 JSF 和 JSP



现在要做的全部工作就是创建一个 TLD(标记库描述符)文件,以登记定制标记,如清单 4 所示:

清单 4. 登记定制标记

[arcmind.tld] <taglib> <tlib-version>0.03</tlib-version> <jsp-version>1.2</jsp-version> <short-name>arcmind</short-name> <uri>http://arcmind.com/jsf/component/tags</uri> <description>ArcMind tags</description> <tag> <name>slabel</name> <tag-class>arcmind.simple.LabelTag</tag-class> <attribute> <name>label</name> <description>The value of the label</description> </attribute> </tag> ...


一旦定义了 TLD 文件,就可以开始在 JSP 中使用标记了,如下面示例所示:

[test.jsp] <%@ taglib prefix="arcmind" uri="http://arcmind.com/jsf/component/tags" %> ... <arcmind:slabel label="Form Test"/>


现在就可以了 —— 开发一个简单的 JSP 组件不需要更多了。但是如果想创建稍微复杂一些的组件,针对更复杂的使用场景时该怎么办?请继续往下看。





回页首

复合组件

在下一个示例中,我将介绍如何创建这样一个组件(和标记),它可以记住最后一个人离开的位置。Field 组件把多个组件的工作组合到一个组件中。复合组件是 JSF 组件开发的重点,会节约大量时间!

Field 组件把标签、文本输入和消息功能组合到一个组件。Field 的文本输入功能允许用户输入文本。如果有问题(例如输入不正确),它的标签功能会显示红色,还会显示星号(*)表示必需的字段。它的消息功能允许它在必要的时候写出出错消息。

Field 组件示例演示了以下内容:

UIInput 组件

处理值绑定和组件属性

解码来自请求参数的值

处理出错消息

与 Label 组件不同,Field 组件使用独立渲染器。如果为一个基于 HTML 的应用程序开发组件,那么不要费力使用独立渲染器。这么做是额外的无用功。如果正在开发许多 JSF 组件,打算卖给客户,而针对的客户又不止一个,那么就需要独立的渲染器了。简而言之,渲染器适用于商业框架的开发人员,不适用于开发内部 Web 应用程序的应用程序开发人员。

了解代码

由于我已经介绍了创建组件、定义渲染器以及创建定制标记的基本步骤,所以这次我让代码自己说话,我只点出几个重要的细节。在清单 5 中,可以看到在典型的应用程序示例中如何使用 Field 标记的:

清单 5. Field 标记

<f:view> <h2>CD Form</h2> <h:form id="cdForm"> <h:inputHidden id="rowIndex" value="#{CDManagerBean.rowIndex}" /> <arcmind:field id="title" value="#{CDManagerBean.title}" label="Title:" errorStyleClass="errorText" required="true" /> <br /> <arcmind:field id="artist" value="#{CDManagerBean.artist}" label="Artist:" errorStyleClass="errorText" required="true" /> <br /> <arcmind:field id="price" value="#{CDManagerBean.price}" label="CD Price:" errorStyleClass="errorText" required="true"> <f:validateDoubleRange maximum="1000.0" minimum="1.0"/> </arcmind:field>


以上标记输出以下 HTML:

<label style="" class="errorText">Artist*</label> <input type="text" id="cdForm:artist " name=" cdForm:artist " /> Artist is blank, it must contain characters


图 5 显示了浏览器中这些内容可能显示的效果。

图 5. Field 组件



清单 6 显示了创建 Field 组件的代码。因为这个组件负责输入文本而不仅仅是输出它(像 Label 那样),所以要从继承 UIInput 开始,而不是从继承 UIOutput 开始。

清单 6. Field 继承 UIInput

 package com.arcmind.jsfquickstart; import javax.faces.component.UIInput; import javax.faces.context.FacesContext; /** * @author Richard Hightower * */ public class FieldComponent extends UIInput { private String label;@Override public Object saveState(FacesContext context) { Object values[] = new Object[2]; values[0] = super.saveState(context); values[1] = label; return ((Object) (values)); } @Override public void restoreState(FacesContext context, Object state) { Object values[] = (Object[])state; super.restoreState(context, values[0]); label = (String)values[1]; } public FieldComponent (){ this.setRendererType("arcmind.Field"); } /** * @return Returns the label. */ public String getLabel() { return label; } /** * @param label * The label to set. */ public void setLabel(String label) { this.label = label; } @Override public String getFamily() { return "arcmind.Field"; } public boolean isError() { return !this.isValid(); } }


可以注意到,代表片段中遗漏了编码方法。这是因为编码和解码发生在独立的渲染器中。我稍后会介绍它。

值绑定和组件属性

虽然 Label 组件只有一个属性(JSP 属性),可是 Field 组件却有多个属性,即 label、errorStyle、errorStyleClass 和 value。label 和 value 属性位于 Field 组件的核心,而 errorStyle 和 errorStyleClass 是特定于 HTML 的。因为这些属性是特定于 HTML 的,所以不需要让它们作为 Field 组件的属性;相反,只是把它们作为组件属性进行传递,只有渲染器知道这些属性。

像使用 Label 组件时一样,需要用定制标记把 Field 组件绑定到 JSP,如清单 7 所示:

清单 7. 为 FieldComponent 创建定制标记

/* * Created on Jul 19, 2004 * */ package com.arcmind.jsfquickstart; import javax.faces.application.Application; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding; import javax.faces.webapp.UIComponentTag; /** * @author Richard Hightower * */ public class FieldTag extends UIComponentTag { private String label; private String errorStyleClass=""; private String errorStyle=""; private boolean required; private String value=""; /** * @return Returns the label. */ public String getLabel() { return label; } /** * @param label The label to set. */ public void setLabel(String label) { this.label = label; } /** * @see javax.faces.webapp.UIComponentTag#setProperties * (javax.faces.component.UIComponent) */ @Overrideprotected void setProperties(UIComponent component) { /* You have to call the super class */ super.setProperties(component); ((FieldComponent)component).setLabel(label); component.getAttributes().put("errorStyleClass", errorStyleClass); component.getAttributes().put("errorStyle",errorStyle); ((FieldComponent)component).setRequired(required); FacesContext context = FacesContext.getCurrentInstance(); Application application = context.getApplication(); ValueBinding binding = application.createValueBinding(value); component.setValueBinding("value", binding); } /** * @see javax.faces.webapp.UIComponentTag#getComponentType() */ @Override public String getComponentType() { return "arcmind.Field"; } /** * @see javax.faces.webapp.UIComponentTag#getRendererType() */ @Override public String getRendererType() { return "arcmind.Field"; } /** * @return Returns the errorStyleClass. */ public String getErrorStyleClass() { return errorStyleClass; } /** * @param errorStyleClass The errorStyleClass to set. */ public void setErrorStyleClass(String errorStyleClass) { this.errorStyleClass = errorStyleClass; } /** * @return Returns the errorStyle. */ public String getErrorStyle() { return errorStyle; } /** * @param errorStyle The errorStyle to set. */ public void setErrorStyle(String errorStyle) { this.errorStyle = errorStyle; } /** * @return Returns the required. */ public boolean isRequired() { return required; } /** * @param required The required to set. */ public void setRequired(boolean required) { this.required = required; } /** * @return Returns the value. */ public String getValue() { return value; } /** * @param value The value to set. */ public void setValue(String value) { this.value = value; } }


从概念上说,在上面的代码和 Label 组件之间找不出太大区别。但是,在这个示例中,setProperties 方法有些不同:

protected void setProperties(UIComponent component) { /* You have to call the super class */ super.setProperties(component); ((FieldComponent)component).setLabel(label); component.getAttributes().put("errorStyleClass", errorStyleClass); component.getAttributes().put("errorStyle",errorStyle); ((FieldComponent)component).setRequired(required);


虽然 label 属性传递时的方式与前面的示例相同,但是 errorStyleClass 和 errorStyle 属性不是这样传递的。相反,它们被添加到 JSF 组件的属性映射 中。Renderer 类会使用属性映射去渲染类和样式属性。这个设置允许特定于 HTML 的代码从组件脱离。

这个修订后的 setProperties 方法实际的值绑定代码也有些不同,如下所示。

protected void setProperties(UIComponent component) { ... FacesContext context = FacesContext.getCurrentInstance(); Application application = context.getApplication(); ValueBinding binding = application.createValueBinding(value); component.setValueBinding("value", binding);


这个代码允许 Field 组件的 value 属性绑定到后台 bean。出于示例的原因,我把 CDManagerBean 的 title 属性绑定到 Field 组件,像下面这样:value="#{CDManagerBean.title}。值绑定是用 Application 对象创建的。Application 对象是创建值绑定的工厂。这个组件拥有保存值绑定的特殊方法,即 setValueBinding;可以有不止一个值绑定。

独立渲染器

最后介绍渲染器,但并不是说它不重要。独立渲染器必须考虑的主要问题是解码(输入) 和编码(输出)。Field 组件做的编码比解码多得多,所以它的渲染器有许多编码方法,而只有一个解码方法。在清单 8 中,可以看到 Field 组件的渲染器:

清单 8. FieldRenderer 扩展自 Renderer

package com.arcmind.jsfquickstart; import java.io.IOException; import java.util.Iterator; import java.util.Map; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.component.UIInput; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.convert.Converter; import javax.faces.convert.ConverterException; import javax.faces.el.ValueBinding; import javax.faces.render.Renderer; /** * @author Richard Hightower * */ public class FieldRenderer extends Renderer {@Override public Object getConvertedValue(FacesContext facesContext, UIComponent component, Object submittedValue) throws ConverterException { //Try to find out by value binding ValueBinding valueBinding = component.getValueBinding("value"); if (valueBinding == null) return null; Class valueType = valueBinding.getType(facesContext); if (valueType == null) return null; if (String.class.equals(valueType)) return submittedValue; if (Object.class.equals(valueType)) return submittedValue; Converter converter = ((UIInput) component).getConverter(); converter = facesContext.getApplication().createConverter(valueType); if (converter != null ) { return converter.getAsObject(facesContext, component, (String) submittedValue); }else { return submittedValue; } }@Override public void decode(FacesContext context, UIComponent component) { /* Grab the request map from the external context */ Map requestMap = context.getExternalContext().getRequestParameterMap(); /* Get client ID, use client ID to grab value from parameters */ String clientId = component.getClientId(context); String value = (String) requestMap.get(clientId); FieldComponent fieldComponent = (FieldComponent)component; /* Set the submitted value */ ((UIInput)component).setSubmittedValue(value); }@Override public void encodeBegin(FacesContext context, UIComponent component) throws IOException { FieldComponent fieldComponent = (FieldComponent) component; ResponseWriter writer = context.getResponseWriter(); encodeLabel(writer,fieldComponent); encodeInput(writer,fieldComponent); encodeMessage(context, writer, fieldComponent); writer.flush(); } private void encodeMessage(FacesContext context, ResponseWriter writer, FieldComponent fieldComponent) throws IOException { Iterator iter = context.getMessages(fieldComponent.getClientId(context)); while (iter.hasNext()){ FacesMessage message = (FacesMessage) iter.next(); writer.write(message.getDetail()); } }private void encodeLabel(ResponseWriter writer, FieldComponent fieldComponent) throws IOException{ writer.startElement("label", fieldComponent); if (fieldComponent.isError()) { String errorStyleClass = (String) fieldComponent.getAttributes().get("errorStyleClass"); String errorStyle = (String) fieldComponent.getAttributes().get("errorStyle"); writer.writeAttribute("style", errorStyle, "style"); writer.writeAttribute("class", errorStyleClass, "class"); } writer.write("" + fieldComponent.getLabel()); if (fieldComponent.isRequired()) { writer.write("*"); } writer.endElement("label"); } private void encodeInput(ResponseWriter writer, FieldComponent fieldComponent) throws IOException{ FacesContext currentInstance = FacesContext.getCurrentInstance(); writer.startElement("input", fieldComponent); writer.writeAttribute("type", "text", "type"); writer.writeAttribute("id", fieldComponent.getClientId(currentInstance), "id"); writer.writeAttribute("name", fieldComponent.getClientId(currentInstance), "name"); if(fieldComponent.getValue()!=null) writer.writeAttribute("value", fieldComponent.getValue().toString(), "value"); writer.endElement("input"); } }


编码和解码

正如前面提到的,渲染器做的主要工作就是解码输入和编码输出。我先从解码开始,因为它是最容易的。 FieldRenderer 的 decode 方法如下所示:

@Override public void decode(FacesContext context, UIComponent component) { /* Grab the request map from the external context */ Map requestMap = context.getExternalContext().getRequestParameterMap(); /* Get client ID, use client ID to grab value from parameters */ String clientId = component.getClientId(context); String value = (String) requestMap.get(clientId); FieldComponent fieldComponent = (FieldComponent)component; /* Set the submitted value */ ((UIInput)component).setSubmittedValue(value); }


Label 组件不需要进行解码,因为它是一个 UIOutput 组件。Field 组件是一个 UIInput 组件,这意味着它接受输入,所以 必须 进行解码。decode 方法可以从会话、cookie、头、请求等处读取值。在大多数请问下,decode 方法只是像上面那样从请求参数读取值。Field 渲染器的 decode 方法从组件得到 clientId,以标识要查找的请求参数。给定组件容器的路径,clientId 被计算成为组件的全限定名称。而且,因为示例组件在表单中(是个容器),所以它的 clientid 应当是 nameOfForm:nameOfComponent 这样的,或者是示例中的 cdForm:artist、cdForm:price、cdForm:title。decode 方法的最后一步是把提交的值保存到组件(稍后会转换并验证它,请参阅 参考资料 获取更多关于验证和转换的内容)。

编码方法没什么惊讶的。它们与 Label 组件中看到的类似。第一个方法 encodeBegin,委托给三个帮助器方法 encodeLabel、encodeInput 和 encodeMessage,如下所示:

@Override public void encodeBegin(FacesContext context, UIComponent component) throws IOException { FieldComponent fieldComponent = (FieldComponent) component; ResponseWriter writer = context.getResponseWriter(); encodeLabel(writer,fieldComponent); encodeInput(writer,fieldComponent); encodeMessage(context, writer, fieldComponent); writer.flush(); }


encodeLabel 方法负责在出错的时候,把标签的颜色改成红色(或者在样式表中指定的其他什么颜色),并用星号 (*) 标出必需的字段,如下所示:

private void encodeLabel(ResponseWriter writer, FieldComponent fieldComponent) throws IOException{ writer.startElement("label", fieldComponent); if (fieldComponent.isError()) { String errorStyleClass = (String) fieldComponent.getAttributes().get("errorStyleClass"); String errorStyle = (String) fieldComponent.getAttributes().get("errorStyle"); writer.writeAttribute("style", errorStyle, "style"); writer.writeAttribute("class", errorStyleClass, "class"); } writer.write("" + fieldComponent.getLabel()); if (fieldComponent.isRequired()) { writer.write("*"); } writer.endElement("label"); }


首先,encodeLabel 方法检查是否有错误,如果有就输出 errorStyle 和 errorStyleClass(更好的版本是只有在它们不为空的时候才输出 —— 但是我把它留给您做练习!)。然后帮助器方法会检查组件是不是必需的字段,如果是,就输出星号。encodeMessages 和 encodeInput 方法做的就是这件事,即输出出错消息并为 Field 组件生成 HTML 输入的文本字段。

注意,神秘方法!

您可能已经注意到,有一个方法我还没有介绍。这个方法就是这个类中的“黑马”方法。如果您阅读 Renderer(所有渲染器都要扩展的抽象类)的 javadoc,您可能会感觉到这样的方法是不需要的,现有的就足够了:这就是我最开始时想的。但是,您和我一样,都错了!

实际上,基类 Renderer 并不 自动调用 Renderer 子类的相关转换器 —— 即使 Renderer 的 javadoc 和 JSF 规范建议它这样做,它也没做。MyFaces 和 JSF RI 拥有为它们的渲染器执行这个魔术的类(特定于它们的实现),但是在核心 JSF API 中并没有涉及这项功能。

相反,需要使用方法 getConvertedValues 锁定相关的转换器并调用它。清单 9 显示的方法根据值绑定的类型找到正确的转换器:

清单 9. getConvertedValues 方法

@Override public Object getConvertedValue(FacesContext facesContext, UIComponent component, Object submittedValue) throws ConverterException { //Try to find out by value binding ValueBinding valueBinding = component.getValueBinding("value"); if (valueBinding == null) return null; Class valueType = valueBinding.getType(facesContext); if (valueType == null) return null; if (String.class.equals(valueType)) return submittedValue; if (Object.class.equals(valueType)) return submittedValue; Converter converter = ((UIInput) component).getConverter(); converter = facesContext.getApplication().createConverter(valueType); if (converter != null ) { return converter.getAsObject(facesContext, component, (String) submittedValue); }else { return submittedValue; } }


清单 9 的代码添加了 Render javadoc 和 JSF 规范都让您相信应当是自动执行的功能,而实际上并不是。另一方面,请注意如果没有 独立的 Renderer,就不需要 以上(getConvertedValues)方法。UIComponentBase 类(Field 组件的超类)在直接渲染器的情况下提供了这个功能。请接受我的建议,只在特别想尝试或者在编写商业框架的时候,才考虑采用渲染器。在其他情况下,它们不值得额外的付出。

如果想知道如何把组件和渲染器关联,那么只要看看图 6 即可。

图 6. 把渲染器映射到组件



定制标记有两个方法,分别返回组件类型和渲染器类型。这些方法用于查找配置在 faces-config.xml 中的正确的渲染器和组件。请注意(虽然图中没有)组件必须返回正确的 family 类型。





回页首

结束语

通过这些内容,您已经切实地了解了 JSF 组件开发的核心。当然,在这个领域还有许多其他主题需要涉及 —— 包括发出组件事件、国际化组件、创建 UICommand 样式的组件,以及更多。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: