ヘッダーをスキップ
Oracle® Fusion Middleware Oracle Application Development Framework Webユーザー・インタフェース開発者ガイド
11gリリース1(11.1.1.9.0)
B52029-12
  目次へ移動
目次

前
 
次
 

4 ADF FacesでのJSFライフサイクルの使用

この章では、JSFページ・リクエストのライフサイクルとADF Facesからライフサイクルへの追加、およびアプリケーションでのライフサイクル・プロパティの使用方法について説明します。

この章では、次の項目について説明します。

4.1 JSFライフサイクルおよびADF Facesの概要

ADF Facesリッチ・クライアント・フレームワーク(RCF)はJSFフレームワークの拡張であるため、ADF Facesリッチ・クライアント・フレームワークを使用して構築したアプリケーションでは標準JSFページ・リクエスト・ライフサイクルが使用されます。ただし、ADF Facesフレームワークではライフサイクルが拡張され、クライアント側の値のライフサイクル、1つのページに複数のフォームを使用する弊害(ユーザーの編集内容の消失など)なしに独立して送信可能な領域をページに作成できるサブフォーム・コンポーネント、追加スコープなどの追加機能を提供します。

RCFで提供されるライフサイクルの拡張を十分に理解するには、標準JSFライフサイクルを理解することが重要です。この項の説明は概要にすぎません。詳細は、http://www.oracle.com/technetwork/java/index.html">>http://www.oracle.com/technetwork/java/index.htmlでJSFの仕様を参照してください。

JSFページが送信され、新しいページがリクエストされると、JSFページ・リクエスト・ライフサイクルが起動されます。このライフサイクルでは、ページの値の送信、現在のページのコンポーネントの検証、コンポーネントのナビゲーションと結果ページへの表示および状態の保存とリストアが処理されます。JSFライフサイクル・フェーズでは、UIコンポーネント・ツリーを使用してfacesコンポーネントの表示を管理します。このツリーはJSFページのランタイム表現で、ページ内の各UIコンポーネント・タグがツリーのUIコンポーネント・インスタンスに対応します。FacesServletオブジェクトで、JSFアプリケーションのページ・リクエスト・ライフサイクルが管理されます。FacesServletオブジェクトで、リクエスト処理に必要な情報を含み、ライフサイクルを実行するオブジェクトを起動するFacesContextと呼ばれるオブジェクトが作成されます。

図4-1に、ページ・リクエストのJSFライフサイクルを示します。ここに示すとおり、各フェーズの前後でイベントが処理されます。

図4-1 ADF Facesアプリケーションでのページ・リクエストのライフサイクル

ADFおよびJSFのフェーズの連携

JSFアプリケーションでは、ページ・リクエスト・ライフサイクルは次のようになります。

  • ビューのリストア:コンポーネント・ツリーが構築されます。これが初めてのレンダリングでない場合(ページがサーバーに送り返された場合)、適切な状態でツリーがリストアされます。これが初めてのレンダリングの場合、コンポーネント・ツリーが作成され、レスポンスのレンダリング・フェーズに移ります。

  • リクエスト値の適用: ツリーの各コンポーネントでリクエスト・パラメータから新規の値が(decodeメソッドを使用して)抽出され、ローカルに格納されます。関連付けられているほとんどのイベントは、後の処理のためにキューされます。コンポーネントのimmediate属性がtrueに設定されている場合、検証、変換およびコンポーネントに関連付けられているイベントがこのフェーズで処理されます。詳細は、4.2項「immediate属性の使用」を参照してください。

  • 検証の処理: コンポーネントのローカル値が、入力タイプから基礎のデータ型に変換されます。コンバータが失敗した場合、このフェーズが続行されて完了します(残りのすべてのコンバータ、バリデータおよび必要なチェックが実行されます)が、完了時にライフサイクルがレスポンスのレンダリング・フェーズに移ります。

    失敗がない場合、コンポーネントのrequired属性が確認されます。値がtrueで、関連付けられているフィールドに値が含まれている場合、関連付けられているバリデータが実行されます。値がtrueで、フィールド値がない場合、このフェーズは完了します(残りのすべてのバリデータが実行されます)が、ライフサイクルがレスポンスのレンダリング・フェーズに移ります。値がfalseの場合、値が入力されていないかぎり(この場合は検証が実行されない)、フェーズが完了します。変換と検証の詳細は、第6章「入力の検証および変換」を参照してください。

    このフェーズの最後に、変換後のローカル値が設定され、検証および変換のエラー・メッセージとイベントがFacesContextにキューイングされ、値変更イベントが配信されます。


    ヒント:

    要約すると、編集可能な入力コンポーネントの検証の処理フェーズでの手順は次のとおりです。
    1. コンバータが失敗した場合、必要なチェックとバリデータが実行されません。

    2. コンバータは成功したが、必要なチェックが失敗した場合、バリデータが実行されません。

    3. コンバータと必要なチェックが成功した場合、すべてのバリデータが実行されます。1つのバリデータが失敗した場合でも、残りのバリデータは実行されます。これは、ユーザーがエラーを修正する際に使用できる、入力されたデータの問題点を可能なかぎりフィードバックするためです。

      たとえば、2010年の日付のみを受け入れるdateTimeRangeバリデータがあり、ユーザーが日曜日を選択できないdateRestrictionValidatorバリデータがあるとします。ユーザーがJuly 5, 2009(日曜日)と入力した場合、両方のバリデータが失敗することをフィードバックし、ユーザーが有効な日付を入力する可能性を最大限にします。


  • モデル値の更新: 検証されたコンポーネントのローカル値がモデルに移動され、ローカル・コピーが破棄されます。

  • アプリケーションの起動: アプリケーション・レベルのロジック(イベント・ハンドラなど)が実行されます。

  • レスポンスのレンダリング: ツリー内のコンポーネントがレンダリングされます。後続のリクエストおよびビューのリストア・フェーズ用に状態情報が保存されます。

ライフサイクルの説明の一環として、ユーザーが日付を入力し、コマンド・ボタンをクリックして入力値を送信する単純な入力テキスト・コンポーネントを持つページを考えてみます。valueChangeListenerメソッドもコンポーネントに登録されています。例4-1に、この例のコードを示します。

例4-1 JSFライフサイクルを説明するためのサンプル・コード

<af:form>
  <af:inputText value="#{mybean.date}"
      valueChangeListener="#{mybean.valueChangeListener}">
    <af:convertDateTime dateStyle="long"/>
  </af:inputText>
  <af:commandButton text="Save"  actionListener="#{mybean.actionListener}"/>
</af:form>

ユーザーが文字列「June 25, 2005」を入力し、送信ボタンをクリックするとします。図4-2に、値がライフサイクルでどのように受け渡され、各イベントがどの時点で処理されるかを示します。

図4-2 JSFライフサイクルでの値とイベントの例

ライフサイクルでのページの例

4.2 immediate属性の使用

immediate属性を使用して、ライフサイクルのリクエスト値の適用フェーズまでコンポーネントの処理を進めることができます。actionSourceコンポーネント(commandButtonなど)がimmediateに設定されている場合、イベントはアプリケーションの起動フェーズではなくリクエスト値の適用フェーズで配信されます。actionListenerハンドラでレスポンスのレンダリング・フェーズがコールされ、検証およびモデルの更新フェーズが省略されます。

たとえば、「取消」ボタンをimmediateに構成し、アクションが前のページに戻るために使用される文字列を返すようにする場合があります(ナビゲーションの詳細は、第18章「ナビゲーション・コンポーネントの使用」を参照してください)。「取消」ボタンはimmediateに設定されているため、ユーザーが「取消」ボタンをクリックすると、図4-3に示すように、すべての検証がスキップされ、入力されたデータはモデルに更新されず、ユーザーは期待どおりにナビゲートします。

図4-3 immediateに設定されたコマンド・ボタンのライフサイクル

検証とモデルの更新を省略するライフサイクル

注意:

ナビゲーションを提供せず、immediateに設定されているコマンド・ボタンも、レスポンスのレンダリング・フェーズに直接移動します。検証、モデルの更新およびアプリケーションの起動のフェーズはスキップされるため、新しい値はサーバーにプッシュされません。

コマンド・コンポーネントと同様に、表示イベントを呼び出すコンポーネント(showDetailコンポーネントなど)、およびeditableValueHolderコンポーネント(inputTextコンポーネントなど、変更可能な値を保持するコンポーネント)では、immediateに設定されている場合、イベントはリクエスト値の適用フェーズで配信されます。一方、editableValueHolderコンポーネントでは、フェーズをスキップするかわりに、immediateコンポーネントのvalueChangeEventsイベントの変換、検証および配信が、検証の処理フェーズではなく、ライフサイクル初期のリクエスト値の適用フェーズで実行されます。スキップされるライフサイクル・フェーズはありません。

図4-4に、immediate属性がtrueに設定されている入力コンポーネントのライフサイクルを示します。入力コンポーネントは文字列として入力された日付を取得し、コマンド・ボタンがクリックされたときにDateオブジェクトとしてその日付を格納します。

図4-4 入力コンポーネントのimmediate属性

入力コンポーネントのimmediate属性

入力コンポーネントのimmediate属性をtrueを設定すると、1つ以上の入力コンポーネントをその他のコンポーネントより前に検証する必要がある場合に役立ちます。次に、そのコンポーネントに無効なデータがあることが検出された場合、同じページ内の他の入力コンポーネントの検証がスキップされ、そのページのために表示されるエラー・メッセージの数が減少します。

たとえば、名前と住所の情報を収集するために使用するフォームがあり、そのNameフィールドをrequiredに設定したとします。住所のフィールドは、「国」ドロップダウン・フィールドで選択された国に基づいて変更する必要があります。ユーザーが国を選択するときに、名前のフィールドが空白でも検証エラーが発生しないように、ドロップダウン・フィールド(selectOneChoiceコンポーネントを使用して表示される)をimmediateに設定します。例4-2に、住所フィールドのコードを示します(国の切替え機能は示していません)。

例4-2 Immediateを使用する入力コンポーネント

<af:form id="f1">
  <af:panelFormLayout id="pfl1">
    <af:panelGroupLayout id="pgl2" layout="vertical">
      <af:inputText label="Name" id="it1" value="#{bean.name}"
                    required="true"/>
      <af:inputText label="Street" id="it2" value="#{bean.street}"/>
      <af:panelGroupLayout id="pgl1" layout="horizontal">
        <af:inputText label="City" id="it3" value="#{bean.city}"/>
        <af:inputText label="State" id="it4" value="#{bean.state}"/>
        <af:inputText label="Zip" id="it5" value="#{bean.zip}"/>
       </af:panelGroupLayout>
       <af:selectOneChoice label="Country" id="soc1" immediate="true">
                           valueChangeListener="bean.countryChanged">
         <af:selectItem label="US" value="US" id="si1"/>
         <af:selectItem label="Canada" value="Canada" id="si2"/>
       </af:selectOneChoice>
     </af:panelGroupLayout>
    </af:panelFormLayout>
</af:form>

この例では、selectOneChoiceの値の変更イベントはリクエスト値の適用フェーズの後で起動されるため、countryChangedリスナー・メソッドは名前のinputTextコンポーネントが検証される前に実行されます。しかし、編集可能なコンポーネントに対し、フェーズはスキップされないことに注意してください - この場合も検証はこのリクエスト中に実行されます。他のコンポーネントの検証を防ぐには、renderResponseメソッドをリスナー・メソッドから直接呼び出す必要があります。この呼出しは、検証を含めたライフサイクルの残りの部分をスキップし、レスポンスのレンダリング・フェーズに直接ジャンプします。


ヒント:

RenderResponse()を呼び出すときは注意してください。部分的にライフサイクルを通過したコンポーネントは、トラブルの原因となる不完全な状態になることがあります。これらのコンポーネントの値は未検証でデコードされてモデルにプッシュされるため、RenderResponse()を呼び出すときに送信済の値をクリアする必要があります。

例4-3 RenderResponseの呼出し

public void countryChanged(ValueChangeEvent event)
  FacesContext context = Facescontext.getcurrentInstance();
. . .
  some code to change the locale of the form
. . .
context.renderresponse();

ヒント:

ページをそのまま残すことが必要で、immediateコンポーネント以外の値は送信しない場合にのみ、immediate属性を使用します。取消の例では、そのページで値が送信されることはありません。住所の例では、他の値を何も送信せずにページを再レンダリングします。

immediate属性によりコンポーネントはデコードされます(したがって、送信される値を持ちます)が、それ以上は処理されません。immediateアクションのコンポーネントの場合、他のコンポーネントが検証やモデルの更新フェーズに到達することはなく、送信された値はローカルの値に変換され、そのローカルの値がモデルにプッシュされ、クリアされます。このため、値は使用可能にはなりません。

他のフィールドの前に処理すべき多数のフィールドがある場合は、immediate属性は使用しないでください。そのような場合は、ライフサイクルが実行されるときにサブフォーム内のフィールドのみが処理されるように、ページを別々のサブフォームに分割するのが最善です。



パフォーマンスのヒント:

immediate属性をtrueに設定すると、パフォーマンスが向上する場合があります。
  • ナビゲーション・トレインを作成し、navigationPaneコンポーネント内にcommandNavigationItemコンポーネントがある場合、immediate属性をtrueに設定して、次のページへの移動時に、現在のページ(トレイン・ストップ)からのデータの処理を回避します。詳細は、18.8.1項「トレイン・モデルの作成方法」を参照してください。

  • 他の値の前に入力コンポーネント値を送信する必要がある場合、immediate属性をtrueに設定します。たとえば、ページを再送信するまでページ内の他のフィールドを検証したくない場合は、ページの表示に影響を与える選択コンポーネントをimmediateに設定する必要があります。ライフサイクルの早い段階でエラーが検出され、その他の処理が行われずに済みます。


4.2.1 immediate属性の使用方法

プロパティ・インスペクタを使用してimmediate属性を宣言的に設定できます。

immediate属性を使用する手順:

  1. JSFページで、immediateにするコンポーネントを選択します。

  2. プロパティ・インスペクタで、「動作」セクションを開き、immediate属性をtrueに設定します。

4.2.2 同じフォームにあるアクションおよび入力コンポーネントのimmediate属性の使用に関する必知事項

同じページのeditableValueHolderおよびactionSourceコンポーネントにimmediate属性を使用する場合、次の点に注意する必要があります。

  • editableValueHolderコンポーネントがimmediateとマークされている場合、モデル値の更新フェーズの前に実行されます。immediateなactionSourceコンポーネントにeditableValueHolderコンポーネントからのデータが必要な場合、editableValueHolderコンポーネントに入力されたデータはモデルの更新フェーズまでモデルで使用できないため、これが問題となる場合もあります。immediateなactionSourceコンポーネントがあり、このコンポーネントでデータが必要な場合、editableValueHolderフィールドのimmediateも設定します。こうすることで、editableValueHolderコンポーネントのgetValueメソッドをコールでき、ローカル値が返されます。モデルにはまだプッシュされていませんが、コンポーネントで使用できます。

  • immediateなeditableValueHolderコンポーネントが検証に失敗した場合でも、immediateなactionSourceコンポーネントは実行されます。

4.3 最適化されたライフサイクルの使用

ADF Facesには、ページの特定のコンポーネントにのみJSFページ・リクエスト・ライフサイクル(変換と検証を含む)を実行する場合に使用できる最適化されたライフサイクルが用意されています。たとえば、必須属性がtrueに設定されたinputTextコンポーネントがページにあるとします。同じページにラジオ・ボタンがあり、選択すると、図4-5に示すようにoutputTextコンポーネントのテキストをページに表示または非表示します。

図4-5 必須フィールドと自動送信のブール

自動送信でinputTextコンポーネントの検証が省略されます

また、必要なテキストをフィールドに入力する前にユーザーがラジオ・ボタンを選択できるようにするとします。ラジオ・ボタン・コンポーネントで送信アクションを自動的にトリガーするよう設定し、immediate属性をtrueに設定して、inputTextコンポーネントの前に処理されるようにできますが、valueChangeEventリスナーを追加し、ここでレスポンスのレンダリング・フェーズをコールして、入力テキスト・コンポーネントで検証が実行されないようにする必要もあります。

このコードをリスナーに記述するかわりに、ADF Facesでは、ページに境界を設定してライフサイクルが境界内のコンポーネントでのみ実行されるようにすることができます。境界を判断するために、処理するコンポーネントのルートをフレームワークに通知する必要があります。このコンポーネントは、次の2つの方法で決めることができます。

  • コンポーネント: リージョンは、フレームワークで境界が認識されるコンポーネントの1つの例です。リージョン内でトリガーされたイベントに関係なく、リージョン外のコンポーネントでライフサイクルは実行されません。

  • イベント: 特定のイベントはルートであるコンポーネントを示します。たとえば、showDetailコンポーネントを展開または縮小する(8.10項「コンテンツの動的な表示および非表示」を参照)と送信される表示イベントは、showDetailコンポーネントがルートであることを示すため、ライフサイクルはshowDetailコンポーネントと任意の子コンポーネントでのみ実行されます。ライフサイクルは、その表示イベントをリスニングするよう構成されているコンポーネントでも実行できます。イベントを処理するためにルート・コンポーネントでイベントをリスニングするコンポーネントの構成は、クロスコンポーネント・リフレッシュと呼ばれます。

クロスコンポーネント・リフレッシュでは、あるコンポーネントがターゲットと呼ばれる別のコンポーネントのトリガーとして機能するように依存関係を設定できます。トリガー・コンポーネントでイベントが発生すると、ターゲット・コンポーネントと、トリガーおよびターゲットの両方の子コンポーネントでライフサイクルが実行され、これらのコンポーネントのみがレンダリングされます。これは、部分ページ・レンダリング(PPR)とみなされます。

ラジオ・ボタンの例では、例4-4に示すように、ラジオ・ボタンをトリガーとし、出力テキストを含むpanelGroupLayoutコンポーネントをターゲットとして設定します。

例4-4 クロスコンポーネント・レンダリングの例

<af:form>
  <af:inputText label="Required Field" required="true"/>
  <af:selectBooleanRadio id="show" autoSubmit="true" text="Show"                                      
                        value="#{validate.show}"/>
  <af:selectBooleanRadio id="hide" autoSubmit="true" text="Hide" 
                         value="#{validate.hide}"/>
  <af:panelGroupLayout partialTriggers="show hide" id="panel">
    <af:outputText value="You can see me!" rendered="#{validate.show}"/>
  </af:panelGroupLayout>
</af:form>

ラジオ・ボタンのautoSubmit属性がtrueに設定されているため、ボタンが選択されると、SelectionEventが起動され、ラジオ・ボタンがルートとみなされます。panelGroupLayoutコンポーネントが両方のラジオ・コンポーネントのターゲットとして設定されているため、イベントが起動されると、selectOneRadio(ルート)、panelGroupLayoutコンポーネント(ルートのターゲット)とその子コンポーネント(outputTextコンポーネント)のみライフサイクルの処理が行われます。outputTextコンポーネントは表示ラジオ・ボタンの選択時にのみレンダリングされるよう構成されているため、ユーザーは、ラジオ・ボタンの上の必須入力フィールドにテキストを入力しなくても、ラジオ・ボタンを選択して出力テキストを表示できます。

ADF FacesフレームワークでのPPRの使用方法とアプリケーションでのPPRの使用方法の詳細は、第7章「部分ページ・コンテンツの再レンダリング」を参照してください。

4.3.1 immediate属性と最適化されたライフサイクルの使用に関する必知事項

PPRで特定のコンポーネントの検証を回避できない場合があります。たとえば、例4-5に示すように、panelGroupLayoutコンポーネント内にoutputTextコンポーネントのかわりにinputTextコンポーネントを使用し、このrequired属性をtrueに設定するとします。

例4-5 クロスコンポーネントPPRで検証されるpanelGroupコンポーネント内のinputTextコンポーネント

<af:form>
  <af:selectBooleanRadio id="show2" autoSubmit="true" text="Show" 
                         value="#{validate.show2}"/>
  <af:selectBooleanRadio id="hide2" autoSubmit="true" text="Hide" 
                         value="#{validate.hide2}"/>
  <af:panelGroupLayout partialTriggers="show2 hide2">
    <af:inputText label="Required Field" required="true" 
                                         rendered="#{validate.show2}"/>
  </af:panelGroupLayout>
</af:form>

この例では、ライフサイクルはルート(selectOneRadioコンポーネント)、ターゲット(panelGroupLayoutコンポーネント)とターゲットの子(inputTextコンポーネント)で実行されるため、inputTextコンポーネントは検証されます。inputTextコンポーネントが必須としてマークされているため、値がないと、検証が失敗し、エラーがスローされます。エラーのため、ライフサイクルはレスポンスのレンダリング・フェーズに移り、モデルは更新されません。このため、ラジオ・ボタンの値が更新されず、panelGroupLayoutコンポーネントは表示または非表示にできません。

このような場合、ラジオ・ボタンのimmediate属性を使用して検証を省略できます。これにより、inputTextコンポーネントの検証の処理フェーズの前にボタンのvalueChangeEventが実行されます。レスポンスのレンダリング・フェーズをコール(その結果、入力コンポーネントの検証を省略)するvalueChangeListenerハンドラ・メソッドを追加し、ラジオ・ボタンと入力コンポーネントに値を設定する必要があります。例4-6に、これを行うJSFコードを示します。

例4-6 immediate属性とvalueChangeListenerの使用

<af:form>
  <af:selectOneRadio immediate="true" 
                         valueChangeListener="#{validate.toggle}"
                         id="show2" autoSubmit="true" text="Show"                    
                         value="#{validate.show2}"/>
  <af:selectOneRadio id="hide2" autoSubmit="true" text="Hide" 
                         value="#{validate.hide2}"/>
  <af:panelGroupLayout partialTriggers="show2 hide2">
    <af:inputText label="Required Field" required="true"
                  rendered="#{validate.show2}"/>
  </af:panelGroupLayout>
</af:form>

例4-7に、valueChangeListenerのコードを示します。

例4-7 valueChangeListenerで値を設定し、レスポンスのレンダリングをコール

public void toggle(ValueChangeEvent vce)
  {
    setShow2(Boolean.TRUE.equals(vce.getNewValue()));
    FacesContext.getCurrentInstance().renderResponse();
  }

4.3.2 LOVコンポーネントと最適化されたライフサイクルの使用に関する必知事項

inputListOfValuesおよびinputComboBoxListOfValuesコンポーネントの場合、4.3.1項「immediate属性と最適化されたライフサイクルの使用に関する必知事項」で説明されている手順は機能しません。次に例を示します。

図4-6に示すように、ユーザーが従業員名を選択するinputListOfValuesコンポーネントと、必須属性がtrueに設定され、従業員の選択後にその従業員のID番号で更新されるinputTextコンポーネントがあるとします。

図4-6 LOVコンポーネントによる入力コンポーネントの更新

LOVコンポーネントによる入力コンポーネントの更新

これを実現するには、例4-8に示すように、Enameフィールドを部分トリガーとして持つようにEmpnoフィールドを設定できます。

例4-8

<af:inputListOfValues label="Ename" id="lov0" 
                      value="#{validateLOV.ename}" autoSubmit="true"
                      immediate="true"
                      popupTitle="Search and Select: Ename" 
                      searchDesc="Choose a name"
                      model="#{validateLOV.listOfValuesModel}"  
                      valueChangeListener="#{validateLOV.immediateValueChange}"
                      validator="#{validateLOV.validate}"/>
<af:inputText label="Empno" value="#{validateLOV.empno}" required="true"
              id="lovDependent01" partialTriggers="lov0"/>

4.3.1項「immediate属性と最適化されたライフサイクルの使用に関する必知事項」のラジオ・ボタンおよび入力コンポーネントの例と同様に、ユーザーが検索アイコンをクリックすると、ルート(inputListOfValuesコンポーネント)とターゲット(inputTextコンポーネント)の両方でライフサイクルが実行されるため、inputTextコンポーネントが検証されます。図4-7に示すように、inputTextコンポーネントが必須としてマークされているため、値がないと、検証が失敗し、エラーがスローされます。

図4-7 値が必須であるため検証エラーがスローされる

Empnoが必須であることを示す検証エラー

ただし、LOVコンポーネントのimmediate属性をtrueに設定し、LOVでValueChangeListenerを使用するという4.3.1項「immediate属性と最適化されたライフサイクルの使用に関する必知事項」で推奨されているソリューションは、検証エラーを修正しません。LOVコンポーネントの場合、ValueChangeEventはLOVコンポーネントの値が変更されたときにのみキューに入れられます。そのため、immediate属性をtrueに設定しても、ユーザーが検索アイコンをクリックしたときに効果はありません。その時点でADF LaunchPopupEventimmediate属性の値にかかわらず常にアプリケーションの呼出しフェーズのキューに入れられるからです。つまり、最適化されたライフサイクルは、ルート・コンポーネントとターゲット・コンポーネントの両方で通常どおり実行されるため、入力コンポーネントは検証エラーをスローします。

ユーザーが「値リスト」ポップアップから新しい値を選択すると、LOVコンポーネントは2つのイベントをキューに入れます。1つは、コンポーネントの値の変化を示すValueChangeEventです。2つ目は、アプリケーションの呼出しフェーズについてキューに入れられるReturnPopupEventです。これはアプリケーション・メソッドに選択を処理する機会を与えます。LOVが期待どおりに動作するためには、この両方のイベントが発生する必要があります。

前述のように、LOVコンポーネントはユーザーが新しい値を選択したときのにみValueChangeEventをキューに入れます。LOVコンポーネントでimmediate属性をtrueに設定した場合、このイベントはリクエスト値の適用フェーズのキューに入れられ、新しい値が検証されます。また、図4-8に示すように、LOVコンポーネントのValueChangeListenerメソッドを作成し、その実装でレスポンスのレンダリング・フェーズに移動して入力コンポーネントの検証を回避した場合、選択した値はモデルにプッシュされず、ReturnPopupListenerはアプリケーションの呼出しフェーズ中に決してコールされず、ターゲット入力コンポーネントは新しい値で更新されません。

図4-8 モデルは更新されない

Empnoの値は変更されない

ValueChangeEventReturnPopupEventの両方を同じリクエストの一部としてキューに入れる必要性と、新規に選択された値でターゲット・フィールドをリフレッシュする必要性の問題を解決するには、LOVコンポーネントを入力コンポーネントの部分トリガーとして宣言的に設定し、ValueChangeListenerのメソッドを作成するかわりに、ReturnPopupEventのリスナーを作成する必要があります。このリスナーは、入力コンポーネントをLOVの部分ターゲットとしてプログラムで設定する必要があります。ReturnPopupListenerメソッドが実行されるまで入力コンポーネントはLOVのターゲットでなくなり、ライフサイクルが実行されないため検証は失敗しないため、LOVのimmediate属性をtrueに設定する必要はありません。また、リスナー・メソッドはValueChangeEventではなくReturnPopupEventに使用されるため、両方のイベントをキューに入れ、それに応じてモデルを更新できます。

例4-9に、LOVと入力コンポーネントに必要なページ・コードを示します。

例4-9

 <af:inputListOfValues label="Ename" id="lov1" 
                       value="#{validateLOV.ename}" autoSubmit="true"
                       returnPopupListener="#{validate.processReturnPopup}" 
                       Title="Search and Select: Ename" searchDesc="Choose a name"
                       model="#{validateLOV.listOfValuesModel}"
                       validator="#{validateLOV.validate}"/>
  <af:inputText label="Empno" value="#{validateLOV.empno}" required="true"
                id="lovDependent1" binding="#{validate.lovDependent1}"/>

入力コンポーネントは、そのbinding属性を使用してバッキングBeanにインスタンスを格納し、リスナー・メソッドがインスタンスにアクセスできるようにします。例4-10に示すように、リスナー・メソッドは、入力コンポーネントにアクセスし、それをLOVの部分ターゲットとして設定します。

例4-10

AdfFacesContext.getCurrentInstance().addPartialTarget(_lovDependent1)

部分ページ・レンダリングのプログラムによる設定の詳細は、7.3項「部分ページ・レンダリングのプログラムによる有効化」を参照してください。

4.4 クライアント側ライフサイクルの使用

ADF Facesフレームワークは、クライアント側変換および検証を提供します。サーバーにアクセスせずにページで実行できる独自のJavaScriptベースのコンバータおよびバリデータを作成できます。

クライアント側検証を使用できるため、特定のクライアント・イベントがキューイングされると、適切なフォームまたはサブフォームのクライアント検証がトリガーされます(サブフォームの詳細は、4.5項「サブフォームを使用したページでの領域の作成」を参照)。このクライアント検証が失敗し、既知のエラーがあることがわかった場合は、通常サーバーに伝播するイベント(たとえば、フォームが送信されたときのコマンド・ボタンのactionEvent)はサーバーに移動しません。イベントを配信しないことは、何も送信されず、クライアント・リスナーがコールされないことも意味します。これは、サーバーで検証が失敗した場合にライフサイクルがレスポンスのレンダリング・フェーズにジャンプするサーバー側検証に似ています。アクション・イベントはキューに入れられますが、決して配信されず、actionListenerハンドラ・メソッドは決してコールされません。

たとえば、ADF Facesで入力コンポーネントにrequired属性を用意し、この検証がクライアントで実行されるとします。この属性をtrueに設定すると、コンポーネントの値がnullの場合、フレームワークでページにエラーが表示され、サーバーへの送信は必要ありません。例4-11に、inputTextコンポーネントのrequired属性をtrueに設定し、actionListener属性がマネージドBeanのメソッドにバインドされているコマンド・ボタンを含むコードを示します。

例4-11 簡単なクライアント側検証の例

<af:form>
  <af:inputText id="input1" required="true" value="a"/>
  <af:commandButton text="Search" actionListener="#{demoForm.search}"/>
</af:form>

このページを実行した場合、inputTextコンポーネントの値のフィールドをクリアし、フィールドからタブ移動すると、フィールドが赤い縁取りで再表示されます。フィールドをクリックすると、図4-9に示すように、値が必要なことを示すエラー・メッセージが表示されます。サーバーへの送信はなく、エラーの検出とメッセージの生成はすべてクライアントで行われます。

図4-9 クライアント側検証でサーバーへの送信なしにエラーを表示

必須属性のクライアント側検証

この例で、値のフィールドをクリアし、「検索」ボタンをクリックした場合、必須フィールドが空でありエラーが発生するため、ページは送信されません。アクション・イベントは配信されず、アクション・リスナーにバインドされたメソッドは実行されません。サーバーで検証が失敗したことをクライアントが通知できる場合はページを送信する理由がないため、このプロセスは適切です。

クライアント側の検証と変換の詳細は、第6章「入力の検証および変換」を参照してください。

4.5 サブフォームを使用したページでの領域の作成

JSF参照実装では、ページの領域を別個に送信する場合、複数のフォームを使用する必要があります。ただし、複数のフォームにはページの状態の複数のコピーが必要なため、送信されないフォームでのユーザーの編集内容が失われる可能性があります。

ADF Facesには、別個に送信可能なページの領域を示すサブフォーム・コンポーネントのサポートが追加されています。サブフォームのコンテンツは、サブフォーム内部のコンポーネントがページの送信に関与する場合にのみ検証(または処理)されるため、まったく別のフォーム要素を使用するという妥協をすることなく、検証されてモデルへプッシュされるコンポーネントのセットを比較的きめ細かく制御できます。サブフォームを使用するページの送信時に、ページの状態が一度だけ書き込まれ、すべてのユーザー編集内容が保持されます。


ベスト・プラクティス:

常にページごとに1つのみのformタグを使用します。複数のformタグを使用する必要がある場合は、subformタグを使用します。

サブフォームでは、ページがそのサブフォーム外のコンポーネントによって送信された場合でも、リクエスト値の適用フェーズでその子コンポーネントを常に実行できます。ただし、検証の処理フェーズとモデル値の更新フェーズはスキップされます(これは、送信されない場合はリクエスト値の適用フェーズを実行できない通常のフォーム・コンポーネントとは異なります)。サブフォーム外のコンポーネントが送信アクションの原因となった場合にサブフォーム内のコンポーネントを検証の処理フェーズとモデル値の更新フェーズで処理できるようにするには、default属性を使用します。サブフォームのdefault属性をtrueに設定すると、そのサブフォームはほとんどの点で他のサブフォームと同様に動作しますが、ページのどのサブフォームにもその子コンポーネントからの適切なイベントがない場合、defaulttrueに設定されたサブフォームは、その子コンポーネントの1つが送信の原因となったかのように動作します。サブフォームの詳細は、9.2項「formの定義」を参照してください。

4.6 オブジェクト・スコープ・ライフサイクル

実行時、ページがデータにアクセスできるオブジェクト・スコープに必要なデータを格納することで、データをページに渡します。スコープによってオブジェクトの存続期間が決まります。オブジェクトをスコープに置くと、EL式を使用してスコープからアクセスできます。たとえば、fooという名前のマネージドBeanを作成し、Beanをリクエスト・スコープに存続させるよう定義するとします。このBeanにアクセスするには、#{requestScope.foo}という式を使用します。

標準JSFアプリケーションには、次の3種類のスコープがあります。

  • applicationScope: オブジェクトは、アプリケーションの間使用できます。

  • sessionScope: オブジェクトは、セッションの間使用できます。

  • requestScope: オブジェクトは、HTTPリクエストが送信されてから、レスポンスがクライアントに返るまでの間使用できます。

標準JSFスコープに加え、ADF Facesには次のスコープが用意されています。

  • pageFlowScope: オブジェクトは、ユーザーがあるページから別のページへのナビゲートを続けるかぎり使用できます。ユーザーが新たなブラウザ・ウィンドウを開いてナビゲートを始めた場合、そのウィンドウで別のpageFlowScopeスコープを持ちます。

  • backingBeanScope: ページ・フラグメントと宣言コンポーネントのみに対するマネージドBeanに使用します。オブジェクトは、HTTPリクエストが送信されてから、レスポンスがクライアントに返るまでの間使用できます。このスコープは、複数のページ・フラグメントと宣言コンポーネントがページにある場合があり、値の競合を避けるために別々のスコープ・インスタンスに値を保持するために必要です。ページ・フラグメントまたは宣言コンポーネントに対して作成されるマネージドBeanには、backingBeanScopeスコープを使用します。

  • viewScope: オブジェクトは、現在のビューのIDが変更されるまで使用できます。viewScopeスコープを使用して特定のページの値を保持します。


注意:

これらは標準JSFスコープではないため、EL式には、Beanを参照するためのスコープを明示的に含める必要があります。たとえば、pageFlowScopeスコープからMyBeanマネージドBeanを参照するには、#{pageFlowScope.MyBean}という式になります。

オブジェクト・スコープは、プログラミング言語のグローバル変数およびローカル変数のスコープに相当します。スコープが広いほど、オブジェクトの可用性が高くなります。存続期間の間、これらのオブジェクトで特定のインタフェースの公開、情報の保持および他のオブジェクトへの変数とパラメータの受渡しを行うことができます。たとえば、sessionScopeスコープに定義されているマネージドBeanは、複数のページ・リクエストの間使用できます。しかし、requestScopeスコープに定義されているマネージドBeanは、1つのページ・リクエストの間のみ使用できます。

図4-10に、各タイプのスコープが有効な期間およびページ・フローとの関係を示します。

図4-10 スコープとページ・フローの関係

ADFライフサイクルでのスコープ

マネージドBeanを登録するスコープまたは値を格納するスコープを決める場合、可能なかぎり狭いスコープを常に使用するようにします。sessionScopeスコープは、ユーザー情報やコンテキスト情報などのセッション全体に関係する情報にのみ使用します。あるページから別のページへの値の受渡しには、sessionScopeスコープは使用しないようにします。


注意:

フルFusionテクノロジ・スタックを使用している場合、様々な構成ファイルにマネージドBeanを登録するためのオプションがあります。詳細は、『Oracle Fusion Middleware Oracle Application Development Framework Fusion開発者ガイド』のFusion WebアプリケーションでのマネージドBeanの使用に関する項を参照してください。

4.7 ページ間の値の受渡し


注意:

フルFusionテクノロジ・スタックを使用していて、ADFバインド・タスク・フローのページ間またはADFリージョンとページの間での値の受渡しについての情報が必要な場合は、『Oracle Fusion Middleware Oracle Application Development Framework Fusion開発者ガイド』の「ADFタスク・フローのスタート・ガイド」を参照してください。

ADF FacesのpageFlowScopeスコープは、ページ間での値の受渡しを容易にするため、マスター/ディテール・ページを簡単に開発できます。pageFlowScopeスコープに追加された値は、redirectディレクティブを使用する場合でも、ユーザーがページ間を移動する際に自動的に継続使用できます。ただし、sessionスコープとは異なり、これらの値を表示できるのは、現在のページ・フローまたはプロセス内のみです。ユーザーが新たなウィンドウを開いてナビゲートを始めた場合、そのウィンドウは固有のプロセスを持ちます。各ウィンドウに格納された値は独立したままです。

標準JSFスコープに格納されているオブジェクト同様、pageFlowスコープに格納されているオブジェクトもEL式を介してアクセスできます。pageFlowスコープとの唯一の違いは、オブジェクト名にpageFlowScope接頭辞を使用する必要があることです。たとえば、ボタンのラベルをpageFlowスコープに格納されているマネージドBeanで指定し、ボタンが選択されるとBeanのメソッドがコールされるようにする場合は、ページに次のコードを使用します。

<af:commandButton text="#{pageFlowScope.buttonBean.label}"
                  action="#{pageFlowScope.buttonBean.action}"/>

pageFlowScopeは、Javaコードからアクセスできるjava.util.Mapオブジェクトです。setPropertyListenerタグでは、スコープにプロパティ値を設定でき、タグでリスニングするイベントを定義することもできます。たとえば、type属性をactionに設定したsetPropertyListenerタグを使用する場合、ナビゲーションの前にアクション・ソース(commandButtonなど)に値を設定させる宣言方式が用意されています。pageFlowScopeスコープとsetPropertyListenerタグを併用して、バッキングBeanにJavaコードを記述せずに、ページ間で値を受渡しできます。たとえば、setPropertyListenerタグとコマンド・コンポーネントを使用してpageFlowScopeスコープに値を設定するページと、テキスト・コンポーネントでpageFlowScopeスコープを使用して値を取得する別のページがある場合があります。

pageFlowScopeスコープを使用して、ダイアログなどの2つ目のウィンドウ間で値を設定することもできます。commandButtonコンポーネントなどから2つ目のウィンドウを起動する場合、launchEventイベントおよびpageFlowScopeスコープを使用して、親プロセスの値をオーバーライドせずに2つ目のウィンドウとの間で値を受け渡すことができます。

4.7.1 Javaコード内でのpageFlowScopeスコープの使用方法

pageFlowスコープは、アプリケーションのJavaコード内からアクセスできます。終了したら、スコープを必ずクリアしてください。


注意:

アプリケーションでADFコントローラが使用されている場合は、スコープを手動でクリアする必要はありません。

JavaコードでpageFlowScopeを使用する手順:

  1. pageFlowScopeスコープへの参照を得るために、org.apache.myfaces.trinidad.context.RequestContext.
    getPageFlowScope()
    メソッドを使用します。

    たとえば、pageFlowScopeスコープからオブジェクトを取得するには、次のJavaコードを使用します。

    import java.util.Map;
    import org.apache.myfaces.trinidad.context.RequestContext;
    . . .
    Map pageFlowScope = RequestContext.getCurrentInstance().getPageFlowScope();
    Object myObject = pageFlowScope.get("myObjectName");
    
  2. pageFlowScopeスコープをクリアするには、スコープにアクセスしてから手動でクリアします。

    たとえば、次のJavaコードを使用してスコープをクリアします。

    RequestContext afContext = RequestContext.getCurrentInstance();
    afContext.getPageFlowScope().clear();
    

4.7.2 Javaコードを記述せずにpageFlowScopeスコープを使用する方法

Javaコードを記述せずにpageFlowScopeスコープを使用するには、setPropertyListenerタグとコマンド・コンポーネントを組み合せて使用し、スコープに値を設定します。setPropertyListenerタグでは、リスニングするイベント・タイプを定義するtype属性を使用します。このタイプに合致しないイベントはすべて無視されます。設定後は、ページ・フロー内の別のページからその値にアクセスできます。


ヒント:

(以前のバージョンのADF Facesで使用されていた可能性がある)setActionListenerタグを使用するかわりに、setPropertyListenerタグを使用し、イベント・タイプをactionに設定します。

pageFlowScopeスコープに値を設定する手順:

  1. 値の設定元のページで、コンポーネント・パレットを使用してコマンド・コンポーネントを作成します。

  2. コンポーネント・パレットの「操作」パネルから、「プロパティ・リスナーの設定」をコマンド・コンポーネントの子としてドラッグ・アンド・ドロップします。

    または、コンポーネントを右クリックし、「ボタンの中に挿入」→「ADF Faces」→「setPropertyListener」の順に選択します。

  3. 「プロパティ・リスナーの設定の挿入」ダイアログで、「自」フィールドに別のコンポーネントに設定される値を設定します。

    たとえば、従業員の名前を格納するMyBeanという名前のマネージドBeanがあり、次のページに値を受け渡すとします。「自」フィールドに#{myBean.empName}と入力します。

  4. 「至」フィールドにpageFlowScopeスコープでの値を設定します。

    たとえば、「至」フィールドに#{pageFlowScope.empName}と入力します。

  5. 「タイプ」ドロップダウン・メニューから、「アクション」を選択します。

    これによって、リスナーで、コマンド・コンポーネントに関連付けられているアクション・イベントをリスニングできます。

pageFlowScopeスコープから値にアクセスする手順:

  1. 値のアクセス元のページに、値を表示するコンポーネントをドロップします。

  2. コンポーネントの値を、setPropertyListenerタグで設定したTo値と同じ値に設定します。

    たとえば、outputTextコンポーネントで従業員名にアクセスするには、このコンポーネントの値を#{pageFlowScope.empName}に設定します。

4.7.3 実行時の処理: 値の受渡し

ユーザーがsetPropertyListenerタグを含むコマンド・ボタンをクリックすると、リスナーが実行され、To値が解決されて取得され、pageFlowScopeスコープにプロパティとして格納されます。EL式を通じてそのプロパティにアクセスする後続のページでは、式は元のページによって設定された値に解決されます。