0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【実務でハマったJSF】Ajaxのrender先が更新されない|NamingContainerとClientIdの確認ポイント

0
Posted at

💥 発生した事象

JSFでAjaxを使い、処理後に画面の一部だけを更新しようとしていました。

<f:ajax
    execute="@this"
    render="resultArea" />

サーバー側の処理自体は実行されているのに、
resultArea の表示だけが期待通り更新されません。

Beanの値も更新されていたため、

「Ajaxは動いているのに、なんで画面だけ変わらない?」

という状態になりました。


🚀 急いでいる人向け

f:ajax の処理は動いているのに render 先が更新されない場合は、
まず次の3つを確認します。

  1. render で指定したコンポーネントが同じ NamingContainer 内にあるか
  2. ブラウザ上で実際に出力されている id(ClientId)がどうなっているか
  3. 再描画対象となる要素自体がHTMLに存在しているか

同じ h:form 内なら、

render="resultArea"

で指定できることがあります。

別の h:form などにある場合は、

render=":resultForm:resultArea"

のように、ViewRootからの絶対指定にすると分かりやすいです。

また、更新対象そのものに

rendered="false"

を指定してHTMLから消えている場合は、
外側に常に存在するコンポーネントを用意し、そちらを render します。


💡 この記事で分かること

  • f:ajaxrender が更新されないときの確認ポイント
  • 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を確認すると早く見つかることがあります。


この記事が、同じ現象で悩んでいる方の参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?