環境
javaEE 6、jsf2.1
問題
<!-- 前処理 -->
<f:event type="preRenderView" listener="#{xxx.init}">
<!-- 前処理でフィルタしたyyy.itemsに対して繰り返し処理を実施 -->
<c:forEach var="item" items="#{yyy.items}">
<c:forEach>の結果が期待通りにならない。
前処理の結果が反映されていない状態で<c:forEach>が実行されている。
原因
<c:forEach>などのタグハンドラはコンポーネントツリーのビルドやレンダリングの前に実行される。
そのためpreRenderViewの前に<c:forEach>が評価されてしまっているのが原因。
タグハンドラとコンポーネントの処理タイミングの違いは下記サイトが分かりやすい。
http://www.ninthavenue.com.au/jsf-c-foreach-vs-ui-repeat
タグハンドラはコンポーネントではなく、コンポーネントツリーを作るのを補助するのが役目なので、
コンポーネントをビルドしたりレンダリングする前に動く必要があるらしい。
対応方法1
<c:forEach>ではなく<ui:repeat>を利用する
<c:forEach var="item" items="#{yyy.items}">
↓
<ui:repeat var="item" value="#{yyy.items}">
対応方法2
前処理をpreRenderViewではなく、タグハンドラの評価タイミングで実施する
直接xhtmlにEL式を記述する。(あまり良い方法ではないと思うが)
<f:event type="preRenderView" listener="#{xxx.init}">
↓
# {xxx.init()}
ちなみにタグハンドラから前処理を呼ぶ方法は色々試してみたが、うまく動作しなかった。
詳細は後述。
うまくいかなかった対応1
<f:event>のpreRenderView以外のフェーズで前処理実施
<f:event>で指定できる各種フェーズで実行してみた結果。
- preValidate:動作しない
- postValidate:動作しない
- postAddToView:タグハンドラの実行後に動作
- preRenderComponent:タグハンドラの実行後に動作
- preRenderView:タグハンドラの実行後に動作
<f:event>で指定できるフェーズ一覧。
https://javaserverfaces.java.net/docs/2.1/vdldocs/facelets/f/event.html
おそらく、コンポーネントツリーをビルドするのが「Render Response」フェーズが初めてなので、
そのタイミングで「Apply Request Values」や「Process Validations」フェーズにリスナーを仕込むことはできないのだと思う。
ちなみに一度開いた画面でsubmitした場合は、すべてのケースで期待されるフェーズで動作していた。
前回の「Render Response」フェーズで既にコンポーネントツリーを作成しているので、
「Apply Request Values」や「Process Validations」フェーズにリスナーを仕込むことができたのだと思う。
厳密にどのフェーズで実行されるのが正しいかは下記を参考に。
http://stackoverflow.com/questions/13999099/jsf-execution-order-of-fevents
うまくいかなかった対応2
<f:event type="preRenderView">ではなく、<f:viewAction>を利用する
Java EE7、jsf2.2以降なら<f:viewAction>が利用できる。
ただし、動作検証していないが、<f:event>でフェーズ指定しても正しく動かなかったのと同じ理由で<f:viewAction>もダメな気がする。
ただし、下記のコメントではできたと報告があるので要検証。
http://kikutaro777.hatenablog.com/entry/20121210/1355146339
うまくいかなかった対応3
タグハンドラで前処理を実施
<c:set>などのタグハンドラや、<f:viewParam>で試してみたがうまく動作しなかった。
<f:event type="preRenderView" listener="#{xxx.init}">
↓
// 動かない
<c:set var="test" value="#{xxx.init()}" />
// 動かない
<f:viewParam id="id" name="name" value="#{xxx.init()}" />
参考)<f:viewParam>を使った方法
http://stackoverflow.com/questions/8156351/jsf-method-of-fevent-prerenderview-called-after-cforeach