3 自订组件
3.2 简单实例
3.2.2 组件标签
在我们的例子中,我们都是处理字符串对象,所以这边不需要转换器,如果您需要使用转 换器,可以呼叫 setConverter()方法加以设定,在不使用 Renderer 的时候,Component 要设定转换器来自行进行字符串与对象的转换。
3.2.2 组件标签
完成 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);
private void setStringProperty(UIComponent
component,
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,所传回的值在
<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 中可以如下取得存入 Map 的 size 属性:
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 {
可以使用 isValueReference()来测试是否为 JSF Expression Language 的绑定语法,如果 是的话,则我们必须建立 ValueBinding 对象,并设定值绑定:
....
private void setStringProperty(UIComponent component, String attrName, String attrValue) {
如果是 value 属性,记得在上一个主题中我们提过,从 UIOutput 继承下来的 getValue() 方法可以取得 Component 的 value 设定值,这个值可能是静态的属性设定值,也可能是 JSF Expression 的绑定值,预设会先从组件的属性设定值开始找寻,如果找不到,再从绑定值
(ValueBinding 对象)中找寻。
最后,我们必须提供自订 Tag 的 tld 檔:
• 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-c lass>
<body-content>empty</body-content>
<attribute>
<name>size</name>
</attribute>
<attribute>
<name>value</name>
<required>true</required>
</attribute>
</tag>
</taglib>
3.2.3 使用自订组件
在 Component 与 Tag 自订完成后,这边来看看如何使用它们,首先定义 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.java
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>
3.2.4 自订 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();
private void encodeTextField(UIComponent component,
ResponseWriter writer, String clientId)
throws IOException {
writer.startElement("input", component);
writer.writeAttribute("name", clientId + TEXT,
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 family 与 renderer 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>
...
4 参考数据
Core JavaServer Faces
使用 JavaServer Faces 开发 UI A first look at JavaServer Faces
Using JavaServer Faces Technology in JSP Pages Free JSF (JavaServer Faces) Training Materials Craig McClanahan's Weblog
JSF Central
Java Server Faces Resources