JSF——简单实例

简单实例
在不考虑组件有子组件的情况下,这边以实际的一个例子来说明开发组件的过程,至于考虑子组件的情况请参考专书介绍。
o 编码, 译码
o 组件卷标
o 使用自订组件
o 自订 Renderer
编码, 译码

Component可以自己负责将对象数据编码为HTML文件或其它的输出文件,也可以将这个任务委托给 Renderer,这边先介绍的是让Component自己负责编码的动作。

  这边着重的是介绍完成自订组件所必须的流程,所以我们不设计太复杂的组件,这边将完成以下的组件,这个组件会有一个输入文字字段以及一个送出按钮:

 

  您要继承UIComponent或其子类别来自订Component,由于文字字段是一个输入字段,为了方便,您可以继承UIInput类别,这可以让您省去一些处理细节的功夫,在继承UIComponent或其子类别后,与编码相关的主要有三个方法:

  • encodeBegin()
  • encodeChildren()
  • encodeEnd()

  其中encodeChildren()是在包括子组件时必须定义,Component如果它的 getRendersChildren()方法传回true时会呼叫encodeChildren()方法,预设上, getRendersChildren()方法传回false

  由于我们的自订组件相当简单,所以将编码的动作写在encodeBegin()或是encodeEnd()都可以,我们这边是定义encodeBegin ()方法:

UITextWithCmd.java

package onlyfun.caterpillar;

 

 import java.io.IOException;

 import java.util.Map;

 import javax.faces.component.UIInput;

 import javax.faces.context.FacesContext;

 import javax.faces.context.ResponseWriter;

 

 public class UITextWithCmd extends UIInput {

    private static final String TEXT = ".text";

    private static final String CMD = ".cmd";

 

    public UITextWithCmd() {

        setRendererType(null);

    }

 

    public void encodeBegin(FacesContext context)

                                      throws IOException {

        ResponseWriter writer = context.getResponseWriter();

        String clientId = getClientId(context);

 

        encodeTextField(writer, clientId);

        encodeCommand(writer, clientId);

    }

 

    public void decode(FacesContext context) {

        // .....

    }

 

    private void encodeTextField(ResponseWriter writer,

                        String clientId) throws IOException {

        writer.startElement("input", this);

        writer.writeAttribute("name", clientId + TEXT, null);

 

        Object value = getValue();

        if(value != null) {

            writer.writeAttribute("value",

                                  value.toString(), null);

        }

 

        String size = (String) getAttributes().get("size");

        if(size != null) {

            writer.writeAttribute("size", size, null);

        }

 

        writer.endElement("input");

    }

 

    private void encodeCommand(ResponseWriter writer,

                        String clientId) throws IOException {

        writer.startElement("input", this);

        writer.writeAttribute("type", "submit", null);

        writer.writeAttribute("name", clientId + CMD, null);

        writer.writeAttribute("value", "submit", null);

        writer.endElement("input");

    }

 }

  在encodeBegin()方法中,我们取得ResponseWriter对象,这个对象可以协助您输出HTML卷标、属性等,我们使用 getClientId()取得组件的id,这个id是每个组件的唯一识别,预设上如果您没有指定,则JSF会自动为您产生id值。

  接着我们分别对输入文字字段及送出钮作HTML标签输出,在输出时,我们将name属性设成clientId与一个字符串值的结合(即TEXTCMD),这是为了方便在译码时,取得对应name属性的请求值。

  在encodeTextField中我们有呼叫getValue()方法,这个方法是从UIOutput继承下来的,getValue() 方法可以取得Component的设定值,这个值可能是静态的属性设定值,也可能是JSF Expression的绑定值,预设会先从组件的属性设定值开始找寻,如果找不到,再从绑定值(ValueBinding对象)中找寻,组件的属性值或绑定值的设定,是在定义Tag时要作的事。

  编碥的部份总结来说,是取得Component的值并作适当的HTML标签输出,再来我们看看译码的部份,这是定义在decode()方法中,将下面的内容加入至上面的类别定义中:

....

    public void decode(FacesContext context) {

        Map reqParaMap = context.getExternalContext().

                                getRequestParameterMap();

        String clientId = getClientId(context);

 

        String submittedValue =

                   (String) reqParaMap.get(clientId + TEXT);

        setSubmittedValue(submittedValue);

        setValid(true);

    }

 ....

  我们必须先取得RequestParameterMap,这个Map对象中填入了所有客户端传来的请求参数, Component在这个方法中有机会查询这些请求参数中,是否有自己所想要取得的数据,记得我们之前译码时,是将输入字段的name属性译码为 client id加上一个字符串值(即TEXT设定的值),所以这时,我们尝试从RequestParameterMap中取得这个请求值。

  取得请求值之后,您可以将数据藉由setSumittedValue()设定给绑定的bean,最后呼叫setValid()方法,这个方法设定为 true时,表示组件正确的获得自己的值,没有任何的错误发生。

  由于我们先不使用Renderer,所以在建构函式中,我们设定RendererTypenull,表示我们不使用Renderer进行译码输出:

public UITextWithCmd() {

        setRendererType(null);

    }

  在我们的例子中,我们都是处理字符串对象,所以这边不需要转换器,如果您需要使用转换器,可以呼叫setConverter()方法加以设定,在不使用 Renderer的时候,Component要设定转换器来自行进行字符串与对象的转换。

组件卷标

完成Component的自订,接下来要设定一个自订Tag与之对应,自订Tag的目的,在于设定 Component属性,取得Componenty型态,取得Renderer型态值等;属性的设定包括了设定静态值、设定绑定值、设定验证器等等。

  要自订与Component对应的Tag,您可以继承UIComponentTag,例如:

TextWithCmdTag.java

package onlyfun.caterpillar;

 

 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;

 

 public class TextWithCmdTag extends UIComponentTag {

    private String size;

    private String value;

 

    public String getComponentType() {

        return "onlyfun.caterpillar.TextWithCmd";

    }

 

    public String getRendererType() {

        return null;

    }

 

    public void setProperties(UIComponent component) {

        super.setProperties(component);

 

        setStringProperty(component, "size", size);

        setStringProperty(component, "value", value);

    }

 

    private void setStringProperty(UIComponent component,

                       String attrName, String attrValue) {

        if(attrValue == null)

            return;

 

        if(isValueReference(attrValue)) {

            FacesContext context =

                         FacesContext.getCurrentInstance();

            Application application =

                         context.getApplication();

            ValueBinding binding =

                   application.createValueBinding(attrValue);

            component.setValueBinding(attrName, binding);

        }

        else {

            component.getAttributes().

                          put(attrName, attrValue);

        }

    }

 

    public void release() {

        super.release();

        size = null;

        value = null;

    }

 

    public String getSize() {

        return size;

    }

 

    public void setSize(String size) {

        this.size = size;

    }

 

    public String getValue() {

        return value;

    }

 

    public void setValue(String value) {

        this.value = value;

    }

 }

  首先看到这两个方法:

public String getComponentType() {

        return "onlyfun.caterpillar.TextWithCmd";

    }

 

    public String getRendererType() {

        return null;

    }

  由于我们的Component目前不使用Renderer,所以getRendererType()传回null值,而 getComponentType()在于让JSF取得这个Tag所对应的Component,所传回的值在faces-config.xml中要有定义,例如:

....

 <component>

    <component-type>

        onlyfun.caterpillar.TextWithCmd

    </component-type>

    <component-class>

        onlyfun.caterpillar.UITextWithCmd

    </component-class>

 </component>

 ....

  藉由faces-config.xml中的定义,JSF可以得知 onlyfun.caterpillar.TextWithCmd的真正类别,而这样的定义方式很显然的,您可以随时换掉<component- class>所对应的类别,也就是说,Tag所对应的Component是可以随时替换的。

  在设定Component属性值时,可以由component.getAttributes()取得Map对象,并将卷标属性值存入Map 中,这个Map对象可以在对应的Component中使用getAttributes()取得,例如在上一个主题中的UITextWithCmd中可以如下取得存入Mapsize属性:

UITextWithCmd.java

package onlyfun.caterpillar;

 

 import java.io.IOException;

 import java.util.Map;

 

 import javax.faces.component.UIInput;

 import javax.faces.context.FacesContext;

 import javax.faces.context.ResponseWriter;

 

 public class UITextWithCmd extends UIInput {

    ....

    private void encodeTextField(ResponseWriter writer,

                        String clientId) throws IOException {

        ....

 

        String size = (String) getAttributes().get("size");

        if(size != null) {

            writer.writeAttribute("size", size, null);

        }

        .....

    }

    ....

 }

  可以使用isValueReference()来测试是否为JSF Expression Language的绑定语法,如果是的话,则我们必须建立ValueBinding对象,并设定值绑定:

....

 private void setStringProperty(UIComponent component,

                        String attrName, String attrValue) {

        if(attrValue == null)

            return;

 

        if(isValueReference(attrValue)) {

            FacesContext context =

                          FacesContext.getCurrentInstance();

            Application application =

                          context.getApplication();

            ValueBinding binding =

                   application.createValueBinding(attrValue);

            component.setValueBinding(attrName, binding);

        }

        else {

            component.getAttributes().

                         put(attrName, attrValue);

        }

    }

 ....

  如果是value属性,记得在上一个主题中我们提过,从UIOutput继承下来的getValue()方法可以取得 Componentvalue设定值,这个值可能是静态的属性设定值,也可能是JSF Expression的绑定值,预设会先从组件的属性设定值开始找寻,如果找不到,再从绑定值(ValueBinding对象)中找寻。

  最后,我们必须提供自订Tagtld档:

textcmd.tld

<?xml version="1.0" encoding="UTF-8"?>

 <taglib version="2.0"

     xmlns="http://java.sun.com/xml/ns/j2ee"

     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation=

 "http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd">

 <tlib-version>1.0</tlib-version>

 <jsp-version>2.0</jsp-version>

 <short-name>textcmd</short-name>

 <uri>http://caterpillar.onlyfun.net/textcmd</uri>

 

 <tag>

     <name>textcmd</name>

     <tag-class>onlyfun.caterpillar.TextWithCmdTag</tag-class>

     <body-content>empty</body-content>

     <attribute>

        <name>size</name>

     </attribute>

     <attribute>

        <name>value</name>

        <required>true</required>

     </attribute>

 </tag>

 

 </taglib>

使用自订组件

ComponentTag自订完成后,这边来看看如何使用它们,首先定义faces-config.xml

faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>

 <!DOCTYPE faces-config PUBLIC "-//Sun Microsystems,

  Inc.//DTD JavaServer Faces Config 1.0//EN"

 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 <faces-config>

    <component>

        <component-type>

            onlyfun.caterpillar.TextWithCmd

        </component-type>

        <component-class>

            onlyfun.caterpillar.UITextWithCmd

        </component-class>

    </component>

    <managed-bean>

        <managed-bean-name>someBean</managed-bean-name>

        <managed-bean-class>

            onlyfun.caterpillar.SomeBean

        </managed-bean-class>

        <managed-bean-scope>session</managed-bean-scope>

    </managed-bean>

 </faces-config>

  <component>中定义Component的型态与实际的类别对应,在您于自订Tag中呼叫 getComponentType()方法所返回的值,就是寻找<component-type>设定的值对应,并由此得知真正对应的 Component类别。

  我们所撰写的SomeBean测试类别如下:

SomeBean

package onlyfun.caterpillar;

 

 public class SomeBean {

    private String data;

 

    public String getData() {

        return data;

    }

 

    public void setData(String data) {

        this.data = data;

    }

 }

  这边写一个简单的网页来测试一下我们撰写的自订组件:

index.jsp

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

 <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

 <%@ taglib uri="/WEB-INF/textcmd.tld" prefix="oc" %>

 <html>

 <link href="styles.css" rel="stylesheet" type="text/css"/>

 <head>

 <title></title>

 </head>

 <body>

 <f:view>

     <h:form>

     Input data: <oc:textcmd size="10"

                             value="#{someBean.data}"/>

     </h:form>

     <h:outputText value="#{someBean.data}"/>

 </f:view>

 </body>      

 </html>

自订 Renderer

Component可以将译码、编码的动作交给Renderer,这让您的表现层技术可以轻易的抽换,我们可以将之前的自订组件的译码、编码动作移出至 Renderer,不过由于我们之前设计的Component是个很简单的组件,事实上,如果只是要新增一个Command在输入字段旁边,我们并不需要大费周章的自订一个新的组件,我们可以直接为输入字段更换一个自订的Renderer

要自订一个Renderer,您要继承javax.faces.render.Renderer,我们的自订Renderer如下:

TextCmdRenderer.java

package onlyfun.caterpillar;

 

 import java.io.IOException;

 import java.util.Map;

 import javax.faces.component.EditableValueHolder;

 import javax.faces.component.UIComponent;

 import javax.faces.component.UIInput;

 import javax.faces.context.FacesContext;

 import javax.faces.context.ResponseWriter;

 import javax.faces.render.Renderer;

 

 public class TextCmdRenderer extends Renderer {

    private static final String TEXT = ".text";

    private static final String CMD = ".cmd";

 

    public void encodeBegin(FacesContext context,

                 UIComponent component) throws IOException {

        ResponseWriter writer = context.getResponseWriter();

        String clientId = component.getClientId(context);

 

        encodeTextField(component, writer, clientId);

        encodeCommand(component, writer, clientId);

    }

 

    public void decode(FacesContext context,

                               UIComponent component) {

        Map reqParaMap = context.getExternalContext().

                                   getRequestParameterMap();

        String clientId = component.getClientId(context);

 

        String submittedValue =

                  (String) reqParaMap.get(clientId + TEXT);

        ((EditableValueHolder) component).setSubmittedValue(

                                             submittedValue);

        ((EditableValueHolder) component).setValid(true);

    }

 

    private void encodeTextField(UIComponent component,

                     ResponseWriter writer, String clientId)

                                      throws IOException {

        writer.startElement("input", component);

        writer.writeAttribute("name", clientId + TEXT, null);

 

        Object value = ((UIInput) component).getValue();

        if(value != null) {

            writer.writeAttribute("value",

                         alue.toString(), null);

        }

 

        String size =

             (String) component.getAttributes().get("size");

        if(size != null) {

            writer.writeAttribute("size", size, null);

        }

 

        writer.endElement("input");

    }

 

    private void encodeCommand(UIComponent component,

                        ResponseWriter writer,

                        String clientId) throws IOException {

        writer.startElement("input", component);

        writer.writeAttribute("type", "submit", null);

        writer.writeAttribute("name", clientId + CMD, null);

        writer.writeAttribute("value", "submit", null);

        writer.endElement("input");

    }

 }

这个自订的Renderer其译码、编码过程,与之前直接在Component中进行译码或编码过程是类似的,所不同的是在译码与编码的方法上,多了 UIComponent参数,代表所代理绘制的Component

接下来在自订Tag上,我们的TextWithCmdTag与之前主题所介绍的没什么差别,只不过在getComponentType() getRendererType()方法上要修改一下:

TextWithCmdTag.java

package onlyfun.caterpillar;

 

 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;

 

 public class TextWithCmdTag extends UIComponentTag {

    private String size;

    private String value;

 

    public String getComponentType() {

        return "javax.faces.Input";

    }

 

    public String getRendererType() {

        return "onlyfun.caterpillar.TextCmd";

    }

    .....

 }

getComponentType()取得的是"javax.faces.Input",它实际上对应至UIInput类别,而 getRendererType()取回的是"onlyfun.caterpillar.TextCmd",这会在faces-config.xml中定 义,以对应至实际的Renderer类别:

faces-config.xml

....

 <faces-config>

     <render-kit>

         <renderer>

             <component-family>

                 javax.faces.Input

             </component-family>

             <renderer-type>

                 onlyfun.caterpillar.TextCmd

             </renderer-type>

             <renderer-class>

                 onlyfun.caterpillar.TextCmdRenderer

             </renderer-class>

         </renderer>

     </render-kit>

 ....

 </faces-config>

Component定义一个Renderer,必须由component familyrenderer type共同定义,这并不难理解,因为一个Component可以搭配不同的Renderer,但它是属于同一个component family,例如UIInput就是属于javax.faces.Input这个组件家族,而我们为它定义一个新的Renderer

接下未完成的范例可以取之前主题介绍过的,我们虽然没有自订组件,但我们为UIInput置换了一个新的Renderer,这个Renderer会在输入字段上加入一个按钮。

如果您坚持使用之前自订的UITextWithCmd,则可以如下修改:

UITextWithCmd.java

package onlyfun.caterpillar;

 

 import javax.faces.component.UIInput;

 

 public class UITextWithCmd extends UIInput {

    public UITextWithCmd() {

        setRendererType("onlyfun.caterpillar.TextCmd");

    }

 }

我们只是单纯的继承UIInput,然后使用setRendererType()设定"onlyfun.caterpillar.TextCmd",但并 没有为组件加入什么行为,看来什么事都没有作,但事实上这是因为继承了UIInput,它为我们处理了大多数的细节。

接下来同样的,设定自订Tag

TextWithCmdTag.java

package onlyfun.caterpillar;

 

 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;

 

 public class TextWithCmdTag extends UIComponentTag {

    private String size;

    private String value;

 

    public String getComponentType() {

        return "onlyfun.caterpillar.TextWithCmd";

    }

 

    public String getRendererType() {

        return "onlyfun.caterpillar.TextCmd";

    }

    .....

 }

要使用自订的Component,记得要在faces-config.xml中再加入:

....

    <component>

        <component-type>

            onlyfun.caterpillar.TextWithCmd

        </component-type>

        <component-class>

            onlyfun.caterpillar.UITextWithCmd

        </component-class>

    </component>

 ...

 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值