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與一個字串值的結合(即TEXT或CMD),這是為了方便在解碼時,取得對應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);
}
....
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,所以在建構函式中,我們設定RendererType為null,表示我們不使用Renderer進行解碼輸出:
public UITextWithCmd() {
setRendererType(null);
}
setRendererType(null);
}
在我們的例子中,我們都是處理字串物件,所以這邊不需要轉換器,如果您需要使用轉換器,可以呼叫setConverter()方法加以設定,在不使用 Renderer的時候,Component要設定轉換器來自行進行字串與物件的轉換。