はじめに
JSFは始まりの1.0の頃はGUI Editorの使用を前提としていたため、カスタムタグの塊です。また、Editorで扱いやすくするためValidatorとかConvertorも全部テンプレートに埋め込むので、faceletsと出力されるHTMLの間にはかなり乖離がありました。
しかし、それはもう遠い昔のことです。
JSF 2.x、特にJavaEE 7で取り込まれた2.2以降はbootstrapやangularを始めとしたHTML構造に強く制約を与えるテンプレートに、容易に対応できるようHTML5-Friendly Markupを標榜した書き方ができるようになりました。
PrimeFacesが使えるような開発者主導の体制ならともかく、デザイナーがテンプレートを作るような体制の場合、カスタムタグを使っていると、デザイナーの作ったテンプレートをJSFに置き換えるというタンポポ作業がツラいです。
そのため、多少冗長になったとしても可能な限りHTML Friendlyに書きたいものです。
JSFで業務システムでは無く一般のユーザーに公開するようなシステムだと、デザイナーがテンプレートを作ることが多いと思いますので、この辺が効いてきます。納期短いし。
とはいえ、残念なことにGoogleの検索結果はおろか、NetBeansのチュートリアルや入門書ですら1.0時代から引きづるカスタムタグを無駄に使った例を載せる体たらくなので、まだまだ一般的とは言えないと思うので、分かる範囲で少しまとめてみました。
基本戦略
- ValidationはValidationタグではなくBean Validationを使う
- Converterは使わずにBackingBeanのsetter/getterで対応
- 可能な限りカスタムタグは使わない
- ui:compositionとかテンプレート系はおとなしく使う
1, 2はプレゼンテーションロジックをPOJOであるBackingBeanに寄せることで、テンプレートの可読性だけではなく、ユニットテストのしやすさも向上します。
3で使うなといったカスタムタグを、4でいきなり使う宣言していますが、こういうのは割り切りです。テンプレート便利。原理主義良くない。
また、カスタムタグを使わないために、要となる技術がJSF 2.2から導入された「Pass-through elements」と「Pass-through attributes」です。
Pass-through elementsとは
jsf名前空間の属性を持つタグをUIコンポーネントとしてコンポーネントツリーに組み立てる仕様。
平たく言えば、普通のHTMLタグをJSFのコンポーネントとみなしてくれる仕様ですね。jsfc 属性の進化版と捉えても良いかもしれません。これでようやく普通に書ける。。。
使い方はXML名前空間に下記を追加。
xmlns:jsf="http://xmlns.jcp.org/jsf"
タグ側はこんな感じ。
<input type="input" jsf:id="textbox01" name="text" value="100" />
コンポーネントとして存在するIDなら何でも良いみたいですが、idなら必ずあるので、「jsf:id」を指定するのが間違いがなさそうです。
Pass-through attributesとは
与えられた属性をそのままHTMLとして出力するための仕様です。
主な用途としてはいくつかの要因でJSF上、IDやnameなどが重複してしまう場合と、カスタム属性(data-xxx)などJSFで処理できない属性を扱うためです。
使い方はXML名前空間に下記を追加。p派とpt派がありますが、私はなんとなくpt派。
xmlns:pt="http://xmlns.jcp.org/jsf/passthrough"
タグ側はこんな感じ。
<input type="radio" pt:name="item1" value="1" rendered="#{foo.bar}"/>
<input type="radio" pt:name="item1" value="2" checked="checked" rendered="#{!foo.bar}"/>
HTML Friendly チートシート
表示
Old Facelets
<h:outputText value="#{bean.id}"></h:outputText>
HTML Friendly
#{bean.id}
備考
EL式だけでOK. 必要なら"span要素"や"p要素"と組合せる。
テキストボックス
Old Facelets
<h:inputText value='#{bean.id}' />
HTML Friendly
<input jsf:id='test' type='text' jsf:value='#{bean.id}' />
備考
input要素を使う。属性にJSFと連携させるために"jsf:value"を使用する
テキストエリア
Old Facelets
<h:inputTextarea value='#{bean.text}' />
HTML Friendly
<textarea jsf:id='test'>#{bean.text}</textarea>
備考
普通にtextarea要素を使う。
ラジオボックス
Old Facelets
<h:selectOneRadio value="#{bean.sport}">
<f:selectItem itemValue="sport1" itemLabel="野球"/>
<f:selectItem itemValue="sport2" itemLabel="サッカー"/>
</h:selectOneRadio>
HTML Friendly
<f:metadata>
<f:viewParam name="sport" value="#{bean.sport}"/>
</f:metadata>
<input type="radio" jsf:id="sport1" pt:name="sport" value="sport1">
<f:passThroughAttributes value="#{(bean.sport1)?{'checked':'checked'}:{'selected':'false'}}"/>
</input>
<label>野球</label>
<input type="radio" jsf:id="sport2" pt:name="sport" value="sport2">
<f:passThroughAttributes value="#{(bean.sport1)?{'checked':'checked'}:{'selected':'false'}}"/>
</input>
<label>サッカー</label>
備考
「type="radio"」はパススルー属性じゃないのでそれなりに頑張りが必要。
viewParamとpassThroughAttributesを組み合わせて対応。
passThroughAttributesに空の連想配列を渡すことは出来ないようなので、"checked"で無い時はダミーの要素を追加。ちょっと冗長なのが難点。。。
チェックボックス
Old Facelets
<h:selectBooleanCheckbox value="#{bean.item}" />
HTML Friendly
<input type="checkbox" jsf:id="item" jsf:value="#{bean.item}" />
備考
基本的にはinputboxのまま。radioと違ってcheckboxはselectBooleanCheckbox に変換されるのでjsf:valueの値でcheckedが生成されるため、passThroughAttributesは不要。
ドロップダウン
Old Facelets
<h:selectOneMenu value="#{simpleBean.item}">
<f:selectItems value="#{simpleBean.items}" />
</h:selectOneMenu>
HTML Friendly
<select jsf:value="#{simpleBean.item}">
<option pt:custom-data="test" jsfc="f:selectItems" value="#{simpleBean.items}" />
</select>
備考
ui:repeatは使えないので、妥協して「jsfc="f:selectItems"」を指定。optionsをf:selectItemに変換してくれれば簡単なんだけど...
ボタン
Old Facelets
<h:commandButton action="#{simpleBean.next}" value="Next" />
HTML Friendly
<button jsf:action="#{simpleBean.next}">Next</button>
備考
buttonに普通に「jsf:action」を指定するだけ。
リンク
Old Facelets
<h:link outcome="index" value="barへ"/>
HTML Friendly
<a jsf:outcome="index">barへ</a>
備考
「jsf:value」だとoutputLinkになるので注意。
まとめ
とりあえず、よく使いそうなものをまとめてみました。チートシートはちょこちょこ増やしていきたいですね。こんなのもあった方が、とかこっちの書き方のほうがとかあれば、コメントなりTwitterでお願いします。
あんまり綺麗に書けないところもありますが、カスタムタグばかりのFaceletsよりはデザイナーとの協業がずっとしやすいと思います。
JSFは便利なんですが、いろいろ頑張らないと普通に書けない部分も多いので、素直にJSP + JAX-RSの方が生産性高いんじゃないか? と思うこともしばしばありますが、それはまた別の機会に><