自訂 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 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>
 ...