taglib+テンプレートベースの部品コンポーネントの作成
JSPのtaglibを書いていて思うのだけど、Javaのソースコード内に直接HTMLのタグを書くのはあまり気持ちよくない。
解決策として、パラメータを加工してコンテキストに渡すだけのカスタムタグを作り、HTMLのレンダリングはタグで囲まれた内側のHTML部分でおこなうという方法をとってみた。
さらに毎回タグ内部にHTMLを書くのは無駄なので、カスタムタグ+内部のHTMLをセットでHTML部品として用意し、それをincludeするだけでtaglibのように使用できるようにした。
実例として、POJOの配列/コレクションからselectタグを作る部品と、日付入力部品の二つを作ってみた。
今回はJSF+Faceletsでやったけど、スコープを持っていて外部HTMLをincludeできるようなテンプレートエンジンなら同じようにできるはず。
感想・まとめ
InterfaceBuilder.appやらWebObjectsやらで育った身としては、コードとリソースを分けて書けるのはやはりとても気持ちがよい。
後半はコンポーネントを組み合わせてコンポーネントを作る話になっていて少し趣旨が変わってしまったかも?再利用可能なコンポーネントを組み合わせて再利用可能なコンポーネントを作っていくような部品の再利用方法は、JSFだと結構面倒くさい。
これは一つには、値のアップデート先となるオブジェクトがBacking Beanに限られていて、子コンポーネントの値を親コンポーネントが自動的に受け取るような仕組みが無いため。IDを決めうちでつけて((ValueHolder) findComponent(〔ID文字列〕)).getValue()するとか、今回のように宙ぶらりんな一時オブジェクトにアップデートさせたり、無理に実現する事は出来なくはない。
しかし、さらに入力に対するバリデーションを考えると頭が痛い。バリデーションフェーズとモデル更新フェーズが分離されているため、親のバリデーションに子の更新値を使う事が出来ないため。
やっぱり素直にWebObjectsとかTapestryとかAribaWebを使った方が良いかも?
環境
使用サンプル1. POJOの配列/コレクションからselectタグを生成
イメージ湧かないと思うので実例から。
プルダウン(selectタグ)のoptionにf:selectItemsを使う時、パラメータで渡すSelectItemのリストを作る必要があるのだけど、元データがPOJOの場合、わざわざリストの種類ごとにメソッドを作ってSelectItemを生成するのは嫌だし、そもそもモデルクラスをSelectItemクラスに依存させるのが嫌だ。そこで、POJOからSelectItemを生成して渡してくれるだけのFaceletsのカスタムタグ(カスタムTagHandler)を作って解決しようと思う。
作ったカスタムタグを含む部品テンプレート(components/listSelector.xhtml):
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:sample="http://terazzo.dyndns.org/jsf/sample"> <ui:component> <!-- listの中身からSelectItemのリストを生成してselectItemsVarにセットする制御専用タグ --> <sample:listSelector list="${list}" valueKey="${valueKey}" labelKey="${labelKey}" selectItemsVar="selectItems"> <!-- 実際のHTMLのレンダリング用タグはsample:listSelectorタグの内部に書く --> <h:selectOneMenu value="${value}"> <f:selectItems value="#{selectItems}"/> </h:selectOneMenu> </sample:listSelector> </ui:component> </html>
呼び出し側(pages/register.xhtml):
<h:form> ... 血液型: <ui:include src="/components/listSelector.xhtml"> <ui:param name="value" value="${member.bloodType}" /> <ui:param name="list" value="${samplePage.bloodTypes}" /> <ui:param name="labelKey" value="label" /> </ui:include> ... <br /> <h:commandButton action="#{samplePage.update}" value="Submit" /> </h:form>
この例では、samplePageのbloodTypesには血液型を表すBloodTypeというenumの配列が入っている。BloodTypeのlabelプロパティ(「A型」など)の値を表示タイトルとしてプルダウンを表示し、ユーザが選択したBloodTypeをmemberのbloodTypeとしてセットする。
レンダリング結果(空白や改行や実体参照は読みやすく変えています):
血液型: <select name="j_id2:j_id20" size="1"> <option value="A">A型</option> <option value="B">B型</option> <option value="O">O型</option> <option value="AB">AB型</option></select> ....
SelectItemを生成するTagHandler(ListSelectorTagHandler)の実装
方針:
- UIComponentは使用せずFaceletsのTagHandlerのみで実装
- listで受け取るのは配列またはコレクション
- listの各要素を元にSelectItemを生成する
- 表示タイトルは、labelKeyで指定された文字列をプロパティ名としてlistの各要素から取得する
- 選択時の値は、valueKeyで指定された文字列をプロパティ名としてlistの各要素から取得する
- labelKey, valueKeyが無指定の場合はlistの各要素をそのまま使用する
- 生成したSelectItemのリストはselectItemsVarで指定された文字列でコンテキストにセットする
package sample.faces.component; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.el.ELException; import javax.faces.FacesException; import javax.faces.component.UIComponent; import javax.faces.model.SelectItem; import org.apache.commons.beanutils.PropertyUtils; import com.sun.facelets.FaceletContext; import com.sun.facelets.FaceletException; import com.sun.facelets.tag.TagAttribute; import com.sun.facelets.tag.TagConfig; import com.sun.facelets.tag.TagHandler; /** listの中身からSelectItemのリストを生成してselectItemsVarにセットするTagHandler */ public class ListSelectorTagHandler extends TagHandler { private static final String LIST_ATTRIBUTE_NAME = "list"; private static final String VALUE_KEY_ATTRIBUTE_NAME = "valueKey"; private static final String LABEL_KEY_ATTRIBUTE_NAME = "labelKey"; private static final String SELECT_ITEMS_VAR_ATTRIBUTE_NAME = "selectItemsVar"; /** 選択肢の元になるListを渡す */ private TagAttribute listAttribute; /** SelectItemのリストをセットするキーを */ private TagAttribute selectItemsVarAttribute; /** 要素から値を取り出すキー */ private TagAttribute valueKeyAttribute; /** 要素からラベル(表示タイトル)を取り出すキー */ private TagAttribute labelKeyAttribute; /** * コンストラクタ。 */ public ListSelectorTagHandler(TagConfig config) { super(config); this.listAttribute = getRequiredAttribute(LIST_ATTRIBUTE_NAME); this.selectItemsVarAttribute = getRequiredAttribute(SELECT_ITEMS_VAR_ATTRIBUTE_NAME); this.valueKeyAttribute = getAttribute(VALUE_KEY_ATTRIBUTE_NAME); this.labelKeyAttribute = getAttribute(LABEL_KEY_ATTRIBUTE_NAME); } /** * apply(FaceletContext, UIComponent)の実装。 */ public void apply(FaceletContext context, UIComponent parent) throws IOException, FacesException, FaceletException, ELException { String valueKey = (String) this.valueKeyAttribute.getObject(context); String labelKey = (String) this.labelKeyAttribute.getObject(context); /* 元リストからSelectItemのListを生成 */ Object listObject = listAttribute.getObject(context); Iterable dataList; if (listObject instanceof Iterable) { dataList = (Iterable)listObject; } else if (listObject.getClass().isArray()) { dataList = Arrays.asList((Object[]) listObject); } else { throw new FacesException("list is not iterable."); } List<SelectItem> selectItems = createSelectItems(context, dataList, valueKey, labelKey); /* SelectItemのListをセット */ String selectItemsVar = selectItemsVarAttribute.getValue(); context.setAttribute(selectItemsVar, selectItems); nextHandler.apply(context, parent); } /** * dataListの各要素からSelectItemを生成し、Listで戻す。 * @param dataList 選択肢の元になる要素を含んだList * @param valueKey dataListの要素から値を取り出す為のキー文字列。 nullならばdataListの要素を値として使用する。 * @param labelKey dataListの要素からラベル(表示タイトル)を取り出す為の キー文字列。nullならばdataListの要素をラベルとして使用する。 * @throws FacesException 値またはラベルの取り出し時に例外が 発生した場合、FacesExceptionにラップしてthrowする。 */ private List<SelectItem> createSelectItems( FaceletContext context, Iterable dataList, String valueKey, String labelKey) throws FacesException { List<SelectItem>selectItems = new ArrayList<SelectItem>(); if (dataList != null) { for (Object data : dataList) { try { Object dataValue = (valueKey != null) ? PropertyUtils.getProperty(data, valueKey) : data; Object dataLabel = (labelKey != null) ? PropertyUtils.getProperty(data, labelKey) : data; selectItems.add(new SelectItem(dataValue, dataLabel.toString())); } catch (IllegalAccessException e) { e.printStackTrace(); throw new FacesException("Failed to create selectItems. " + e.getMessage(), e); } catch (InvocationTargetException e) { e.printStackTrace(); throw new FacesException("Failed to create selectItems. " + e.getMessage(), e); } catch (NoSuchMethodException e) { e.printStackTrace(); throw new FacesException("Failed to create selectItems. " + e.getMessage(), e); } } } return selectItems; } }
sample-taglib.xml:
<?xml version="1.0"?> <!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"> <facelet-taglib> <namespace>http://terazzo.dyndns.org/jsf/sample</namespace> ... <tag> <tag-name>listSelector</tag-name> <handler-class>sample.faces.component.ListSelectorTagHandler</handler-class> </tag> </facelet-taglib>
web.xml:
... <context-param> <param-name>facelets.LIBRARIES</param-name> <param-value> /WEB-INF/tags/sample.taglib.xml </param-value> </context-param> ...
使用サンプル2. 日付入力部品
年月日がそれぞれプルダウンの日付入力部品。コンポーネント内でselectタグを書くのは嫌なので、日付と{年、月、日}の変換だけをコンポーネントでおこない、実際のプルダウン表示はカスタムタグに囲まれた部分のHTMLでおこなう。
まず年月日をプルダウンで表示する部品テンプレート(components/date.xhtml):
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:sample="http://terazzo.dyndns.org/jsf/sample"> <ui:component> <!-- valueから取得した日付を元に、年月日保持用のテンポラリのホルダを 生成してvarにセットする制御専用タグ --> <sample:date value="${value}" var="${dateHolder}"> <!-- 実際のHTMLのレンダリング用タグはsample:dateタグの内部に書く --> <h:selectOneMenu value="${dateHolder.year}" converter="javax.faces.Integer"> <f:selectItems value="#{dateSetting.years}"/> </h:selectOneMenu> 年 <h:selectOneMenu value="${dateHolder.month}" converter="javax.faces.Integer"> <f:selectItems value="#{dateSetting.months}"/> </h:selectOneMenu> 月 <h:selectOneMenu value="${dateHolder.day}" converter="javax.faces.Integer"> <f:selectItems value="#{dateSetting.days}"/> </h:selectOneMenu> 日 </sample:date> </ui:component> </html>
呼び出し側(pages/register.xhtml):
<h:form> ... 生年月日: <ui:include src="/components/date.xhtml"> <ui:param name="value" value="${member.birthday}" /> </ui:include> ... <br /> <h:commandButton action="#{samplePage.update}" value="Submit" /> </h:form>
この例では、プルダウンで選択された年月日がmemberのbirthdayにセットされる
レンダリング結果
生年月日:<select name="j_id2:j_id5:j_id6" size="1"> <option value="2000">2000</option> <option value="2001">2001</option> ... <option value="2019">2019</option> <option value="2020">2020</option></select> 年 <select name="j_id2:j_id5:j_id9" size="1"> <option value="1">1</option> <option value="2">2</option> ... <option value="12">12</option></select> 月 <select name="j_id2:j_id5:j_id12" size="1"> <option value="1">1</option> <option value="2">2</option> ... <option value="31">31</option></select> 日 <br /> ....
日付コンポーネントの実装方針
いろいろなコンポーネントモデルの説明(図入り)
- コンポーネントをツリー状に入れ子に出来る。
- というかページもコンポーネントの一種である。
- 個々のコンポーネントの値は親コンポーネントのプロパティにバインドされており、親コンポーネントにアップデートされる。
- もちろんkeypathを指定してモデル内のプロパティにもバインドも出来る
- 子コンポーネントの入力値を加工して、自分の親にアップデートすることもできる。
- とても良い
今回のコンポーネントモデル
日付コンポーネントの実装
package sample.faces.component; import java.io.IOException; import java.util.Date; import javax.el.ELContext; import javax.el.ValueExpression; import javax.faces.component.NamingContainer; import javax.faces.component.UIInput; import javax.faces.context.FacesContext; public class DateInput extends UIInput implements NamingContainer { public static final String VAR_ATTRIBUTE_NAME = "var"; public static final String COMPONENT_FAMILY = "sample.faces.DateInput"; public static final String COMPONENT_TYPE = "sample.faces.DateInput"; public DateInput() { super(); } public String getFamily() { return COMPONENT_FAMILY; } @Override public void encodeBegin(FacesContext context) throws IOException { /* 子供等のencodeが呼ばれる前に値のget元を準備 */ prepareDateHolder(context); super.encodeBegin(context); } @Override public void encodeEnd(FacesContext context) throws IOException { clearDateHolder(context); super.encodeEnd(context); } public void processUpdates(FacesContext context) { /* 子供等のupdateModel()が呼ばれる前にupdate先を準備 */ prepareDateHolder(context); super.processUpdates(context); clearDateHolder(context); } public void updateModel(FacesContext context) { /* 子供等がupdateModel()でupdateした値を使って日付を生成し、自分の値としてセット */ ELContext elContext = context.getELContext(); ValueExpression var = getValueExpression(VAR_ATTRIBUTE_NAME); DateHolder dateHolder = (DateHolder) var.getValue(elContext); setValue(dateHolder.toDate()); super.updateModel(context); } /** * 子供のコンポーネントが値をセット/取得できるようにDateHolderをぶら下げる。 * @param 現在のコンテキスト */ private void prepareDateHolder(FacesContext context) { ELContext elContext = context.getELContext(); ValueExpression var = getValueExpression(VAR_ATTRIBUTE_NAME); var.setValue(elContext, new DateHolder((Date) getValue())); } /** * DateHolderをコンテキストから削除。 * @param 現在のコンテキスト */ private void clearDateHolder(FacesContext context) { ELContext elContext = context.getELContext(); ValueExpression var = getValueExpression(VAR_ATTRIBUTE_NAME); var.setValue(elContext, null); } }
年、月、日の情報を一時的に保存するDateHolderクラス
package sample.faces.component; import java.io.Serializable; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; /** * 日付の入力値を保持する */ public class DateHolder implements Serializable { /** シリアルバージョン番号 */ private static final long serialVersionUID = -318355117236831950L; private Integer year; private Integer month; private Integer day; public DateHolder() { } public DateHolder(Date date) { GregorianCalendar calendar = new GregorianCalendar(); calendar.setTime(date); year = calendar.get(Calendar.YEAR); month = (1 - Calendar.JANUARY) + calendar.get(Calendar.MONTH); day = calendar.get(Calendar.DAY_OF_MONTH); } public Date toDate() { GregorianCalendar calendar = new GregorianCalendar(); calendar.clear(); calendar.set(Calendar.YEAR, year); calendar.set(Calendar.MONTH, month - (1 - Calendar.JANUARY)); calendar.set(Calendar.DAY_OF_MONTH, day); return calendar.getTime(); } public Integer getYear() { return year; } public void setYear(Integer year) { this.year = year; } public Integer getMonth() { return month; } public void setMonth(Integer month) { this.month = month; } public Integer getDay() { return day; } public void setDay(Integer day) { this.day = day; } }
何もしないRenderクラス
package sample.faces.component; import javax.faces.render.Renderer; /** * 単純に内部をレンダリングするだけのRenderer */ public class TemplateRenderer extends Renderer { public TemplateRenderer() { super(); } public boolean getRendersChildren() { return false; } }
faces-config.xml:
<component> <component-type>sample.faces.DateInput</component-type> <component-class>sample.faces.component.DateInput</component-class> </component> <render-kit> <renderer> <component-family>sample.faces.DateInput</component-family> <renderer-type>sample.faces.TemplateRenderer</renderer-type> <renderer-class>sample.faces.component.TemplateRenderer</renderer-class> </renderer> </render-kit>
sample-taglib.xml: (tld面倒なのでComponentHandlerを指定しつつFacelets的に定義)
<?xml version="1.0"?> <!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"> <facelet-taglib> <namespace>http://terazzo.dyndns.org/jsf/sample</namespace> <tag> <tag-name>date</tag-name> <component> <component-type>sample.faces.DateInput</component-type> <renderer-type>sample.faces.TemplateRenderer</renderer-type> <handler-class>com.sun.facelets.tag.jsf.ComponentHandler</handler-class> </component> </tag> ... </facelet-taglib>
web.xml:
... <context-param> <param-name>facelets.LIBRARIES</param-name> <param-value> /WEB-INF/tags/sample.taglib.xml </param-value> </context-param> ...
使用サンプルのソースコード
サンプルページ(新規会員登録的なイメージ。) 生年月日と血液型を入力する。入力画面と確認画面のみ。
package sample.faces; import sample.model.BloodType; public class SamplePage { /** 編集中のMember */ private Member member = new Member(); public SamplePage() { super(); } public Member getMember() { return member; } /** 次へアクション。 */ public String update() { return "success"; } /** 戻るアクション。 */ public String back() { return "back"; } /** 血液型の配列を戻す。 */ public BloodType[] getBloodTypes() { return BloodType.values(); } }
血液型enum:
package sample.model; public enum BloodType { A (1, "A型"), B (2, "B型"), O (3, "O型"), AB (4, "AB型"); /** ID値 */ private int value; /** ラベル名 */ private String label; BloodType(int value, String label) { this.value = value; this.label = label; } public int getValue() { return value; } public String getLabel() { return label; } }
日付プルダウン用の設定クラス
package sample.faces.setting; import java.util.ArrayList; import java.util.List; import javax.faces.model.SelectItem; public class DateSetting { private int fromYear = 2004; private int toYear = 2010; private boolean initialized = false; private List<SelectItem> years; private List<SelectItem> months; private List<SelectItem> days; public DateSetting() { super(); } public void setFromYear(int fromYear) { this.fromYear = fromYear; } public void setToYear(int toYear) { this.toYear = toYear; } private synchronized void initializeIfNeeded() { if (initialized) { return; } this.years = new ArrayList<SelectItem>(); for (int year = fromYear; year <= toYear; year++) { years.add(new SelectItem(year)); } this.months = new ArrayList<SelectItem>(); for (int month = 1; month <= 12; month++) { this.months.add(new SelectItem(month)); } this.days = new ArrayList<SelectItem>(); for (int day = 1; day <= 31; day++) { this.days.add(new SelectItem(day)); } initialized = true; } public List<SelectItem> getYears() { initializeIfNeeded(); return years; } public List<SelectItem> getDays() { initializeIfNeeded(); return days; } public List<SelectItem> getMonths() { initializeIfNeeded(); return months; } }
入力ページ(pages/register.xhtml):
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets"> <body> <h1>登録</h1> <h:form> 生年月日: <ui:include src="/components/date.xhtml"> <ui:param name="value" value="${member.birthday}" /> </ui:include> <br /> 血液型: <ui:include src="/components/listSelector.xhtml"> <ui:param name="value" value="${member.bloodType}" /> <ui:param name="list" value="${samplePage.bloodTypes}" /> <ui:param name="labelKey" value="label" /> </ui:include> <br /> <h:commandButton action="#{samplePage.update}" value="Submit" /> </h:form> </body> </html>
確認ページ(pages/confirm.xhtml):
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"> <body> <h1>登録(確認)</h1> <h:form> 生年月日: <h:outputText value="${member.birthday}" > <f:convertDateTime pattern="yyyy年MM月dd日" timeZone="JST"/> </h:outputText><br /> 血液型: <h:outputText value="${member.bloodType.label}"/><br /> <h:commandButton action="#{samplePage.back}" value="Back" /> </h:form> </body> </html>
faces-config.xml:
<?xml version="1.0" encoding="UTF-8"?> <faces-config> <application> <!-- tell JSF to use Facelets --> <view-handler>com.sun.facelets.FaceletViewHandler</view-handler> </application> <!-- コンポーネント定義 --> <component> <component-type>sample.faces.DateInput</component-type> <component-class>sample.faces.component.DateInput</component-class> </component> <render-kit> <renderer> <component-family>sample.faces.DateInput</component-family> <renderer-type>sample.faces.TemplateRenderer</renderer-type> <renderer-class>sample.faces.component.TemplateRenderer</renderer-class> </renderer> </render-kit> <!-- 日付プルダウン用の設定(Applicationスコープ) --> <managed-bean> <managed-bean-name>dateSetting</managed-bean-name> <managed-bean-class>sample.faces.setting.DateSetting</managed-bean-class> <managed-bean-scope>application</managed-bean-scope> <managed-property> <property-name>fromYear</property-name> <value>1920</value> </managed-property> <managed-property> <property-name>toYear</property-name> <value>2020</value> </managed-property> </managed-bean> <!-- 会員情報(Sessionスコープ) --> <managed-bean> <managed-bean-name>member</managed-bean-name> <managed-bean-class>sample.faces.Member</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> <!-- ページオブジェクト(Sessionスコープ) --> <managed-bean> <managed-bean-name>samplePage</managed-bean-name> <managed-bean-class>sample.faces.SamplePage</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> <!-- 画面遷移。登録ページ⇔確認ページ --> <navigation-rule> <from-view-id>/pages/register.xhtml</from-view-id> <navigation-case> <from-outcome>success</from-outcome> <to-view-id>/pages/confirm.xhtml</to-view-id> </navigation-case> </navigation-rule> <navigation-rule> <from-view-id>/pages/confirm.xhtml</from-view-id> <navigation-case> <from-outcome>back</from-outcome> <to-view-id>/pages/register.xhtml</to-view-id> </navigation-case> </navigation-rule> </faces-config>
おまけ その1. listSelectorのプルダウンをラジオボタンに変更
htmlを変えるだけ!
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:sample="http://terazzo.dyndns.org/jsf/sample"> <ui:component> <!-- listの中身からSelectItemのリストを生成してselectItemsVarにセットする制御専用タグ --> <sample:listSelector list="${list}" valueKey="${valueKey}" labelKey="${labelKey}" selectItemsVar="selectItems"> <!-- 実際のHTMLのレンダリング用タグはsample:listSelectorタグの内部に書く --> <h:selectOneRadio value="${value}"> <f:selectItems value="#{selectItems}"/> </h:selectOneRadio> </sample:listSelector> </ui:component> </html>
おまけ その2. 日付入力部品をテキスト入力に変更
htmlを変えるだけ!
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:sample="http://terazzo.dyndns.org/jsf/sample"> <ui:component> <!-- valueから取得した日付を元に、年月日保持用のテンポラリの ホルダを生成してvarにセットする制御専用タグ --> <sample:date value="${value}" var="${dateHolder}"> <!-- 実際のHTMLのレンダリング用タグはsample:dateタグの内部に書く --> <h:inputText size="4" value="${dateHolder.year}" converter="javax.faces.Integer"/> <h:inputText size="2" value="${dateHolder.month}" converter="javax.faces.Integer"/> <h:inputText size="2" value="${dateHolder.day}" converter="javax.faces.Integer"/> </sample:date> </ui:component> </html>
おまけ その3. 日付入力部品の並びを「年月日」から「日/月/年」に変更
htmlを変えるだけ!
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:sample="http://terazzo.dyndns.org/jsf/sample"> <ui:component> <!-- valueから取得した日付を元に、年月日保持用のテンポラリの ホルダを生成してvarにセットする制御専用タグ --> <sample:date value="${value}" var="${dateHolder}"> <!-- 実際のHTMLのレンダリング用タグはsample:dateタグの内部に書く --> <h:selectOneMenu value="${dateHolder.day}" converter="javax.faces.Integer"> <f:selectItems value="#{dateSetting.days}"/> </h:selectOneMenu> / <h:selectOneMenu value="${dateHolder.month}" converter="javax.faces.Integer"> <f:selectItems value="#{dateSetting.months}"/> </h:selectOneMenu> / <h:selectOneMenu value="${dateHolder.year}" converter="javax.faces.Integer"> <f:selectItems value="#{dateSetting.years}"/> </h:selectOneMenu> </sample:date> </ui:component> </html>
おまけ その4. Member.bloodTypeにはenumでなくID値を保存
呼び出し側でvalueKeyを指定する
<h:form> ... 血液型: <ui:include src="/components/listSelector.xhtml"> <ui:param name="value" value="${member.bloodType}" /> <ui:param name="list" value="${samplePage.bloodTypes}" /> <ui:param name="labelKey" value="label" /> <ui:param name="valueKey" value="value" /> </ui:include> ... <br /> <h:commandButton action="#{samplePage.update}" value="Submit" /> </h:form>
おまけ その5. ui:include/ui:paramをやめてtaglib風に書きたい
Faceletsのtaglibファイルで定義
pages/register.xhtml:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:tmpl="http://terazzo.dyndns.org/jsf/tmpl"> <body> <h1>登録</h1> <h:form> 生年月日: <tmpl:date value="${member.birthday}" /><br /> 血液型: <tmpl:listSelector value="${member.bloodType}" list="${samplePage.bloodTypes}" labelKey="label"/><br /> <h:commandButton action="#{samplePage.update}" value="Submit" /> </h:form> </body> </html>
とてもシンプルになった。
/WEB-INF/tags/tmpl.taglib.xml:
<?xml version="1.0"?> <!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"> <facelet-taglib> <namespace>http://terazzo.dyndns.org/jsf/tmpl</namespace> <tag> <tag-name>date</tag-name> <source>../../components/date.xhtml</source> </tag> <tag> <tag-name>listSelector</tag-name> <source>../../components/listSelector.xhtml</source> </tag> </facelet-taglib>
web.xml:
... <context-param> <param-name>facelets.LIBRARIES</param-name> <param-value> /WEB-INF/tags/sample.taglib.xml;/WEB-INF/tags/tmpl.taglib.xml </param-value> </context-param> ...