web-dev-qa-db-ja.com

JSF2 FaceletsのJSTL ...理にかなっていますか?

条件付きでFaceletsコードを少し出力したいと思います。

そのため、JSTLタグは正常に機能するようです。

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

ただし、これがベストプラクティスであるかどうかはわかりませんか?私の目標を達成する別の方法はありますか?

158
Jan

前書き

JSTL <c:xxx>タグはすべて taghandlers であり、これらはview build timeの間に実行され、JSF <h:xxx>タグはすべて Iコンポーネント であり、レンダリング時間の表示中に実行されます

JSF独自の<f:xxx>および<ui:xxx>タグからは、 UIComponent からnotを拡張するものだけがタグハンドラーでもあることに注意してください。 <f:validator><ui:include><ui:define>など。UIComponentから拡張されるものもJSF UIコンポーネントです。 <f:param><ui:fragment><ui:repeat>など。JSFUIコンポーネントからは、idおよびbinding属性のみがビューのビルド時に評価されます。したがって、JSTLライフサイクルに関する以下の回答は、JSFコンポーネントのidおよびbinding属性にも適用されます。

ビューのビルド時間は、XHTML/JSPファイルが解析され、JSFコンポーネントツリーに変換され、UIViewRootFacesContextとして保存される瞬間です。ビューのレンダリング時間は、UIViewRoot#encodeAll()で始まるJSFコンポーネントツリーがHTMLを生成しようとする瞬間です。したがって、JSF UIコンポーネントとJSTLタグは、コーディングから予想されるように同期して実行されません。 JSTLは最初に上から下に実行され、JSFコンポーネントツリーを生成し、次にJSFが上から下に再び実行され、HTML出力を生成します。

<c:forEach> vs <ui:repeat>

たとえば、次のFaceletsマークアップは、<c:forEach>を使用して3つのアイテムを反復処理します。

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

...ビューのビルド時に、JSFコンポーネントツリーに3つの別個の<h:outputText>コンポーネントを作成します。おおよそ次のように表されます。

<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マークアップは、JSF UIコンポーネントである<ui:repeat>を使用して3つのアイテムを反復処理しますが、

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

...すでにJSFコンポーネントツリーにそのまま残っているため、ビューのレンダリング中に非常に同じ<h:outputText>コンポーネントがreusedになります現在の反復ラウンドに基づいてHTML出力を生成します。

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

NamingContainerコンポーネントである<ui:repeat>は、繰り返しインデックスに基づいてクライアントIDの一意性を既に確保していることに注意してください。子コンポーネントのid属性でELを使用することもできません。これは、ビューのビルド時にも評価されるため、#{item}はビューのレンダリング時にのみ使用できます。 h:dataTableおよび同様のコンポーネントについても同様です。

<c:if>/<c:choose> vs 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コンポーネントツリーでは、条件に関係なく、上記のとおりになります。したがって、これが多くあり、それらが実際に「静的」モデルに基づいている場合(つまり、fieldは少なくともビュースコープの間は変更されません)、「肥大化した」コンポーネントツリーになることがあります。また、2.2.7より前のMojarraバージョンで追加プロパティを持つサブクラスを扱う場合、EL trouble に遭遇する可能性があります。

<c:set> vs <ui:param>

それらは互換性がありません。 <c:set>は、ELスコープ内の変数を設定します。これは、ビューのビルド時はタグの位置の後からのみアクセスできますが、ビューのレンダリング時はビューのどこからでもアクセスできます。 <ui:param>は、<ui:include><ui:decorate template>、または<ui:composition template>を介して含まれるFaceletテンプレートにEL変数を渡します。古いJSFバージョンには、問題のFaceletテンプレートの外部で<ui:param>変数も使用できるというバグがありました。これに依存しないでください。

scope属性のない<c:set>は、エイリアスのように動作します。 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属性に許容値requestviewsession、またはapplicationのいずれかを設定すると、ビューのビルド時にすぐに評価され、指定されたスコープに格納されます。

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

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

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

JSTLの使用は、<h:dataTable><ui:repeat>などのJSF反復コンポーネント内で使用される場合、またはJSTLタグ属性がpreRenderViewなどのJSFイベントの結果に依存する場合、またはビューのビルド時に使用できないモデル。したがって、JSTLコンポーネントのツリー構築のフローを制御するためだけにJSTLタグを使用してください。 JSF UIコンポーネントを使用して、HTML出力生成のフローを制御します。反復するJSFコンポーネントのvarをJSTLタグ属性にバインドしないでください。 JSTLタグ属性のJSFイベントに依存しないでください。

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

知っておくべき重要なことは、2.1.18よりも古いバージョンのMojarraには、JSTLタグ属性でビュースコープBeanを参照するときに部分的な状態の保存にバグがあったことです。ビュースコープBean全体は、ビューツリーから取得するのではなく、newly再作成されます(JSTLの時点で完全なビューツリーがまだ利用できないためです)実行)。 JSTLタグ属性によってビュースコープBeanに状態を予期または格納している場合、期待値を返さないか、ビューの後に復元される実際のビュースコープBeanで「失われます」ツリーが構築されます。 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コンポーネントをrenderしたい場合は、代わりにJSF HTMLコンポーネントのrendered属性を使用します特に#{lpc}が、<h:dataTable><ui:repeat>などのJSF反復コンポーネントの現在反復されている項目を表す場合。

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

または、条件付きでJSFコンポーネントをbuild(作成/追加)する場合は、JSTLを使用し続けます。 Javaでnew SomeComponent()を冗長に実行するよりもはるかに優れています。

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

こちらもご覧ください:

308
BalusC

つかいます

<h:panelGroup rendered="#{lpc.verbose}">
  ...
</h:panelGroup>
13
Bozho

個別の回答で申し訳ありませんが、上記の回答をコメントできませんでした。

スイッチのような出力の場合、 primefaces-extensions からスイッチを使用できます。

4