傳統 JSF 組件開發需手寫 encode/decode、saveState/restoreState、實現 NamingContainer/StateHolder 等接口,複雜度極高。本文提出「重用標準組件」策略,通過 ValueScroller(數值增減輸入組件)示例,展示如何零渲染器代碼、零狀態管理代碼完成復合組件開發。
UIPanel,子組件用 UIInput、UICommand、UIGraphic,直接設置標準渲染器類型(javax.faces.Grid、javax.faces.Text、javax.faces.Link、javax.faces.Image),框架自動完成編碼/解碼。javax.faces.component.html,僅用抽象基類;模型類純 POJO 無 UI 依賴,便於跨框架重用。public class ValueScroller extends UIPanel {
private UIInput input;
private static final String PANEL_GRID_RENDERER = "javax.faces.Grid";
private static final String INPUT_TEXT_RENDERER = "javax.faces.Text";
private static final String COMMAND_LINK_RENDERER = "javax.faces.Link";
private static final String GRAPHIC_IMAGE_RENDERER = "javax.faces.Image";
public ValueScroller() { super(); addChildrenAndFaces(); }
private void addChildrenAndFaces() {
// 容器屬性
setRendererType(PANEL_GRID_RENDERER);
getAttributes().put("columns", 2);
// 數值輸入框
input = new UIInput();
input.setId("input");
input.setRendererType(INPUT_TEXT_RENDERER);
getChildren().add(input);
// 增減按鈕面板
UIPanel linkPanel = new UIPanel();
linkPanel.setId("linkPanel");
linkPanel.setRendererType(PANEL_GRID_RENDERER);
linkPanel.getAttributes().put("columns", 1);
ScrollerActionListener listener = new ScrollerActionListener();
// 增加鏈接 + 圖標
UICommand upLink = new UICommand();
upLink.setId("upLink");
upLink.setRendererType(COMMAND_LINK_RENDERER);
upLink.addActionListener(listener);
UIGraphic upImg = new UIGraphic();
upImg.setId("upImg");
upImg.setRendererType(GRAPHIC_IMAGE_RENDERER);
upImg.setUrl("/images/up.gif");
upLink.getChildren().add(upImg);
linkPanel.getChildren().add(upLink);
// 減少鏈接 + 圖標
UICommand downLink = new UICommand();
downLink.setId("downLink");
downLink.setRendererType(COMMAND_LINK_RENDERER);
downLink.addActionListener(listener);
UIGraphic downImg = new UIGraphic();
downImg.setId("downImg");
downImg.setRendererType(GRAPHIC_IMAGE_RENDERER);
downImg.setUrl("/images/down.gif");
downLink.getChildren().add(downImg);
linkPanel.getChildren().add(downLink);
getChildren().add(linkPanel);
}
// 內部 ActionListener 直接操作 input 值
private class ScrollerActionListener implements ActionListener {
public void processAction(ActionEvent e) {
if (!(input.getValue() instanceof Integer)) throw new IllegalArgumentException("Only Integer supported");
int val = (Integer) input.getValue();
String cmdId = ((UICommand) e.getSource()).getId();
int step = getStep(), min = getMin(), max = getMax();
if (cmdId.equals("upLink")) {
input.setValue(val + step > max ? max : val + step);
} else if (cmdId.equals("downLink")) {
input.setValue(val - step < min ? min : val - step);
}
}
}
// min/max/step/value 屬性省略 getter/setter
}
public class ValueScrollerTag extends UIComponentTag {
protected void setProperties(UIComponent component) {
super.setProperties(component);
ValueScroller vs = (ValueScroller) component;
Application app = FacesContext.getCurrentInstance().getApplication();
// value 必須為 ValueBinding 表達式
if (value != null) {
if (isValueReference(value)) vs.setValueBinding("value", app.createValueBinding(value));
else throw new IllegalArgumentException("value must be a binding expression");
}
if (id != null) vs.setId(id);
// 自定義屬性直接設組件
vs.setMin(min); vs.setMax(max); vs.setStep(step);
// 標準屬性直接傳給子組件
vs.findComponent("input").getAttributes().put("size", new Integer(size));
}
public String getComponentType() { return "xyz.ValueScroller"; }
public String getRendererType() { return null; } // 無需自定義渲染器
}
<tag>
<name>valueScroller</name>
<tag-class>component.taglib.ValueScrollerTag</tag-class>
<body-content>JSP</body-content>
<attribute><name>value</name></attribute> <!-- ValueBinding -->
<attribute><name>size</name></attribute> <!-- 轉發給 UIInput -->
<attribute><name>min</name></attribute>
<attribute><name>max</name></attribute>
<attribute><name>step</name></attribute>
</tag>
<!-- faces-config.xml -->
<component>
<component-type>xyz.ValueScroller</component-type>
<component-class>component.ValueScroller</component-class>
</component>
<!-- Test.jsp -->
<%@ taglib uri="/WEB-INF/lib/ValueScroller.tld" prefix="xyz" %>
<xyz:valueScroller value="#{pc_Test.itemCount}" size="5" min="-50" max="10000" step="2"/>
| 組件族 | 組件類 | 渲染器類型 | HTML 輸出 |
|---|---|---|---|
| Input | UIInput / HtmlInputText | javax.faces.Text | <input type="text"> |
| Command | UICommand | javax.faces.Link / Button | <a> / <button> |
| Graphic | UIGraphic | javax.faces.Image | <img> |
| Panel | UIPanel | javax.faces.Grid / Group | <table> / <span> |
| Output | UIOutput | javax.faces.Text | 純文本 |
encodeBegin/encodeEnd/decode 代碼。saveState/restoreState 代碼(標準組件自動管理)。源碼下載:配套源碼