1. ホーム
  2. jsf

[解決済み] JSF2 FaceletsでJSTL...意味があるのか?

2022-04-22 21:02:33

質問

Faceletsのコードを少し条件付きで出力したいのですが。

そのためには、JSTLタグで問題ないようです。

<c:if test="${lpc.verbose}">
    ...
</c:if>

しかし、「これはベストプラクティスなのか? 私の目標を達成するための他の方法はないのでしょうか?

どのように解決するのですか?

はじめに

JSTL <c:xxx> タグはすべて タグハンドラ の間に実行されます。 ビュー構築時 一方、JSFは <h:xxx> タグはすべて UIコンポーネント の間に実行されます。 ビューレンダリングタイム .

なお、JSF自身の <f:xxx><ui:xxx> を行うもののみです。 ではなく からの拡張 UIComponent はタグハンドラでもあり、例えば <f:validator> , <ui:include> , <ui:define> など。から伸びるものは UIComponent はJSFのUIコンポーネントでもあり、例えば <f:param> , <ui:fragment> , <ui:repeat> など。JSFのUIコンポーネントからは idbinding 属性は、ビューの構築時にも評価されます。したがって、JSTLライフサイクルに関する以下の回答は、ビューの構築時に評価される idbinding 属性を使用します。

ビュー構築時とは、XHTML/JSPファイルを解析してJSFコンポーネントツリーに変換し、それを UIViewRootFacesContext . ビューレンダータイムとは、JSFコンポーネントツリーがHTMLを生成しようとする瞬間のことで、その始まりは UIViewRoot#encodeAll() . だから JSF UI コンポーネントと JSTL タグは、コーディングから予想されるように同期して実行されません。次のように可視化することができます。まず JSTL が上から下に向かって実行され、JSF コンポーネントツリーが生成されます。そして、JSF が再び上から下に向かって実行され、HTML 出力が生成される番です。

<c:forEach><ui:repeat>

例えば、このFaceletsマークアップは、3つの項目を反復するために <c:forEach> :

<c:forEach items="#{bean.items}" var="item">
    <h:outputText id="item_#{item.id}" value="#{item.value}" />
</c:forEach>

...ビューのビルド時に、3つの別々の <h:outputText> コンポーネントは、JSFのコンポーネントツリーで、おおよそこのように表現されます。

<h:outputText id="item_1" value="#{bean.items[0].value}" />
<h:outputText id="item_2" value="#{bean.items[1].value}" />
<h:outputText id="item_3" value="#{bean.items[2].value}" />

...そして、ビューのレンダリング時に個別にHTML出力を生成します。

<span id="item_1">value1</span>
<span id="item_2">value2</span>
<span id="item_3">value3</span>

コンポーネント ID の一意性を手動で確認する必要があることと、ビューのビルド時にそれらも評価されることに注意してください。

このFaceletsマークアップでは <ui:repeat> これはJSFのUIコンポーネントです。

<ui:repeat id="items" value="#{bean.items}" var="item">
    <h:outputText id="item" value="#{item.value}" />
</ui:repeat>

...すでにJSFのコンポーネントツリーでそのまま終了しており、そこでまったく同じ <h:outputText> コンポーネントは、ビューレンダリング時に 再利用 を使用して、現在の反復ラウンドに基づく HTML 出力を生成します。

<span id="items:0:item">value1</span>
<span id="items:1:item">value2</span>
<span id="items:2:item">value3</span>

ただし <ui:repeat> であることから NamingContainer コンポーネントは、すでに反復インデックスに基づくクライアントIDの一意性を確保しています。また、ELを id 属性もビューの構築時に評価されるため、この方法で子コンポーネントの #{item} は、ビューレンダリング時にのみ利用可能です。同じことが h:dataTable と同様の構成要素です。

<c:if> / <c:choose>rendered

別の例として、この Facelets のマークアップでは、条件付きで異なるタグを <c:if> (を使用することもできます。 <c:choose><c:when><c:otherwise> を使用します)。

<c:if test="#{field.type eq 'TEXT'}">
    <h:inputText ... />
</c:if>
<c:if test="#{field.type eq 'PASSWORD'}">
    <h:inputSecret ... />
</c:if>
<c:if test="#{field.type eq 'SELECTONE'}">
    <h:selectOneMenu ... />
</c:if>

...の場合は type = TEXT を追加するだけです。 <h:inputText> コンポーネントを JSF コンポーネントツリーに追加します。

<h:inputText ... />

このFaceletsのマークアップをしながら

<h:inputText ... rendered="#{field.type eq 'TEXT'}" />
<h:inputSecret ... rendered="#{field.type eq 'PASSWORD'}" />
<h:selectOneMenu ... rendered="#{field.type eq 'SELECTONE'}" />

...は、条件に関係なく、JSFのコンポーネントツリーで上記のように正確に終了します。このため、多くのコンポーネントがあり、それらが実際にはquot;static"モデルに基づいている場合、コンポーネントツリーがquot;bloated"になってしまうかもしれません( field は少なくともビュースコープの間は変更されません)。また、EL トラブル 2.2.7より前のバージョンのMojarraで、追加のプロパティを持つサブクラスを扱うとき。

<c:set><ui:param>

両者は互換性がありません。その <c:set> はELスコープ内の変数を設定し、その変数にアクセスできるのは の後に タグの位置は、ビューの構築時に指定され、 ビューのレンダリング時にはビューの任意の場所に指定されます。また <ui:param> を介してインクルードされたFaceletテンプレートにEL変数を渡します。 <ui:include> , <ui:decorate template> または <ui:composition template> . 古いバージョンのJSFでは <ui:param> 変数が Facelet テンプレートの外側でも利用可能であるため、決してこれに頼ってはいけません。

その <c:set> を使用せずに scope 属性はエイリアスのように振る舞います。これは、どのようなスコープにおいても、EL式の結果をキャッシュすることはありません。従って、例えばJSFコンポーネントを反復する内部で使用しても全く問題ありません。従って、例えば以下のように使用することができます。

<ui:repeat value="#{bean.products}" var="product">
    <c:set var="price" value="#{product.price}" />
    <h:outputText value="#{price}" />
</ui:repeat>

ただ、ループの中で合計を計算する場合などには不向きです。そのような場合は、代わりに EL 3.0ストリーム :

<ui:repeat value="#{bean.products}" var="product">
    ...
</ui:repeat>
<p>Total price: #{bean.products.stream().map(product->product.price).sum()}</p>

だけ、設定すると scope 属性に、許容される値のうちの1つを指定します。 request , view , session または application を指定すると、ビューの構築時に直ちに評価され、指定したスコープに格納されます。

<c:set var="dev" value="#{facesContext.application.projectStage eq 'Development'}" scope="application" />

これは一度だけ評価され、次のように利用できます。 #{dev} は、アプリケーション全体を通して

JSTL を使用して JSF コンポーネントのツリー構築を制御する

JSTLを使用すると、以下のようなJSF反復コンポーネント内部で使用した場合にのみ、予期せぬ結果を招く可能性があります。 <h:dataTable> , <ui:repeat> など、JSTLタグの属性がJSFイベントの結果に依存する場合、または preRenderView または、ビューの構築時に利用できない、モデル内の送信されたフォームの値。ですから、JSTLタグは、JSFコンポーネントのツリー構築のフローを制御するためにのみ使用してください。JSF UI コンポーネントは、HTML 出力生成のフローを制御するために使用してください。をバインドしないでください。 var を、JSTLタグの属性に反復して適用しています。JSTL タグの属性において、JSF イベントに依存しないこと。

バッキングビーンにコンポーネントをバインドする必要がある場合は、いつでも binding を使うか、あるいは findComponent() を持つバッキングビーンの中で、Java コードを使用してその子を作成/操作します。 new SomeComponent() そのような場合は、すぐにJSTLの使用を中止して、代わりにJSTLを使用することを検討してください。JSTL は XML ベースなので、JSF コンポーネントを動的に作成するために必要なコードは、非常に読みやすく、保守しやすくなります。

重要なのは、Mojarraの2.1.18より古いバージョンでは、JSTLタグ属性でビュースコープされたビーンを参照する場合、部分的な状態保存のバグがあったということです。ビュースコープされたビーン全体は 新たに は、ビューツリーから取得するのではなく、再作成されます (単に、JSTL が実行される時点では、完全なビューツリーがまだ利用可能でないためです)。もし、JSTLタグ属性によってビュースコープされたビーンに何らかの状態を期待したり保存したりしている場合、期待した値を返さなかったり、ビューツリーが構築された後に復元される実際のビュースコープされたビーンにおいて "lost"されたりすることがあります。Mojarra 2.1.18 以降にアップグレードできない場合は、以下の方法で部分的な状態保存をオフにすることができます。 web.xml のようにします。

<context-param>
    <param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
    <param-value>false</param-value>
</context-param>

こちらもご覧ください。

JSTLタグが役に立つ(つまり、ビューの構築時に本当に適切に使用される)実際の例を見るには、以下の質問/回答を参照してください。


ひとことで言うと

具体的な機能要件についてですが、仮に レンダリング JSFコンポーネントを条件付きで使用するには rendered 属性の代わりに、JSF HTML コンポーネントの 特に もし #{lpc} のようなJSFイテレートコンポーネントの現在イテレートされている項目を表します。 <h:dataTable> または <ui:repeat> .

<h:someComponent rendered="#{lpc.verbose}">
    ...
</h:someComponent>

あるいは、もしあなたが ビルド (作成/追加) JSF コンポーネントを条件付きで使用する場合は、JSTL を使用し続けてください。それは、冗長に new SomeComponent() をjavaで実行します。

<c:if test="#{lpc.verbose}">
    <h:someComponent>
        ...
    </h:someComponent>
</c:if>

こちらもご覧ください。