Web的开发就是组件的开发 ,JSF的组件开发实例(下)

本文详细解析了JSF中的Field组件,包括其如何整合标签、文本输入和消息功能,以及如何处理值绑定、请求参数解码和出错消息。通过具体示例展示了Field组件在实际应用中的使用方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

复合组件

在下一个示例中,我将介绍如何创建这样一个组件(和标记),它可以记住最后一个人离开的位置。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 组件
5

清单 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 组件却有多个属性,即 labelerrorStyleerrorStyleClassvaluelabelvalue 属性位于 Field 组件的核心,而 errorStyleerrorStyleClass 是特定于 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)
      */
     @Override
     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);
     
     
         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 属性传递时的方式与前面的示例相同,但是 errorStyleClasserrorStyle 属性不是这样传递的。相反,它们被添加到 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,委托给三个帮助器方法 encodeLabelencodeInputencodeMessage,如下所示:

@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 方法检查是否有错误,如果有就输出 errorStyleerrorStyleClass(更好的版本是只有在它们不为空的时候才输出 —— 但是我把它留给您做练习!)。然后帮助器方法会检查组件是不是必需的字段,如果是,就输出星号。encodeMessagesencodeInput 方法做的就是这件事,即输出出错消息并为 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. 把渲染器映射到组件
6

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

 

http://hi.baidu.com/rover828/blog/item/e28bdbc43145accf38db49ec.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值