💥 発生した事象
JSFでAjaxを使い、処理後に画面の一部だけを更新しようとしていました。
<f:ajax
execute="@this"
render="resultArea" />
サーバー側の処理自体は実行されているのに、
resultArea の表示だけが期待通り更新されません。
Beanの値も更新されていたため、
「Ajaxは動いているのに、なんで画面だけ変わらない?」
という状態になりました。
🚀 急いでいる人向け
f:ajax の処理は動いているのに render 先が更新されない場合は、
まず次の3つを確認します。
-
renderで指定したコンポーネントが同じ NamingContainer 内にあるか - ブラウザ上で実際に出力されている
id(ClientId)がどうなっているか - 再描画対象となる要素自体がHTMLに存在しているか
同じ h:form 内なら、
render="resultArea"
で指定できることがあります。
別の h:form などにある場合は、
render=":resultForm:resultArea"
のように、ViewRootからの絶対指定にすると分かりやすいです。
また、更新対象そのものに
rendered="false"
を指定してHTMLから消えている場合は、
外側に常に存在するコンポーネントを用意し、そちらを render します。
💡 この記事で分かること
-
f:ajaxのrenderが更新されないときの確認ポイント - JSFの
idとブラウザ上のClientIdの違い - NamingContainerがClientIdに与える影響
- 別の
h:formにあるコンポーネントを再描画する方法 -
rendered="false"の要素をAjax更新するときの考え方
はじめに
JSFのAjaxでは、
<f:ajax render="resultArea" />
のように、更新したいコンポーネントの id を指定できます。
単純な画面ではこれで問題ありません。
ただ、画面が少し複雑になって、
-
h:formが複数ある - テーブルや複合コンポーネントの中にある
- 条件によってコンポーネントを表示・非表示にしている
といった構成になると、
XHTMLでは resultArea というidなのに更新されない
ということがあります。
自分の場合、こういうときはBean側を疑う前に、
まずブラウザへ実際に出力された id を確認するようにしています。
🔍 原因
JSFでは、XHTMLに書いた id が
そのままブラウザ上のHTMLの id になるとは限りません。
例えば、
<h:form id="searchForm">
<h:panelGroup id="resultArea">
...
</h:panelGroup>
</h:form>
と書いた場合、ブラウザ上では次のようなClientIdになります。
<span id="searchForm:resultArea">
...
</span>
h:form などの NamingContainer によって、
親コンポーネントのIDが付加されるためです。
イメージとしては、
searchForm
└─ resultArea
が、
searchForm:resultArea
というClientIdになります。
そのため、Ajaxの実行元と更新対象の位置関係によっては、
render="resultArea"
だけでは対象を見つけられないことがあります。
✅ 解決方法
同じform内なら相対指定
まず、実行元と更新先が同じ h:form 内にあるケースです。
<h:form id="searchForm">
<h:commandButton
id="searchAction"
value="検索"
actionListener="#{samplePage.search}">
<f:ajax
execute="@this"
render="resultArea" />
</h:commandButton>
<h:panelGroup id="resultArea">
#{samplePage.result}
</h:panelGroup>
</h:form>
このような単純な構成なら、
render="resultArea"
で問題ありません。
別のformを更新する場合
例えば、実行元と表示先が別々の h:form にある場合です。
<h:form id="searchForm">
<h:commandButton
id="searchAction"
value="検索"
actionListener="#{samplePage.search}">
<f:ajax
execute="@this"
render=":resultForm:resultArea" />
</h:commandButton>
</h:form>
<h:form id="resultForm">
<h:panelGroup id="resultArea">
#{samplePage.result}
</h:panelGroup>
</h:form>
ここでは、
render=":resultForm:resultArea"
としています。
先頭の : を付けることで、
ViewRoot側からコンポーネントを指定します。
ブラウザ上のClientIdは、
resultForm:resultArea
のようになります。
📖 NamingContainerとは
JSFでは、一部のコンポーネントが
子コンポーネントの名前空間を作ります。
代表的なのが h:form です。
例えば、
<h:form id="formA">
<h:panelGroup id="resultArea" />
</h:form>
<h:form id="formB">
<h:panelGroup id="resultArea" />
</h:form>
のように、
2つのコンポーネントへ同じ resultArea というIDを付けても、
ブラウザ上では、
formA:resultArea
formB:resultArea
となります。
つまり、JSF内部では同じローカルIDを使えても、
最終的なClientIdでは区別されます。
この仕組みを意識せずに render を指定すると、
resultAreaって書いてあるのに見つからない
ということがあります。
🔎 ClientIdはブラウザで確認する
render が効かないときは、
XHTMLを眺め続けるよりブラウザの開発者ツールを見る方が早いです。
例えばXHTMLでは、
<h:panelGroup id="resultArea">
だったとしても、ブラウザでは、
<span id="resultForm:resultArea">
となっている場合があります。
ChromeやEdgeのDevToolsで対象要素を確認し、
id="..."
を見れば、実際のClientIdを確認できます。
自分は render 周りで怪しいとき、
まずここを見るようにしています。
⚠️ rendered="false" の要素を更新するとき
もう1つハマりやすいのが、
更新対象そのものを rendered で消している場合です。
例えば、
<h:panelGroup
id="resultArea"
rendered="#{samplePage.showResult}">
結果
</h:panelGroup>
showResult == false の状態では、
resultArea 自体がHTMLへ出力されません。
その状態から、
<f:ajax render="resultArea" />
で更新しようとしても、
ブラウザ側には差し替える対象となるDOMがありません。
こういう場合は、
外側に常に存在するコンポーネントを用意します。
<h:panelGroup id="resultArea">
<h:panelGroup rendered="#{samplePage.showResult}">
結果
</h:panelGroup>
</h:panelGroup>
Ajaxでは外側を指定します。
<f:ajax render="resultArea" />
これなら外側の resultArea は常にHTMLへ存在するため、
Ajaxで中身を再描画できます。
個人的にはこの形の方が、
条件付き表示をAjaxで切り替える場合は扱いやすいです。
💼 実務での確認順
render が効かないとき、
いきなりBeanやAjax処理全体を追うと時間がかかります。
自分の場合は、だいたい次の順番で確認します。
1. Beanの処理が呼ばれているか
まず、
action / actionListener は実行されているか
を確認します。
ここが動いているなら、
次は表示側を疑います。
2. Beanの値が変わっているか
サーバー側の値が期待通り更新されているか確認します。
ここまで問題なければ、
かなり render 側が怪しくなります。
3. render対象のClientIdを確認
DevToolsで、
id="resultForm:resultArea"
のような実際のIDを確認します。
4. NamingContainerを確認
更新元と更新先が、
同じformか
別のformか
別のNamingContainer配下か
を見ます。
5. 更新対象がDOMに存在するか
rendered="false" などで、
更新対象そのものがHTMLから消えていないか確認します。
この順番で見ると、
かなり原因を絞りやすいです。
📝 まとめ
JSFのAjaxで render が効かない場合は、
処理そのものよりClientIdの指定が原因になっていることがあります。
今回のポイントはこのあたりです。
- XHTMLの
idとブラウザ上のClientIdは同じとは限らない -
h:formなどのNamingContainerによってClientIdが変わる - 同じNamingContainer内なら相対指定が使いやすい
- 別のNamingContainerを指定する場合は絶対指定も確認する
- 実際のClientIdはDevToolsで確認するのが早い
-
rendered="false"でDOM自体がない場合は、外側の常設コンポーネントをrenderする
Ajaxの処理自体は動いているのに画面だけ変わらない場合は、
「Beanがおかしい?」
と追い始める前に、
一度 render 先のClientIdを確認すると早く見つかることがあります。
この記事が、同じ現象で悩んでいる方の参考になれば幸いです。