この章では、ADF Facesコンポーネントを使用してSRDemoアプリケーションの機能の一部を作成する方法について説明します。
この章の内容は次のとおりです。
ADF Facesコンポーネントは、ユーザーの操作を単純化します。たとえば、inputFileを使用するとファイルのアップロードが可能になります。また、selectInputTextには、選択された値に応じてポップアップ・ウィンドウにナビゲートしたり、最初のページに戻るためのダイアログ・サポートが組み込まれています。ほとんどのADF Facesコンポーネントは最小限のJavaコーディングで即時利用できますが、バッキングBean内の追加のコーディングとfaces-config.xml内の構成が必要となるものもあります。
SRDemoページではカスタム・スキンを使用していますが、この章では、レンダリングされたUIコンポーネントの説明および図にデフォルトのOracleスキンを使用します。
この章では次の内容について説明します。
メニュー・モデルを使用して動的なナビゲーション・メニューを作成する方法
コマンド・コンポーネントを使用してポップアップ・ダイアログを作成する方法
部分トリガーおよびイベントを使用して部分ページ・レンダリングを明示的に有効化する方法
プロセス・トレイン・モデルを使用して複数ページ・プロセスを作成する方法
ファイル・アップロード・サポートを提供する方法
静的および動的な値リストを備えたリスト、およびナビゲーション・リスト・バインディングを作成する方法
リスト・アイテムを表示および移動するためのシャトルを作成する方法
SRDemoページでは、panelPageコンポーネントを使用して、ページ・ナビゲーションのための階層メニュー・システムを備えたページをレイアウトしています。図19-1は、SRDemoアプリケーションのメニュー階層から選択可能なメニュー項目を持つ「Management」ページを示しています。通常、メニュー階層はグローバル・ボタン、メニュー・タブ、およびメニュー・タブの下のメニュー・バーで構成されます。
手動での作成。個々のメニュー項目コンポーネントを各メニュー・コンポーネントに挿入し、各ページ上で現在のメニュー項目を選択済としてマーク付けします。
宣言的に作成。各メニュー・コンポーネントをメニュー・モデル・オブジェクトにバインドし、現在の項目を選択済として設定することを含め、メニュー・モデルを使用して適切なメニュー項目を表示します。
SRDemoアプリケーションで表示されるほとんどのページでは、(メニュー・モデルとマネージドBeanを使用する)宣言的な手法を採用してメニュー階層を動的に生成しています。
panelPageコンポーネントでは、ユーザーがアプリケーション内の関連ページをすばやく表示できる階層構造のナビゲーション・メニューを作成するためのmenu1およびmenu2ファセットがサポートされています。
menu1ファセットは、メニュー・タブとしてレンダリングされる一連のメニュー項目をレイアウトするmenuTabsコンポーネントを取ります。同様に、menu2ファセットは、メニュー・タブの下のバーにメニュー項目をレンダリングするmenuBarコンポーネントを取ります。
グローバル・ボタンは、「Help」ボタンのように、アプリケーションのどのページからも常に選択可能なボタンです。panelPageのmenuGlobalファセットは、一連のボタンをレイアウトするmenuButtonsコンポーネントを取ります。
|
注意: SRDemoアプリケーションのグローバル・ボタンは動的に生成されるのではなく、<f:subview>および<jsp:include>を使用した動的なインクルードによって各ページにハードコードされます。一部のページでは、menuTabsコンポーネント内に、キャッシュ可能なフラグメントが使用されています。この章では動的メニューの作成方法を説明することが目的なので、説明とコード・サンプルにグローバル・ボタンは含まれていますが、キャッシングは含まれていません。キャッシングの詳細は、第23章「キャッシングによるアプリケーション・パフォーマンスの最適化」を参照してください。 |
階層メニューを動的に表示するには、メニュー・モデルを構築し、(menuTabsやmenuBarなどの)メニュー・コンポーネントをメニュー・モデルにバインドします。実行時に、メニュー・モデルによってページの階層メニュー選択項目が生成されます。
動的ナビゲーション・メニューを作成する手順:
メニュー・モデルを作成します。(19.2.1.1項「メニュー・モデルの作成」を参照。)
メニュー選択項目、またはメニュー階層内の項目ごとにJSFページを作成します。(19.2.1.2項「各メニュー項目のJSFページの作成」を参照。)
メニュー項目ごとに、ナビゲーション・ケースを持つグローバル・ナビゲーション・ルールを1つ作成します。(19.2.1.3項「JSFナビゲーション・ルールの作成」を参照。)
oracle.adf.view.faces.model.MenuModel、oracle.adf.view.faces.model.ChildPropertyTreeModelおよびoracle.adf.view.faces.model.ViewIdPropertyMenuModelクラスを使用して、メニュー階層を動的に生成するメニュー・モデルを作成します。
メニュー・モデルを作成する手順:
メニュー階層またはツリー内の項目ごとに、プロパティを取得および設定できるクラスを作成します。
たとえば、ツリー内の各項目にはlabel、viewIdおよびoutcomeプロパティが必要です。項目に子がある場合(たとえば、メニュー・タブ項目に子メニュー・バー項目が含まれる場合など)は、子のリストを表すプロパティ(たとえば、childrenプロパティなど)を定義する必要があります。セキュリティ・ロールに応じてページに項目を表示するか否かを決定するには、ブール・プロパティ(たとえば、shownプロパティなど)を定義します。例19-1に、SRDemoアプリケーションで使用されているMenuItemクラスを示します。
例19-1 すべてのメニュー項目に対するMenuItem.java
package oracle.srdemo.view.menu;
import java.util.List;
import oracle.adf.view.faces.component.core.nav.CoreCommandMenuItem;
public class MenuItem {
private String _label = null;
private String _outcome = null;
private String _viewId = null;
private String _destination = null;
private String _icon = null;
private String _type = CoreCommandMenuItem.TYPE_DEFAULT;
private List _children = null;
//extended security attributes
private boolean _readOnly = false;
private boolean _shown = true;
public void setLabel(String label) {
this._label = label;
}
public String getLabel() {
return _label;
}
// getter and setter methods for remaining attributes omitted
}
|
注意: typeプロパティは、メニュー項目をグローバル項目または非グローバル項目として定義します。グローバル項目は、アプリケーションのどのページからでもアクセスできます。たとえば、ページの「Help」ボタンはグローバル項目です。 |
階層内のメニュー項目またはページごとに、マネージドBeanを構成します。インスタンス化時に設定を必要とするプロパティには値を指定します。
各Beanは、手順1で作成したメニュー項目クラスのインスタンスである必要があります。例19-2に、SRDemoアプリケーション内のすべてのメニュー項目に対するマネージドBeanコードを示します。項目が子項目を持つ場合、リスト・エントリには、子のマネージドBeanを適切な順序でリストします。たとえば、「Management」メニュー・タブ項目は2つの子を持ちます。
通常、各Beanにはそのスコープとしてnoneを割り当てる必要があります。ただし、SRDemoアプリケーションでは、メニュー項目が動的に作成されるときにセキュリティ属性が割り当てられるため、メニュー項目にはsessionスコープのマネージドBeanを使用しています。また、SRDemoアプリケーションでは、現在ログインしているユーザーのユーザー・ロール情報を保持するために、sessionスコープのUserInfo Beanを使用しています。ユーザーのログイン時に表示されるメニュー項目は、このユーザー・ロール情報を使用して決定されます。たとえば、「Management」メニュー・タブは、管理者のユーザー・ロールを持つユーザーのみに表示されます。JSFでは、noneスコープのBeanからsessionスコープのマネージドBeanを参照することはできません。このため、SRDemoアプリケーションでは、メニュー・システムに対してすべてsessionスコープのマネージドBeanを使用しています。
例19-2 faces-config.xmlファイル内のメニュー項目に対するマネージドBean
<!-- If you were to use dynamically generated global buttons -->
<!-- Root pages: Two global button menu items -->
<managed-bean>
<managed-bean-name>menuItem_GlobalLogout</managed-bean-name>
<managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>#{resources['srdemo.menu.logout']}</value>
</managed-property>
<managed-property>
<property-name>icon</property-name>
<value>/images/logout.gif</value>
</managed-property>
<managed-property>
<property-name>type</property-name>
<value>global</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/app/SRLogout.jsp</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>GlobalLogout</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_GlobalHelp</managed-bean-name>
<managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>#{resources['srdemo.menu.help']}</value>
</managed-property>
<managed-property>
<property-name>icon</property-name>
<value>/images/help.gif</value>
</managed-property>
<managed-property>
<property-name>type</property-name>
<value>global</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/app/SRHelp.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>GlobalHelp</value>
</managed-property>
</managed-bean>
<!-- Root pages: Four menu tabs -->
<!-- 1. My Service Requests menu tab item -->
<managed-bean>
<managed-bean-name>menuItem_MyServiceRequests</managed-bean-name>
<managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>#{resources['srdemo.menu.my']}</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/app/SRList.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>GlobalHome</value>
</managed-property>
</managed-bean>
<!-- 2. Advanced Search menu tab item -->
<managed-bean>
<managed-bean-name>menuItem_AdvancedSearch</managed-bean-name>
<managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>#{resources['srdemo.menu.advanced']}</value>
</managed-property>
<managed-property>
<property-name>shown</property-name>
<value>#{userInfo.staff}</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/app/staff/SRSearch.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>GlobalSearch</value>
</managed-property>
</managed-bean>
<!-- 3. New Service Request menu tab item -->
<managed-bean>
<managed-bean-name>menuItem_New</managed-bean-name>
<managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>#{resources['srdemo.menu.new']}</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/app/SRCreate.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>GlobalCreate</value>
</managed-property>
</managed-bean>
<!-- 4. Management menu tab item -->
<!-- This managed bean uses managed bean chaining for children menu items -->
<managed-bean>
<managed-bean-name>menuItem_Manage</managed-bean-name>
<managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>#{resources['srdemo.menu.manage']}</value>
</managed-property>
<managed-property>
<property-name>shown</property-name>
<value>#{userInfo.manager}</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/app/management/SRManage.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>GlobalManage</value>
</managed-property>
<managed-property>
<property-name>children</property-name>
<list-entries>
<value-class>oracle.srdemo.view.menu.MenuItem</value-class>
<value>#{subMenuItem_Manage_Reporting}</value>
<value>#{subMenuItem_Manage_ProdEx}</value>
</list-entries>
</managed-property>
</managed-bean>
<!-- Children menu bar items for Management tab -->
<managed-bean>
<managed-bean-name>subMenuItem_Manage_Reporting</managed-bean-name>
<managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>#{resources['srdemo.menu.manage.reporting']}</value>
</managed-property>
<managed-property>
<property-name>shown</property-name>
<value>#{userInfo.manager}</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/app/management/SRManage.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>GlobalManage</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>subMenuItem_Manage_ProdEx</managed-bean-name>
<managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>#{resources['srdemo.menu.manage.prodEx']}</value>
</managed-property>
<managed-property>
<property-name>shown</property-name>
<value>#{userInfo.manager}</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/app/management/SRSkills.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>Skills</value>
</managed-property>
</managed-bean>
|
注意: 図19-1に示されているように、「Management」メニュー・タブには、「Overview」および「Technician Skills」という2つの項目を持つメニュー・バーがあります。各メニュー項目は独自のページまたはマネージドBeanを持つため、この2つの項目はそれぞれ、subMenuItem_Manage_ReportingおよびsubMenuItem_Manage_ProdExというマネージドBeanで表されます。「Management」メニュー・タブはmenuItem_ManageというマネージドBeanで表されます。これは、リストのvalue要素内部で値バインディング式(#{subMenuItem_Manage_ProdEx}など)を使用して子のマネージドBeanを参照します。 |
ChildPropertyTreeModelインスタンスを構成するクラスを作成します。このインスタンスはメニュー・システムのツリー階層全体を表します。後でこれをメニュー・モデルに挿入します。例19-3に、SRDemoアプリケーションで使用されているMenuTreeModelAdapterクラスを示します。
例19-3 メニュー・ツリー階層を収容するためのMenuTreeModelAdapter.java
package oracle.srdemo.view.menu;
import java.beans.IntrospectionException;
import java.util.List;
import oracle.adf.view.faces.model.ChildPropertyTreeModel;
import oracle.adf.view.faces.model.TreeModel;
public class MenuTreeModelAdapter {
private String _propertyName = null;
private Object _instance = null;
private transient TreeModel _model = null;
public TreeModel getModel() throws IntrospectionException
{
if (_model == null)
{
_model = new ChildPropertyTreeModel(getInstance(), getChildProperty());
}
return _model;
}
public String getChildProperty()
{
return _propertyName;
}
/**
* Sets the property to use to get at child lists
* @param propertyName
*/
public void setChildProperty(String propertyName)
{
_propertyName = propertyName;
_model = null;
}
public Object getInstance()
{
return _instance;
}
/**
* Sets the root list for this tree.
* @param instance must be something that can be converted into a List
*/
public void setInstance(Object instance)
{
_instance = instance;
_model = null;
}
/**
* Sets the root list for this tree.
* This is needed for passing a List when using the managed bean list
* creation facility, which requires the parameter type of List.
* @param instance the list of root nodes
*/
public void setListInstance(List instance)
{
setInstance(instance);
}
}
手順3のメニュー・ツリー・モデル・クラスを参照するマネージドBeanを構成します。このBeanはインスタンス化されたときに、childProperty値が、手順1でBeanに作成した子リストを表すプロパティ値と同じになる必要があります。
また、listInstanceプロパティの値として(適切な順序でリストした)ルート・ページのリストが使用されている必要もあります。例19-2に示したように、ルート・ページはグローバル・ボタン・メニュー項目であり、第1レベルのメニュー・タブ項目です。例19-4に、メニュー・ツリー・モデルを作成するためのマネージドBeanを示します。
例19-4 faces-config.xmlファイル内のメニュー・ツリー・モデルに対するマネージドBean
<managed-bean> <managed-bean-name>menuTreeModel</managed-bean-name> <managed-bean-class> oracle.srdemo.view.menu.MenuTreeModelAdapter </managed-bean-class> <managed-bean-scope>session</managed-bean-scope> <managed-property> <property-name>childProperty</property-name> <value>children</value> </managed-property> <managed-property> <property-name>listInstance</property-name> <list-entries> <value-class>oracle.srdemo.view.menu.MenuItem</value-class> <value>#{menuItem_GlobalLogout}</value> <value>#{menuItem_GlobalHelp}</value> <value>#{menuItem_MyServiceRequests}</value> <value>#{menuItem_AdvancedSearch}</value> <value>#{menuItem_New}</value> <value>#{menuItem_Manage}</value> </list-entries> </managed-property> </managed-bean>
ViewIdPropertyMenuModelインスタンスを構成するクラスを作成します。このインスタンスにより、メニュー・ツリー・モデルからメニュー・モデルが作成されます。例19-5に、SRDemoアプリケーションで使用されているMenuModelAdapterクラスを示します。
例19-5 MenuModelAdapter.java
package oracle.srdemo.view.menu;
import java.beans.IntrospectionException;
import java.io.Serializable;
import java.util.List;
import oracle.adf.view.faces.model.MenuModel;
import oracle.adf.view.faces.model.ViewIdPropertyMenuModel;
public class MenuModelAdapter implements Serializable {
private String _propertyName = null;
private Object _instance = null;
private transient MenuModel _model = null;
private List _aliasList = null;
public MenuModel getModel() throws IntrospectionException
{
if (_model == null)
{
ViewIdPropertyMenuModel model =
new ViewIdPropertyMenuModel(getInstance(),
getViewIdProperty());
if(_aliasList != null && !_aliasList.isEmpty())
{
int size = _aliasList.size();
if (size % 2 == 1)
size = size - 1;
for ( int i = 0; i < size; i=i+2)
{
model.addViewId(_aliasList.get(i).toString(),
_aliasList.get(i+1).toString());
}
}
_model = model;
}
return _model;
}
public String getViewIdProperty()
{
return _propertyName;
}
/**
* Sets the property to use to get at view id
* @param propertyName
*/
public void setViewIdProperty(String propertyName)
{
_propertyName = propertyName;
_model = null;
}
public Object getInstance()
{
return _instance;
}
/**
* Sets the treeModel
* @param instance must be something that can be converted into a TreeModel
*/
public void setInstance(Object instance)
{
_instance = instance;
_model = null;
}
public List getAliasList()
{
return _aliasList;
}
public void setAliasList(List aliasList)
{
_aliasList = aliasList;
}
}
手順5のメニュー・モデル・クラスを参照するマネージドBeanを構成します。ページ上のすべてのメニュー・コンポーネントは、このBeanにバインドされます。
このBeanはインスタンス化されたときに、instanceプロパティ値が、手順4で構成したメニュー・ツリー・モデルBeanのmodelプロパティに設定されている必要があります。また、インスタンス化されたBeanでは、viewIdProperty値が、手順1で作成したBeanのviewIdプロパティに設定されている必要があります。例19-6に、メニュー・モデルを作成するためのマネージドBeanコードを示します。
例19-6 faces-config.xmlファイル内のメニュー・モデルに対するマネージドBean
<!-- create the main menu menuModel -->
<managed-bean>
<managed-bean-name>menuModel</managed-bean-name>
<managed-bean-class>
oracle.srdemo.view.menu.MenuModelAdapter</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>viewIdProperty</property-name>
<value>viewId</value>
</managed-property>
<managed-property>
<property-name>instance</property-name>
<value>#{menuTreeModel.model}</value>
</managed-property>
</managed-bean>
値バインディング式を使用してマネージドBean定義を連鎖させることにより、フラットな構造でなくツリー形式のメニュー・システムを作成できます。faces-config.xmlの個々のマネージドBean定義はどのような順序でもかまいませんが、親Bean内の子list-entriesの順序は、メニュー選択項目を表示する順序と同じにする必要があります。
マネージドBean定義を連鎖させる場合、Beanスコープに互換性があることが必要です。表19-1に、互換性のあるBeanスコープを示します。
SRDemoアプリケーションのすべてのラベルに対するStringリソースは、リソース・バンドルに含まれています。このリソース・バンドルはfaces-config.xml内で構成されています。前述のように、各メニュー項目はsessionスコープのマネージドBeanとして定義され、メニュー項目の様々な属性(typeやlabelなど)はマネージドBeanプロパティを介して定義されています。メニュー項目のマネージドBeanがリソース・バンドルから使用するラベルにアクセスするには、バンドルへのアクセスを提供するマネージドBeanを構成する必要があります。
SRDemoアプリケーションでは、ResourceAdapterクラスが、resourcesというマネージドBeanを介してEL式内でリソース・バンドルを公開しています。例19-7に、ResourceAdapterクラスと、バンドルからStringを取得するJSFUtils.getStringFromBundle()メソッドを示します。
例19-7 ResourceAdapter.javaの一部およびJSFUtils.javaの一部
package oracle.srdemo.view.resources;
import oracle.srdemo.view.util.JSFUtils;
/**
* Utility class that allows us to expose the specified resource bundle within
* general EL
*/
public class ResourceAdapter implements Map {
public Object get(Object resourceKey) {
return JSFUtils.getStringFromBundle((String)resourceKey);
}
// Rest of file omitted from here
}
...
/** From JSFUtils.java */
package oracle.srdemo.view.util;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
...
public class JSFUtils {
private static final String NO_RESOURCE_FOUND = "Missing resource: ";
/**
* Pulls a String resource from the property bundle that
* is defined under the application's message-bundle element in
* faces-config.xml. Respects Locale.
* @param key
* @return Resource value or placeholder error String
*/
public static String getStringFromBundle(String key) {
ResourceBundle bundle = getBundle();
return getStringSafely(bundle, key, null);
}
/*
* Internal method to proxy for resource keys that don't exist
*/
private static String getStringSafely(ResourceBundle bundle, String key,
String defaultValue) {
String resource = null;
try {
resource = bundle.getString(key);
} catch (MissingResourceException mrex) {
if (defaultValue != null) {
resource = defaultValue;
} else {
resource = NO_RESOURCE_FOUND + key;
}
}
return resource;
}
//Rest of file omitted from here
}
例19-8に、他のマネージドBeanに対してStringリソースへのアクセスを提供するresourcesというマネージドBeanコードを示します。
例19-8 リソース・バンドルのStringにアクセスするためのマネージドBean
<!-- Resource bundle -->
<application>
<message-bundle>oracle.srdemo.view.resources.UIResources</message-bundle>
...
</application>
<!-- Managed bean for ResourceAdapater class -->
<managed-bean>
<managed-bean-name>resources</managed-bean-name>
<managed-bean-class>
oracle.srdemo.view.resources.ResourceAdapter</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
resourcesというマネージドBeanは、faces-config.xml内で定義されているリソース・バンドルへのMapインタフェースを定義します。メニュー項目ラベルは、適切な言語文字列を自動的に選択します。
|
ヒント: メニュー・モデルは、最初に参照されるときに構築されます。このため、1つのセッション内でブラウザ言語が変更されたときには再構築されません。 |
(メニュー・タブ項目、メニュー・バー項目またはグローバル・ボタンのいずれの場合も)各メニュー項目はそれぞれ独自のページを持ちます。ページ上に使用可能なメニュー選択項目を表示するには、(menuTabs、menuBar、menuButtonsなどの)メニュー・コンポーネントをメニュー・モデルにバインドします。例19-9に、コンポーネントをメニュー・モデルにバインドするmenuTabsコンポーネント・コードを示します。
例19-9 メニュー・モデルにバインドされたmenuTabsコンポーネント
<af:panelPage title="#{res['srmanage.pageTitle']}"
binding="#{backing_SRManage.panelPage1}"
id="panelPage1">
<f:facet name="menu1">
<af:menuTabs value="#{menuModel.model}"...>
...
</af:menuTabs>
</f:facet>
...
</af:panelPage>
例19-10に示すように、各メニュー・コンポーネントは、1つのcommandMenuItemコンポーネントを取るnodeStampファセットを持ちます。変数を使用し、メニュー・コンポーネントをモデルにバインドすれば、commandMenuItemコンポーネントを1つ使用するだけで、メニュー内のすべての項目を表示できます。これには、commandMenuItemコンポーネントで、text値に#{var.label}のようなEL式を使用し、action 値に#{var.getOutcome}のようなEL式を使用します。これは、メニュー項目に表示される実際のラベルと、そのメニュー項目が起動されたときのナビゲーション結果を提供するcommandMenuItemコンポーネントです。
例19-10 nodeStampファセットとcommandMenuItemコンポーネント
<af:panelPage title="#{res['srmanage.pageTitle']}"
binding="#{backing_SRManage.panelPage1}"
id="panelPage1">
<f:facet name="menu1">
<af:menuTabs var="menuTab"
value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{menuTab.label}"
action="#{menuTab.getOutcome}"
.../>
</f:facet>
</af:menuTabs>
</f:facet>
...
</af:panelPage>
メニュー項目がページ上にレンダリングされるかどうかは、現在ログインしているユーザーのセキュリティ・ロールによって決定されます。たとえば、「Management」メニュー・タブは、管理者ロールを持つユーザーのみに表示されます。メニュー項目をレンダリングするか無効にするかは、commandMenuItemコンポーネントのrenderedまたはdisabled属性によって指定します。
例19-1ではMenuItemクラスを使用しています。グローバル項目の場合、rendered属性を変数のtypeプロパティにバインドし、このプロパティをglobalに設定します。非グローバル項目以外の場合、rendered属性を変数のshownプロパティとtypeプロパティにバインドし、typeプロパティをdefaultに設定します。非グローバル項目の場合は、さらにdisabled属性を変数のreadOnlyプロパティにバインドします。例19-11に、menuTabs(非グローバル・コンポーネント)およびmenuButtons(グローバル・コンポーネント)に対してこれを行う方法を示します。
例19-11 レンダリングおよび無効化するMenuItemコンポーネント
<af:menuTabs var="menuTab" value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{menuTab.label}"
action="#{menuTab.getOutcome}"
rendered="#{menuTab.shown and
menuTab.type=='default'}"
disabled="#{menuTab.readOnly}"/>
</f:facet>
</af:menuTabs>
...
<af:menuButtons var="menuOption" value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{menuOption.label}"
action="#{menuOption.getOutcome}"
rendered="#{menuOption.type=='global'}"
icon="#{menuOption.icon}"/>
</f:facet>
</af:menuButtons>
アプリケーションでは、必要なメニューを自由に組み合せて使用できます。たとえば、メニュー・タブを持たないメニュー・バーのみを使用することもできます。メニュー階層の開始レベルをADF Facesに認識させるには、メニュー・コンポーネントにstartDepth属性を設定します。3つのメニュー・レベルを使用する場合、ゼロ・ベースの索引に基づいて、startDepthの有効な値は0、1および2になります。startDepthを指定しない場合は、デフォルトで0に設定されます。
アプリケーションでグローバル・メニュー・ボタン、メニュー・タブおよびメニュー・バーを使用する場合、グローバルなmenuButtonsコンポーネントのstartDepthは常に0になります。メニュー・タブは第1レベルなので、menuTabsのstartDepthも同様に0になります。この場合、menuBarコンポーネントのstartDepthの値は1になります。例19-12に、panelPageコンポーネントのメニュー・コードの一部を示します。
例19-12 メニュー・ファセットを持つpanelPageコンポーネント
<af:panelPage title="#{res['srmanage.pageTitle']}">
<f:facet name="menu1">
<af:menuTabs var="menuTab" value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{menuTab.label}"
action="#{menuTab.getOutcome}"
rendered="#{menuTab.shown and
menuTab.type=='default'}"
disabled="#{menuTab.readOnly}"/>
</f:facet>
</af:menuTabs>
</f:facet>
<f:facet name="menu2">
<af:menuBar var="menuSubTab" startDepth="1"
value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{menuSubTab.label}"
action="#{menuSubTab.getOutcome}"
rendered="#{menuSubTab.shown and
menuSubTab.type=='default'}"
disabled="#{menuSubTab.readOnly}"/>
</f:facet>
</af:menuBar>
</f:facet>
<f:facet name="menuGlobal">
<af:menuButtons var="menuOption" value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{menuOption.label}"
action="#{menuOption.getOutcome}"
rendered="#{menuOption.type=='global'}"
icon="#{menuOption.icon}"/>
</f:facet>
</af:menuButtons>
</f:facet>
...
</af:panelPage>
|
ヒント: メニュー・システムで第1レベルとしてメニュー・バーを使用する場合、menuBarのstartDepthは0に設定する必要があります(その他も同様)。 |
panelPageコンポーネントを使用してページの各メニュー・コンポーネントをメニュー・モデル・オブジェクトにバインドせずに、メニュー・モデルを使用するpageコンポーネントを使用することもできます。次のコードSnippetに示すように、pageコンポーネントをメニュー・モデルに値バインディングすることにより、ページ・コンポーネントのよりフレキシブルなレンダリング機能を活用できます。たとえば、メニュー・コンポーネントのルック・アンド・フィールは、pageコンポーネントの新しいレンダラを作成することで簡単に変更できます。panelPageコンポーネントを使用する場合は、メニュー・コンポーネントごとにレンダラを変更する必要があります。
<af:page title="Title 1" var="node" value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{node.label}"
action="#{node.getOutcome}"
type="#{node.type}"/>
</f:facet>
</af:page>
メニュー・モデルは動的に階層(つまり、各メニュー・コンポーネントに表示されるリンク)を決定し、またフォーカス・パス上の現在の項目を選択済として設定するため、実質的には各ページに同じコードを使用できます。
第1レベルのグローバルなメニュー項目ごとに、ナビゲーション・ケースを持つグローバル・ナビゲーション・ルールを1つ作成します。グローバル・ナビゲーション・ルールには子メニュー項目は含めません。例19-13に示すように、子メニュー項目を持つメニュー項目(たとえば、「Management」メニュー・タブは子メニュー・バー項目を持つ)に対して、親項目からのすべてのナビゲーション・ケースを含むナビゲーション・ルールを作成します。
例19-13 faces-config.xmlファイル内のメニュー・システムに対するナビゲーション・ルール
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>GlobalHome</from-outcome>
<to-view-id>/app/SRList.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>GlobalSearch</from-outcome>
<to-view-id>/app/staff/SRSearch.jspx</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>GlobalCreate</from-outcome>
<to-view-id>/app/SRCreate.jspx</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>GlobalManage</from-outcome>
<to-view-id>/app/management/SRManage.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>GlobalLogout</from-outcome>
<to-view-id>/app/SRLogout.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>GlobalAbout</from-outcome>
<to-view-id>/app/SRAbout.jspx</to-view-id>
</navigation-case>
</navigation-rule>
<!-- Navigation rule for Management menu tab with children items -->
<navigation-rule>
<from-view-id>/app/management/SRManage.jspx</from-view-id>
<navigation-case>
<from-outcome>Skills</from-outcome>
<to-view-id>/app/management/SRSkills.jspx</to-view-id>
</navigation-case>
</navigation-rule>
MenuModelAdapterは、menuModelというマネージドBeanを介してメニュー・モデル(ViewIdPropertyMenuModelインスタンス)を構成します。menuTreeModel Beanがリクエストされると、連鎖しているBean menuItem_GlobalLogout、menuItem_GlobalHelp、menuItem_MyServiceRequestsなどの作成が自動的にトリガーされます。メニュー項目のツリーがメニュー・モデルに挿入されます。メニュー・システムをナビゲートすると、メニュー・モデルにより、メニュー上の項目が適切に強調表示および有効化されたモデルが提供されます。
個々のメニュー項目マネージドBean(たとえば、menuItem_MyServiceRequests)は、メニュー・モデルがメニュー項目を動的に生成するときに使用するlabel、viewIdおよびoutcomeの値でインスタンス化されます。デフォルトのJSF actionListenerメカニズムでは、outcomeの値を使用してページ・ナビゲーションが処理されます。
各メニュー・コンポーネントはnodeStampファセットを持ちます。これは、メニュー・モデル内の様々なメニュー項目にスタンプを設定するために使用されます。nodeStampファセットの中のcommandMenuItemコンポーネントは、各メニュー項目のテキストおよびアクションを提供します。nodeStampにスタンプが設定されるたびに、現在のメニュー項目のデータがELに到達可能なプロパティにコピーされます。このプロパティの名前は、nodeStampファセットの中のメニュー・コンポーネントのvar属性によって定義されます。メニューのレンダリングが完了すると、このプロパティは削除されます(または以前の値に戻ります)。例19-14では、各メニュー・バー項目のデータがELプロパティmenuSubTabの下に配置されます。nodeStampは、menuSubTabプロパティから詳細なプロパティを取得して、各項目のデータを表示します。
例19-14 メニュー・モデルにバインドされたmenuBarコンポーネント
<af:menuBar var="menuSubTab" startDepth="1"
value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{menuSubTab.label}"
action="#{menuSubTab.getOutcome}"
rendered="#{menuSubTab.shown and
menuSubTab.type=='default'}"
disabled="#{menuSubTab.readOnly}"/>
</f:facet>
</af:menuBar>
メニュー・コンポーネントをメニュー・モデルにバインドし、メニュー項目を表す変数を使用することにより、commandMenuItemコンポーネントを1つ使用するだけで、その階層レベルのすべてのメニュー項目を表示できます。この場合、項目ごとにcommandMenuItemコンポーネントを手動で挿入する場合に比べ、複数のページで再利用できるコードが増え、ミスは大幅に少なくなります。たとえば、menuが変数の場合、#{menu.label}や#{menu.getOutcome}などのEL式は、commandMenuItemコンポーネントのtextおよびaction値を指定します。
メニュー・モデルはnodeStampとともに、メニュー項目を選択済としてレンダリングするかどうかを制御します。前述のように、メニュー・モデルは、各ノードのviewId情報が含まれたツリー・モデルから作成されます。ViewIdPropertyMenuModel(MenuModelのインスタンス)は、ノードのviewIdを使用してフォーカスrowKeyを決定します。メニュー・モデル内の各項目には、現在のrowKeyに基づいてスタンプが設定されます。ユーザーのナビゲートにより現在のviewIdが変更されると、モデルのフォーカス・パスも変更され、新しい項目セットがアクセスされます。MenuModelには、getFocusRowKey()というメソッドがあります。このメソッドは、フォーカスを得るページを決定し、ノードがそのフォーカス・パス上にあれば自動的にそのノードをレンダリングします。
メニュー・モデルを使用せずに、手動でメニューを作成する場合もあります。
図19-2に示すように、第1レベルのメニュー・タブ「My Service Requests」には、複数の項目を持つ第2レベルのメニュー・バーが1つあります。「My Service Requests」から、未処理、保留中、処理済またはすべてのサービス・リクエストを表示できます。これらは、左から順に1、2、3および4番目のメニュー・バー項目として表示されます。各ビューは、実際にはSRList.jspxページから生成されます。
SRList.jspxページでは、menuBarコンポーネントをメニュー・モデルにバインドし、nodeStampを使用してメニュー項目を生成するのではなく、個々の子commandMenuItemコンポーネントを使用してメニュー項目を表示しています。これは、コマンド・コンポーネントが、ナビゲート先のリクエストのタイプ(たとえば、未処理、保留中、処理済またはすべてのサービス・リクエスト)を決定するために値を必要とするためです。例19-15に、SRList.jspxページで使用されているmenuBarコンポーネントのコードの一部を示します。
例19-15 子commandMenuItemコンポーネントを持つmenuBarコンポーネント
<af:menuBar>
<af:commandMenuItem text="#{res['srlist.menubar.openLink']}"
disabled="#{!bindings.ExecuteWithParams.enabled}"
selected="#{userState.listModeOpen}"
actionListener="#{bindings.ExecuteWithParams.execute}">
<af:setActionListener from="#{'Open'}"
to="#{userState.listMode}"/>
</af:commandMenuItem>
<af:commandMenuItem text="#{res['srlist.menubar.pendingLink']}"
disabled="#{!bindings.ExecuteWithParams.enabled}"
selected="#{userState.listModePending}"
actionListener="#{bindings.ExecuteWithParams.execute}"
<af:setActionListener from="#{'Pending'}"
to="#{userState.listMode}"/>
</af:commandMenuItem>
...
<af:commandMenuItem text="#{res['srlist.menubar.allRequests']}"
selected="#{userState.listModeAll}"
disabled="#{!bindings.ExecuteWithParams.enabled}"
actionListener="#{bindings.ExecuteWithParams.execute}">
<af:setActionListener from="#{'%'}"
to="#{userState.listMode}"/>
</af:commandMenuItem>
...
</af:menuBar>
af:setActionListenerタグは、ナビゲーション前にActionSourceコンポーネントの値を宣言的に設定し、適切なリスト・モード値をuserStateマネージドBeanに渡します。sessionスコープのuserStateマネージドBeanは、ページの現在のリスト・モードを保存します。
commandMenuItemコンポーネントが起動されると、ExecuteWithParams組込み操作が実行され、パラメータの値がデータ・モデル内のServiceRequestsByStatusビュー・オブジェクト・インスタンスの名前付きバインド変数に渡されます。これにより、行セットが更新され、渡されたパラメータ値と一致するステータスを持つサービス・リクエストのみが含まれるようになります。また、commandMenuItemコンポーネントはUserSystemState Beanクラス内の簡易関数を使用して、メニュー項目を選択済としてマーク付けするかどうかを評価します。
新しいページを、現在のページと同じウィンドウではなく、別のポップアップ・ダイアログ内に表示する場合があります。ポップアップ・ダイアログでは、ユーザーに情報を入力または選択させてから、その情報を使用する元のページに戻らせることができます。通常は、ポップアップ・ダイアログを起動してプロセスを管理するためにJavaScriptを使用する必要がありました。また、PDAなどの特定のクライアント・デバイスではポップアップ・ダイアログがサポートされていないため、このようなケースを管理するためのコードを作成する必要もありました。ADF Facesにはダイアログ・フレームワークが備わっており、JavaScriptを使用せずにポップアップ・ダイアログおよびプロセスの起動と管理を容易に行うことができます。
受注を確認しようとするユーザーにログインを要求する単純なアプリケーションについて考えてみます。図19-3は、5つのページ(login.jspx、orders.jspx、new_account.jspx、account_details.jspxおよびerror.jspx)からなるアプリケーションのページ・フローを示しています。
既存のユーザーが正常にログインすると、アプリケーションにより「Orders」ページが表示され、ユーザーの受注が(存在していれば)表示されます。ユーザーが正常にログインできなかった場合、ポップアップ・ダイアログに「Error」ページが表示されます(図19-4)。
「Error」ページには「Cancel」ボタンがあります。ユーザーが「Cancel」をクリックすると、ポップアップ・ダイアログが閉じ、アプリケーションは「Login」ページ(図19-5)に戻ります。
新規のユーザーが「Login」ページで「New user」リンクをクリックすると、ポップアップ・ダイアログ内に「New Account」ページが表示されます(図19-6)。
ファースト・ネームやラスト・ネームなどの情報を入力した後、ユーザーが「Details」ボタンをクリックすると、同じポップアップ・ダイアログ内に「Account Details」ページが表示されます(図19-7)。ユーザーは「Account Details」ページでその他の情報を入力し、新しいログイン・アカウントのパスワードを確認入力します。「Account Details」ページには、2つのボタン(「Cancel」および「Done」)があります。
新規のユーザーが新しいログイン・アカウントの作成を中止しようとして「Cancel」をクリックした場合、ポップアップ・ダイアログが閉じ、アプリケーションは「Login」ページに戻ります。新規のユーザーが「Done」をクリックした場合、ポップアップ・ダイアログが閉じ、アプリケーションは「Login」ページに戻ります。このページの「Username」フィールドにユーザーの名前が移入されます(図19-8)。これで、新規のユーザーは、新しいパスワードを入力してログインできるようになります。
ADF Facesには、アプリケーションで簡単にポップアップ・ダイアログをサポートできるように、ActionSourceを実装するコンポーネント(commandButtonやcommandLinkなど)に対するダイアログ機能が組み込まれています。ActionSourceコンポーネントからポップアップ・ダイアログ内にページを起動するかどうかをADF Facesに判断させるためには、次の4つの条件が満たされている必要があります。
コマンド・コンポーネントのアクション結果が"dialog:"で始まっていること
コマンド・コンポーネントのuseWindow属性が"true"であること
クライアント・デバイスでポップアップ・ダイアログがサポートされていること
|
注意: useWindowがfalseの場合や、クライアント・デバイスでポップアップ・ダイアログがサポートされていない場合、ADF Facesでは自動的に、ポップアップを使用せずに現在のウィンドウ内にページが表示されます。この処理を実現するためにコードを変更する必要はありません。 |
ポップアップ・ダイアログ内に表示されるページは、通常のJSFページです。しかし、この章ではポップアップ・ダイアログの実装方法を説明することを目的としているため、ポップアップ・ダイアログ内に表示されるページをダイアログ・ページと表記し、ポップアップ・ダイアログが起動される前のページを元ページと表記します。ダイアログ・プロセスは、元ページがダイアログ(1つのダイアログ・ページが含まれる場合も、一連のダイアログ・ページが含まれる場合もある)を起動した時点で開始され、ユーザーがダイアログを閉じて元ページに戻った時点で終了します。
アプリケーションでポップアップ・ダイアログをサポートするための操作は、次のとおりです。
ダイアログを起動するためのJSFナビゲーション・ルールを定義します。
ダイアログを起動する元のJSFページを作成します。
ダイアログ・ページを作成し、ダイアログ値を返します。
戻り値を処理します。
値をダイアログに渡します。
これらの操作は、任意の順序で実行できます。
ポップアップ・ダイアログのナビゲーションを管理するには、特殊なdialog:結果を持つ標準的なJSFナビゲーション・ルールを定義します。ダイアログ・サンプル・アプリケーション(図19-3)を使用すると、「Login」ページからは次の3つのナビゲーション結果が考えられます。
同じウィンドウ内に「Orders」ページを表示(ログインの正常完了)
ポップアップ・ダイアログ内に「Error」ダイアログ・ページを表示(ログインの失敗)
ポップアップ・ダイアログ内に「New Account」ダイアログ・ページを表示(新規のユーザー)
例19-16に、「Login」ページ(login.jspx)からの3つのナビゲーション・ケースに対するナビゲーション・ルールを示します。
例19-16 faces-config.xmlファイル内のダイアログ・ナビゲーション・ルール
<navigation-rule>
<!-- Originating JSF page -->
<from-view-id>/login.jspx</from-view-id>
<!-- Navigation case for the New Account dialog page (new user)-->
<navigation-case>
<from-outcome>dialog:newAccount</from-outcome>
<to-view-id>/new_account.jspx</to-view-id>
</navigation-case>
<!-- Navigation case for the Error dialog page (upon login failure) -->
</navigation-case>
<from-outcome>dialog:error</from-outcome>
<to-view-id>/error.jspx</to-view-id>
</navigation-case>
<!-- Navigation case for the Orders page (upon login success) -->
</navigation-case>
<from-outcome>orders</from-outcome>
<to-view-id>/orders.jspx</to-view-id>
</navigation-case>
</navigation-rule>
ポップアップ・ダイアログを起動する元ページでは、ActionSourceコンポーネントに対してアクション・メソッドまたは静的アクション結果を使用できます。静的アクション結果を指定する場合も、アクション結果を返すアクション・メソッドを使用する場合も、このアクション結果はdialog:で開始する必要があります。
サンプル・アプリケーションでは、commandButtonコンポーネントに対してアクション・メソッド・バインディングを使用して、「Orders」ページまたは「Error」ダイアログ・ページのいずれにナビゲートするかをプログラムにより指定しています。また、commandLinkコンポーネントに対して静的アクション結果を使用して、「New Account」ダイアログ・ページに直接ナビゲートしています。どちらのコマンド・コンポーネントも「Login」ページにあります。例19-17に、「Login」commandButtonコンポーネントのコードを示します。
例19-17 「Login」ページの「Login」ボタン
af:commandButton id="cmdBtn"
text="Login"
action="#{backing_login.commandButton_action}"
useWindow="true"
windowHeight="200"
windowWidth="500"
partialSubmit="true"/>
ポップアップ・ダイアログ内にページを起動する際は、属性useWindow、windowHeightおよびwindowWidthが使用されます。クライアント・デバイスでポップアップ・ダイアログがサポートされていない場合、これらの属性は無視されます。
useWindow="true"の場合、ADF Facesにより新しいポップアップ・ダイアログ内にダイアログ・ページが起動されます。windowHeight属性とwindowWidth属性は、ポップアップ・ダイアログのサイズを指定します。
commandButtonのaction属性は、ページのバッキングBean(Login.java)内のアクション・メソッドへの参照を指定します。アクション・メソッドは結果文字列を返す必要があります。JSFはこの結果文字列を、faces-config.xml内で定義されているナビゲーション・ケースにおける結果と比較することにより、次に表示するページを決定します。このアクション・メソッドのコードを例19-18に示しています。
例19-18 「Login」ボタンのアクション・メソッド・コード
public String commandButton_action()
{
String retValue;
retValue = "orders";
_cust = getListCustomer();
if (_cust == null || !password.equals(_cust.getPassword()))
{
retValue = "dialog:error";
}
return retValue;
}
例19-19に、静的アクション結果を使用する「New user」のcommandLinkコンポーネントのコードを示します。
例19-19 「Login」ページの「New user」コマンド・リンク
<af:commandLink id="cmdLink"
text="New User?"
action="dialog:newAccount"
useWindow="true"
partialSubmit="true"
windowHeight="200"
windowWidth="500" />
action属性値はdialog:で始まる単なる静的結果文字列であり、アクション・メソッドを参照しません。
ADF Facesは、属性useWindow="true"を、dialog:で始まるアクション結果とともに使用して、ダイアログ・プロセスを開始してポップアップ・ダイアログ内にページを起動するかどうかを決定します(faces-config.xml内にdialog:ナビゲーション・ルールが定義されていることを前提とします)。
アクション結果がdialog:で始まっていない場合、useWindow="true"であっても、ADF Facesはプロセスの開始もポップアップ・ダイアログの起動も行いません。逆に、アクション結果がdialog:で始まっている場合、useWindow="false"であるか、useWindowが設定されていないとき、ADF Facesはポップアップ・ダイアログを起動しませんが、新しいプロセスを開始します。
クライアント・デバイスでポップアップ・ダイアログがサポートされていない場合、ADF Facesは、現在のページのすべての状態を保存した後、現在のウィンドウ内にダイアログ・ページを表示します。この処理を実現するためにコードを記述する必要はありません。
コマンド・コンポーネントは、ダイアログを起動するとき、起動イベント(LaunchEvent)を配信します。起動イベントは、ポップアップ・ダイアログを起動するコンポーネントと、ダイアログ・プロセスの開始時に表示するコンポーネント・ツリーのルートに関する情報を格納しています。起動イベントは、パラメータのマップをダイアログに渡すこともできます。詳細は、19.3.1.5項「ダイアログに値を渡す」を参照してください。
サンプル・アプリケーションのダイアログ・ページは、「Error」ページ、「New Account」ページおよび「Account Details」ページです。新規のユーザーのダイアログ・プロセスでは、実際に2つのページ(「New Account」ページと「Account Details」ページ)が表示されます。ユーザーがログインに失敗したときのダイアログ・プロセスでは、「Error」ページだけが表示されます。
ダイアログ・ページは他のJSFページとほとんど同じですが、1つだけ例外があります。ダイアログ・ページでは、ダイアログ・プロセスが終了したとき(つまり、ユーザーがダイアログを閉じたとき)にADF Facesに通知する方法を指定する必要があります。通常は、コマンド・コンポーネントを介して、プログラムまたは宣言により指定します。例19-20に、「Error」ページの「Cancel」ボタンを介して、プログラムによりこれを指定する方法を示します。
例19-20 「Error」ページの「Cancel」ボタン
<af:commandButton text="Cancel"
actionListener="#{backing_error.cancel}" />
commandButtonのactionListener属性は、ページのバッキングBean(Error.java)内のアクション・リスナー・メソッドへの参照を指定します。アクション・リスナー・メソッドは、「Cancel」ボタンをクリックしたときに生成されるアクション・イベントを処理します。このアクション・リスナー・メソッド内でAdfFacesContext.returnFromDialog()メソッドをコールします(例19-21を参照)。
例19-21 バッキングBean内の「Cancel」ボタンに対するアクション・リスナー・メソッド
public void cancel(ActionEvent actionEvent)
{
AdfFacesContext.getCurrentInstance().returnFromDialog(null, null);
}
|
注意: AdfFacesContext.returnFromDialog()メソッドはnullを返します。バッキングBean内で「Cancel」アクション・イベントを処理するのに必要な値はこれだけです。 |
「Account Details」ダイアログ・ページで宣言的に同じことを達成するには、af:returnActionListenerタグを「Cancel」ボタン・コンポーネントにアタッチします(例19-22を参照)。af:returnActionListenerタグは、AdfFacesContextのreturnFromDialogメソッドをコールします。バッキングBeanのコードは必要ありません。
例19-22 「Account Details」ページの「Cancel」ボタン
<af_commandButton text="Cancel" immediate="true">
<af:returnActionListener/>
</af:commandButton>
af:returnActionListenerタグでは、属性は使用されません。commandButtonのimmediate属性はtrueに設定します。これにより、ユーザーが必須の「Password」および「Confirm Password」フィールドに値を入力せずに「Cancel」をクリックした場合、アプリケーション起動フェーズではなくリクエスト値適用フェーズ中にデフォルトのJSF ActionListenerを実行して、入力の検証を省略することができます。
「New Account」ページと「Account Details」ページは、同じダイアログ・プロセスに属しています。ダイアログ・プロセスは必要なだけいくつでも指定できますが、AdfFacesContext.returnFromDialog()のコールは1回しか必要ありません。
プロセスを終了する場合とダイアログから値を返す場合に、同じaf:returnActionListenerタグまたはAdfFacesContext.returnFromDialog()メソッドを使用することもできます。たとえば、ユーザーが「Account Details」ページの「Done」をクリックした場合、プロセスは終了し、ユーザーの入力値が返されます。例19-23に、「Done」ボタンのコードを示します。
例19-23 「Account Details」ページの「Done」ボタン
<af:commandButton text="Done"
actionListener="#{backing_new_account.done}" />
commandButtonのactionListener属性は、ページのバッキングBean(New_account.java)内のアクション・リスナー・メソッドへの参照を指定します。アクション・リスナー・メソッドは、「Done」ボタンをクリックしたときに生成されるアクション・イベントを処理します。例19-24に、このアクション・リスナー・メソッドのコードを示します。ここでは、戻り値が取得されてから、AdfFacesContext.returnFromDialog()メソッドを介して戻されています。
例19-24 バッキングBean内の「Done」ボタンに対するアクション・リスナー・メソッド
public void done(ActionEvent e)
{
AdfFacesContext afContext = AdfFacesContext.getCurrentInstance();
String firstname = afContext.getProcessScope().get("firstname").toString();
String lastname = afContext.getProcessScope().get("lastname").toString();
String street = afContext.getProcessScope().get("street").toString();
String zipCode = afContext.getProcessScope().get("zipCode").toString();
String country = afContext.getProcessScope().get("country").toString();
String password = afContext.getProcessScope().get("password").toString();
String confirmPassword =
afContext.getProcessScope().get("confirmPassword").toString();
if (!password.equals(confirmPassword))
{
FacesMessage fm = new FacesMessage();
fm.setSummary("Confirm Password");
fm.setDetail("You've entered an incorrect password. Please verify that you've
entered a correct password!");
FacesContext.getCurrentInstance().addMessage(null, fm);
}
else
{
//Get the return value
Customer cst = new Customer();
cst.setFirstName(firstname);
cst.setLastName(lastname);
cst.setStreet(street);
cst.setPostalCode(zipCode);
cst.setCountry(country);
cst.setPassword(password);
// And return it
afContext.getCurrentInstance().returnFromDialog(cst, null);
afContext.getProcessScope().clear();
}
}
AdfFacesContext.returnFromDialog()メソッドを使用すると、パラメータのjava.lang.Objectまたはjava.util.Mapの形式で戻り値を返すことができます。値を返す先を知らなくてもかまいません。ADF Facesにより自動的に処理されます。
AdfFacesContext.returnFromDialog()メソッドは、ユーザーがダイアログを閉じるとADF Facesに通知します。このメソッドは、ダイアログ・ページがポップアップ・ダイアログに表示されているかメイン・ウィンドウに表示されているかに関係なく、コールできます。ポップアップ・ダイアログが使用されている場合、ADF Facesにより自動的にそのポップアップ・ダイアログが閉じられます。
サンプル・アプリケーションでは、ユーザーが「Error」ページまたは「Account Details」ページの「Cancel」ボタンをクリックした場合、ADF FacesによってAdfFacesContext.returnFromDialog()がコールされ(これによりnullが返される)、ポップアップ・ダイアログが閉じて元ページに戻ります。
新規のユーザーのダイアログ・プロセスの最初のページは、「New Account」ページです。「New Account」ページの「Details」ボタンがクリックされると、アプリケーションは「New Account」ページの状態を保存した後、(useWindow="false"であるため)同じポップアップ・ダイアログ内に「Account Details」ダイアログ・ページを表示します。
「Account Details」ページの「Done」ボタンがクリックされると、ADF Facesはポップアップ・ダイアログを閉じ、AdfFacesContext.returnFromDialog()は元ページにcstを返します。
ダイアログが閉じると、ADF Facesは戻りイベント(ReturnEvent)を生成します。AdfFacesContext.returnFromDialog()メソッドは、戻り値を戻りイベントのプロパティとして送信します。この戻りイベントは、ダイアログを起動したコマンド・コンポーネント(「Login」ページの「New user」commandLink)に登録されている戻りリスナー(ReturnListener)に配信されます。戻り値を処理する方法は、19.3.1.4項「戻り値の処理」を参照してください。
戻り値を処理するには、ダイアログを起動したコマンド・コンポーネント(サンプル・アプリケーションでは「Login」ページの「New user」リンク・コンポーネント)に戻りリスナーを登録します。例19-25に、「New user」リンク・コンポーネントのコードを示します。
例19-25 「Login」ページの「New user」コマンド・リンク
<af:commandLink id="cmdLink" text="New User?"
action="dialog:newAccount"
useWindow="true" partialSubmit="true"
returnListener="#{backing_login.handleReturn}"
windowHeight="200" windowWidth="500" />
commandLinkのreturnListener属性は、ページのバッキングBean(Login.java)内の戻りリスナー・メソッドへの参照を指定します。戻りリスナー・メソッドは、ダイアログが閉じられたときに生成される戻りイベントを処理します。例19-26に、戻り値を処理する戻りリスナー・メソッドのコードを示します。
例19-26 バッキングBean内の「New user」リンクに対する戻りリスナー・メソッド
public void handleReturn(ReturnEvent event)
{
if (event.getReturnValue() != null)
{
Customer cst;
String name;
String psw;
cst = (Customer)event.getReturnValue();
name = cst.getFirstName();
psw = cst.getPassword();
CustomerList.getCustomers().add(cst);
inputText1.setSubmittedValue(null);
inputText1.setValue(name);
inputText2.setSubmittedValue(null);
inputText2.setValue(psw);
}
}
getReturnValue()メソッドを使用して戻り値を取得します。戻り値は自動的にReturnEventのプロパティとして追加されるためです。
AdfFacesContext.returnFromDialog()メソッドを使用すると、ダイアログからの戻り値を返すことができます。値をダイアログに渡す必要がある場合もあります。値をダイアログに渡すには、起動リスナー(LaunchListener)を使用します。
サンプル・アプリケーションでは、新規のユーザーは「Login」ページの「Username」フィールドに名前を入力してから、「New user」リンクをクリックします。ポップアップ・ダイアログ内に「New Account」ダイアログ・ページが表示されると、「First Name」入力フィールドには、「Login」ページで入力された名前が自動的に移入されます。これを実現するには、ダイアログを起動したコマンド・コンポーネント(commandLink)に起動リスナーを登録します。例19-27に、commandLinkコンポーネントのコードを示します。
例19-27 「Login」ページの入力フィールドと「New user」コマンド・リンク
<af:inputText label="Username" value="#{backing_login.username}"/>
<af:commandLink id="cmdLink" text="New User?"
action="dialog:newAccount"
useWindow="true" partialSubmit="true"
launchListener="#{backing_login.handleLaunch}"
returnListener="#{backing_login.handleReturn}"
windowHeight="200" windowWidth="500" />
commandLinkのLaunchListener属性は、ページのバッキングBean(Login.java)内の起動リスナー・メソッドへの参照を指定します。起動リスナー・メソッド内で、getDialogParameters()メソッドを使用して、キーと値のペアを使用してパラメータをMapに追加します。例19-28に、起動リスナー・メソッドのコードを示します。
例19-28 バッキングBean内の「New user」コマンド・リンクに対する起動リスナー・メソッド
public void handleLaunch(LaunchEvent event)
{
//Pass the current value of the field into the dialog
Object usr = username;
event.getDialogParameters().put("firstname", usr);
}
// Use by inputText value binding
public String username;
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
「New Account」ダイアログ・ページにパラメータ値を表示するには、ADF FacesのprocessScopeを使用して、#{processScope.someKey}という書式の特殊なEL式を介してキーと値を取得します(例19-29を参照)。
例19-29 「New Account」ページの入力フィールド
<af:inputText label="First name" value="#{processScope.firstname}"/>
|
注意: processScopeは、ADF Facesコンポーネントだけでなく、すべてのJSFコンポーネントで使用できます。 |
(すべての条件が満たされていることを前提とすると、)コマンド・コンポーネントがダイアログを起動しようとすると、ADF Facesが起動イベントをキューに挿入します。このイベントは、ダイアログを起動するコンポーネントと、ダイアログ・プロセスの開始時に表示するコンポーネント・ツリーのルートに関する情報を格納しています。起動イベントに関連付けられている起動リスナーは、1つの引数として起動イベントを取り、必要に応じてイベントを処理します。
サンプル・アプリケーションでは、ADF FacesがcommandLinkコンポーネントに登録されている起動リスナーに起動イベントを配信すると、handleLaunch()メソッドがコールされ、それに応じてイベントが処理されます。
ADF Facesでは、プロセスは常に、ダイアログが起動されたページのprocessScope内に存在するすべての値のコピーを取得します。getDialogParameters()メソッドがパラメータをMapに追加すると、これらのパラメータもprocessScope内で使用可能になり、ダイアログ・プロセス内のすべてのページは、EL式を介してprocessScopeオブジェクトを参照することによりprocessScopeから値を取得できます。
sessionScopeとは異なり、processScopeの値は、現在のページ・フローまたはプロセスでのみ表示可能です。ユーザーが新しいウィンドウを開いてナビゲーションを開始すると、その一連のウィンドウは独自のプロセスを持ちます。つまり、各ウィンドウで格納された値は他からの独立性を保ちます。ブラウザの「Back」ボタンをクリックすると、自動的にprocessScopeは元の状態にリセットされます。プロセスから戻ると、processScopeはプロセス開始前の状態に戻ります。プロセスからの値を渡すには、AdfFacesContext.returnFromDialog()、sessionScopeまたはapplicationScopeを使用します。
SRDemoアプリケーションでは、次のような場合にポップアップ・ダイアログが使用されます。
よくある質問(FAQ)のリストの表示
未処理のサービス・リクエストに対する技術者の選定と割当て
「Create New Service Request」ページ(図19-13を参照)では、ユーザーが「Frequently Asked Questions」リンクをクリックすると、FAQリストが示されたポップアップ・ダイアログが表示されます。
「Edit Service Request」ページでは、ユーザーが「Assigned to」ラベルの横の懐中電灯アイコン(図19-12を参照)をクリックすると、「Search for Staff」ポップアップ・ダイアログが表示されます。このダイアログ(図19-9)では、ユーザーは最初にユーザー・ロールに基づいて検索します。次に、結果のセクションで名前の横のラジオ・ボタンをクリックして「Select」をクリックします。
選択が完了すると、ポップアップ・ダイアログが閉じ、アプリケーションは「Edit Service Request」ページに戻ります。このページでは、表示専用の「Assigned to」フィールドが更新され、選択された技術者のファースト・ネームとラスト・ネームが表示されています(図19-10)。外部キーAssignedToの変更時にフィールドは自動的に更新されるため、更新を支援するコードを作成する必要はありません。詳細は、7.3項「結合ビュー・オブジェクトへの参照エンティティの組込み」を参照してください。
繰り返して説明すると、ポップアップ・ダイアログをサポートするための操作は次のとおりです(順序は任意です)。
dialog:結果を持つJSFナビゲーション・ルールを作成します。
dialog:アクション結果を介してダイアログを起動するページを作成します。
ダイアログ・ページを作成し、値を返します。
戻り値を処理します。
最初に、ダイアログを起動するためのJSFナビゲーション・ルールを例19-30に示します。ダイアログ・ページSRStaffSearch.jspxを表示するためのナビゲーション・ケースは、dialog:StaffSearch結果によって定義されています。SRFaq.jspxダイアログ・ページを表示するためのナビゲーション・ケースは、dialog:FAQ結果によって定義されています。
例19-30 faces-config.xmlファイル内のダイアログ・ナビゲーション・ルール
<navigation-rule>
<from-view-id>/app/staff/SREdit.jspx</from-view-id>
...
<navigation-case>
<from-outcome>dialog:StaffSearch</from-outcome>
<to-view-id>/app/staff/SRStaffSearch.jspx</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<from-view-id>/app/SRCreate.jspx</from-view-id>
<navigation-case>
<from-outcome>dialog:FAQ</from-outcome>
<to-view-id>/app/SRFaq.jspx</to-view-id>
</navigation-case>
...
</navigation-rule>
2番目に、ポップアップ・ダイアログを起動するページはSREdit.jspxとSRCreate.jspxです。いずれのページでも、commandLinkコンポーネントのuseWindow属性をtrueに設定します。これは、ポップアップ・ダイアログを起動する必要があることをADF Facesに認識させるための前提条件です。
例19-31に、SRStaffSearch.jspxダイアログ・ページを起動する、ページのcommandLinkコンポーネントを示します。commandLinkコンポーネントは、静的アクション結果dialog:StaffSearchを持ちます。
例19-31 SRStaffSearchダイアログ・ページを起動するためのcommandLinkコンポーネント
<af:commandLink id="staffLOVLink" action="dialog:StaffSearch"
useWindow="true" immediate="true"
partialSubmit="true"
returnListener="#{backing_SREdit.handleStaffLOVReturn}"..>
<af:objectImage height="24" width="24"
source="/images/searchicon_enabled.gif"/>
</af:commandLink>
例19-32に、SRFaq.jspxダイアログ・ページを起動する、ページのcommandLinkコンポーネントを示します。commandLinkコンポーネントは、静的アクション結果dialog:SRFaqを持ちます。
例19-32 SRFaqダイアログ・ページを起動するためのcommandLinkコンポーネント
<af:commandLink action="dialog:FAQ"
text="#{res['srcreate.faqLink']}"
useWindow="true"
immediate="true"
partialSubmit="true"/>
3番目に、ダイアログ・ページSRStaffSearch.jspxおよびSRFaq.jspxは、AdfFacesContext.returnFromDialog()メソッドをコールして、ユーザーがダイアログを閉じたときにADF Facesに通知する必要があります。SRStaffSearch.jspxでは、tableコンポーネントとtableSelectOneコンポーネントをあわせて使用して、選択する名前を表示します。また、ユーザーが表内の名前に対応するラジオ・ボタンをクリックして「Select」 commandButtonをクリックすると、AdfFacesContext.returnFromDialog()メソッドがコールされます。「Select」ボタン・コンポーネントのaf:returnActionListenerタグは、returnFromDialog()メソッドをコールします。このメソッドは、ダイアログを閉じ、ダイアログからの戻り値を送信します。バッキングBeanコードは必要ありません。例19-33に、「Select」ボタン・コンポーネントのコードSnippetを示します。
例19-33 名前を選択してポップアップ・ダイアログを閉じるためのcommandButtonコンポーネント
<af:tableSelectOne>
<af:commandButton text="#{res['srstaffsearch.button.select']}">
<af:setActionListener from="#{row.UserId}"
to="#{bindings.AssignedTo.inputValue}"/>
<af:returnActionListener value="#{row.UserId}"/>
</af:commandButton>
</af:tableSelectOne>
SRFaq.jspxの場合と同様に、ダイアログの終了とAdfFacesContext.returnFromDialog()メソッドのコールにはcommandLinkコンポーネントが使用されています。af:returnActionListenerタグは、AdfFacesContextのreturnFromDialogメソッドをコールします。バッキングBeanのコードは必要ありません。例19-34に、commandLinkのコードSnippetを示します。ユーザーがSRFaq.jspxポップアップ・ダイアログを閉じると、ADF Facesはダイアログを閉じるだけです。ダイアログの戻り値は送信されないため、戻り値の処理は必要ありません。
例19-34 SRFaqポップアップ・ダイアログを閉じるためのcommandLinkコンポーネント
<af:commandLink text="#{res['srdemo.close']}">
<af:returnActionListener/>
</af:commandLink>
SRStaffSearchページ定義ファイルには、AssignedTo属性へのバインディングが含まれます。SRStaffSearchダイアログでは、ユーザーが名前用のラジオ・ボタンを選択して「Select」をクリックすると、ダイアログが閉じ、アプリケーションはSREditページに戻ります。この場合、「Assigned To」表示専用フィールドには選択した名前が移入されています。例19-33のように、「Select」 commandButtonコンポーネントには、選択したUserIdの値をAssignedTo属性の値に設定するよう構成されたaf:setActionListenerタグがあります。また、「Select」 commandButtonには、選択したUserIdの値を戻すaf:returnActionListenerタグもあります。SRStaffSearchダイアログを起動するcommandLinkコンポーネントにはアタッチされた戻りリスナーがありますが(例19-31を参照)、戻りハンドラ・コード(例19-35を参照)では戻り値は使用されません。戻りハンドラによって行われるのは、「Assigned On」日付フィールドでのバインディングの値のリフレッシュのみです。
ADF Facesダイアログ・フレームワークには、次の3つの制限があることがわかっています。
新しいポップアップ・ダイアログ内にダイアログ・ページを起動するためのナビゲーション・ルール内で</redirect>を使用することはサポートされていません。ただし、同じウィンドウ内にダイアログ・ページを起動するナビゲーション・ルール内で</redirect>を使用することはできます。
ポップアップ・ブロッカを検出できません。Webアプリケーションでポップアップ・ダイアログを使用する場合は、サイトのポップアップ・ブロックを無効にしておくようユーザーに指示してください。
ADF Facesの選択入力コンポーネント(selectInputTextやselectInputDateなど)にも、ダイアログ・サポートが組み込まれています。これらのコンポーネントは、ポップアップ・ダイアログ内でのページ起動と、戻りイベントの受信を自動的に処理します。たとえば、selectInputTextを使用してダイアログを起動する場合は、action属性をdialog:結果に設定し、ダイアログの幅と高さを指定するだけです。ユーザーがダイアログを閉じると、ダイアログからの戻り値が入力コンポーネントの新しい値として自動的に使用されます。ただしこの場合も、dialog:結果を持つJSFナビゲーション・ルールを定義し、ダイアログ・ページを作成し、アクション・イベントを処理するダイアログ・ページのバッキングBeanを作成する必要はあります。
アクション・イベントからポップアップ・ダイアログを起動する他にも、値変更イベントおよびポーリング・イベントからポップアップ・ダイアログを起動することもできます。たとえば、(JSFナビゲーション・ルールを使用せずに)プログラムによりダイアログを起動するには、値変更リスナー・メソッドまたはポーリング・リスナー・メソッド内でAdfFacesContext.launchDialog()メソッドを使用します。
フレームワークまたはコンポーネントを開発する場合は、カスタム・レンダラによってダイアログの起動と戻り値の処理が実行されるようにすることができます。または、LaunchEventおよびReturnEventイベントのサポートをカスタムActionSourceコンポーネントに追加することもできます。ダイアログを実装するときに使用できるDialogService APIの詳細は、ADF FacesのJavadocでoracle.adf.view.faces.context.DialogServiceを参照してください。また、カスタム・コンポーネントおよびカスタム・レンダラでのダイアログ・サポートの詳細は、ADF Facesの開発者ガイドを参照してください。
ADF Facesコンポーネントでは部分ページ・レンダリング(PPR)が使用されるため、ページ全体を再描画しなくても、ページの小さな領域をリフレッシュできます。PPRは、操作をより対話的にするためにページの一部のみを更新するAjaxスタイルのブラウザ・ユーザー・インタフェースと同じです。現在、PPRは次のブラウザでサポートされています。
Internet Explorer 5.5以上(Windows)
Mozilla 1.0/Netscape 7.0
これ以外のプラットフォームでは、ADF Facesにより自動的に全体ページ・レンダリングが使用されます。この両方のケースに対応するためにPPRを無効化したり、コードを記述する必要はありません。
ADF FacesコンポーネントにはPPRのサポートが組み込まれているため、PPRを有効化するための操作は、ほとんどの場合必要ありません。たとえば、SRSearch.jspxページのResultsセクションでは、ユーザーが検索結果のサマリー・ビューまたは詳細ビューを表示できるように、2つのshowDetailItemを持つshowOneTabコンポーネントを使用しています。図19-11は、「Summary View」が選択された状態の「Results」セクションを示しています。ユーザーが「Detail View」をクリックすると、「Results」タイトルの下のページ部分のみがリフレッシュされます。
ページの一部を自分で明示的にリフレッシュすることもできます。たとえば、出力コンポーネントによってユーザーが入力コンポーネントで選択または入力した内容を表示したり、コマンド・リンクやボタンによって別のコンポーネントを更新する場合などです。次の3つの主要なコンポーネント属性を使用して、部分ページ・レンダリングを有効化できます。
autoSubmit: 入力コンポーネント(inputTextやselectOneChoiceなど)または表選択コンポーネント(tableSelectOneなど)のautoSubmit属性をtrueに設定した場合、該当するアクション(値変更など)が実行されると、コンポーネントはそれ自身を囲んでいるフォームを自動的に送信します。PPRの場合、この属性は、送信に基づくイベントが起動されたときになんらかのロジックを実行するメソッドにバインドされたリスナー属性とあわせて使用することができます。
partialSubmit: コマンド・コンポーネントのpartialSubmit属性をtrueに設定した場合、ボタンまたはリンクがクリックされると、ページは部分的に送信されます。この属性は、ボタンまたはリンクがクリックされたときになんらかのロジックを実行するactionListenerメソッドとあわせて使用することができます。
partialTriggers: レンダリングされたすべてのコンポーネントはpartialTriggers属性をサポートします。この属性の値は、他のトリガー・コンポーネントの1つ以上のIDです。これらのトリガー・コンポーネントが(たとえば、自動送信または部分送信を介して)更新されると、ターゲット・コンポーネントも更新されます。
SRDemoアプリケーションのSREdit.jspxページは、部分ページ送信および部分トリガーを使用してPPRをサポートします。
図19-12は、未割当てのサービス・リクエストが表示されたSREdit.jspxページを示しています。ユーザーが懐中電灯アイコン(objectImageコンポーネントを持つcommandLinkコンポーネント)をクリックすると、ユーザーが名前を検索および選択できるポップアップ・ダイアログが表示されます。名前が選択されると、ポップアップ・ダイアログは閉じ、「Assigned to」表示専用フィールド(outputTextコンポーネント)および「Status」フィールドの下の日付フィールド(selectInputDateコンポーネント)が適切な値でリフレッシュされます(ただし、編集ページの他の部分はリフレッシュされません)。
コマンド・コンポーネントによって別のコンポーネントを部分的にリフレッシュする手順:
トリガー・コマンド・コンポーネントで、id属性を一意の値に設定し、partialSubmit属性をtrueに設定します。
トリガー・コマンド・コンポーネントが起動されたときに部分的にリフレッシュするターゲット・コンポーネントで、partialTriggers属性をコマンド・コンポーネントのIDに設定します。
|
ヒント: コンポーネントの一意IDは有効なXML名である必要があります。つまり、IDの最初の文字を数値または空白にすることはできません。また、JSFではID内にコロン(:)は使用できません。 |
例19-36に、PPRを示すために、SREdit.jspxページで使用されているコマンドと読取り専用出力コンポーネントのコードSnippetを示します。
例19-36 部分送信を介して部分ページ・レンダリングを有効化するためのコード
<af:panelLabelAndMessage label="#{res['sredit.assignedTo.label']}">
<af:panelHorizontal>
<af:outputText value="#{bindings.AssignedToFirstName.inputValue}"
partialTriggers="staffLOVLink"/>
<af:outputText value="#{bindings.AssignedToLastName.inputValue}"
partialTriggers="staffLOVLink"/>
<af:commandLink id="staffLOVLink" action="dialog:StaffSearch"
useWindow="true" immediate="true"
partialSubmit="true"
returnListener="#{backing_SREdit.handleStaffLOVReturn}"
partialTriggers="status"
disabled="#{bindings.Status.inputValue==2}">
<af:objectImage height="24" width="24"
source="/images/searchicon_enabled.gif"/>
</af:commandLink>
<f:facet name="separator">
<af:objectSpacer width="4" height="10"/>
</f:facet>
</af:panelHorizontal>
</af:panelLabelAndMessage>
|
ヒント: ターゲット・コンポーネントのpartialTriggers属性には、1つ以上のトリガー・コンポーネントのIDを含めることができます。複数のIDはスペースを使用して区切ります。 |
ADF Facesのコマンド・ボタンおよびコマンド・リンクは、部分イベントを生成できます。commandButtonまたはcommandLinkのpartialSubmit属性により、部分ページ送信を使用してアクションを実行するかどうかが決定されます。partialSubmitがtrueの場合、ADF Facesは部分ページ送信を介してアクションを実行します。このため、送信時にページ全体を再描画しなくても、コマンド・ボタンまたはコマンド・リンクを使用してページの一部を更新できます。デフォルトでは、partialSubmitはfalseです。これは、部分イベントに対して全体ページ・レンダリングが使用されるということです。また、クライアント・ブラウザまたはプラットフォームで部分ページ・レンダリングがサポートされていない場合、あるいは別のページにナビゲートする場合も、全体ページ・レンダリングが自動的に使用されます。
例では、「Assigned to」という表示専用outputTextコンポーネントのpartialTriggers属性が、commandLinkコンポーネントのIDに設定されています。commandLinkコンポーネントが部分イベントを起動した場合、(commandLinkから部分イベントをリスニングする)出力コンポーネントは、部分ページ・レンダリングを介して値をリフレッシュします。
ある特定の受注で一連のページにアクセスする必要がある場合は、processTrainおよびprocessChoiceBarコンポーネントを使用して複数ページ・プロセスを表示することを検討してください。SRDemoアプリケーションでは、SRCreate.jspxおよびSRCreateConfirm.jspxページで、ユーザーが新しいサービス・リクエストを作成できるようにprocessTrainおよびprocessChoiceBarコンポーネントを使用しています。
レンダリングされると、processTrainコンポーネントはそのプロセス内の合計ページ数と、ユーザーに現在表示されているページを表示します。ユーザーはこれらのページをナビゲーションできます。たとえば、図19-13は、サービス作成リクエスト・プロセスの最初のページを示しています。このページでは、ユーザーがリスト・ボックスから機器を1つ選択し、テキスト・ボックスに問題の説明を入力します。トレイン上のノードの数は、プロセス内の事前定義済ページの合計数を示しています。塗りつぶされているノードは、ユーザーが現在、プロセス内のそのページで作業していることを示します。プロセス内の次のページに移動するには、ノードの下にあるアクティブなテキスト・リンクをクリックします。
この章の図では、SRDemoスキンではなくOracleスキンを使用しています。
processChoiceBarコンポーネントは、プロセス内のページを選択するためのドロップダウン・メニューをレンダリングし、適用可能であれば、プロセス内を前後にナビゲーションするための1つ以上のボタンをレンダリングします。
サービス・リクエスト作成プロセスの最初のページでは、ユーザーが「Confirm」テキスト・リンクまたは「Continue」ボタンをクリックするか、ドロップダウン・メニューから「Confirm」を選択すると、プロセスの2番目のページ(図19-14)が表示されます。
2番目のページからは、ユーザーはトレイン上の「Basic Problem Details」をクリックするか、「Back」ボタンをクリックすることにより、あるいはドロップダウン・メニューから「Basic Problem Details」をクリックすることにより、問題の説明のページに戻ることができます。
完了後、ユーザーが「Submit Request」をクリックすると、「Request Submitted」ページが表示されます(図19-15)。
各ページ上にプロセス・トレインを表示するには、processTrainコンポーネントをプロセス・トレイン・モデルにバインドします。実行時、列モデルによりプロセス内の各ページのトレインが動的に作成されます。
プロセス・トレインを作成して使用する手順:
プロセス・トレイン・モデルを作成します。(19.5.1.1項「プロセス・トレイン・モデルの作成」を参照。)
トレイン上の各ノードのJSFページを作成します。(19.5.1.2項「各トレイン・ノードのJSFページの作成」を参照。)
ノードごとに、ナビゲーション・ケースを持つナビゲーション・ルールを作成します。(19.5.1.3項「JSFナビゲーション・ルールの作成」を参照。)
oracle.adf.view.faces.model.MenuModelクラスとoracle.adf.view.faces.model.ProcessMenuModelクラスを使用して、動的にプロセス・トレインを生成するプロセス・トレイン・モデルを作成します。MenuModelクラスは、メニュー・タブおよびメニュー・バーを作成する場合に使用されるメニュー・モデル・メカニズムと同じです(19.2.1項「動的ナビゲーション・メニューを作成する方法」を参照)。
プロセス・トレイン上のノードごとに、プロパティを取得および設定できるクラスを作成します。
トレイン上の各ノードには、label、viewIdおよびoutcomeプロパティが必要です。例19-37に、SRDemoアプリケーションで使用されているMenuItemクラスの一部を示します。
例19-37 プロセス・トレイン・ノードのMenuItem.java
package oracle.srdemo.view.menu;
public class MenuItem {
private String _label = null;
private String _outcome = null;
private String _viewId = null;
...
//extended security attributes
private boolean _readOnly = false;
private boolean _shown = true;
public void setLabel(String label) {
this._label = label;
}
public String getLabel() {
return _label;
}
// getter and setter methods for remaining attributes omitted
}
トレイン上のノードごとにマネージドBeanを構成します。インスタンス化時に設定を必要とするプロパティには値を指定します。
各Beanは、手順1で作成したクラスのインスタンスである必要があります。例19-38に、faces-config.xml内のプロセス・トレイン・ノードに対するマネージドBeanコードを示します。
例19-38 faces-config.xmlファイル内のプロセス・トレイン・ノードに対するマネージドBean
<!--First train node -->
<managed-bean>
<managed-bean-name>createTrain_Step1</managed-bean-name>
<managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>#{resources['srcreate.train.step1']}</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/app/SRCreate.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>GlobalCreate</value>
</managed-property>
</managed-bean>
<!-- Second train node-->
<managed-bean>
<managed-bean-name>createTrain_Step2</managed-bean-name>
<managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>#{resources['srcreate.train.step2']}</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/app/SRCreateConfirm.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>Continue</value>
</managed-property>
</managed-bean>
スコープをapplicationとして、リストのインスタンスであるマネージドBeanを構成します。
リスト・エントリは、手順2で作成したトレイン・ノードのマネージドBeanです。順序は、トレインに表示するとおりにします。例19-39に、プロセス・トレイン・リストを作成するためのマネージドBeanコードを示します。
例19-39 faces-config.xmlファイル内のプロセス・トレイン・リストに対するマネージドBean
<!-- create the list to pass to the train model -->
<managed-bean>
<managed-bean-name>createTrainNodes</managed-bean-name>
<managed-bean-class>java.util.ArrayList</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
<list-entries>
<value-class>oracle.srdemo.view.menu.MenuItem</value-class>
<value>#{createTrain_Step1}</value>
<value>#{createTrain_Step2}</value>
</list-entries>
</managed-bean>
ProcessMenuModelインスタンスの構成を容易にするためのクラスを作成します。このクラスは、少なくとも2つのプロパティ(viewIdPropertyおよびinstance)を持つ必要があります。
例19-40に、SRDemoアプリケーションで使用されているTrainModelAdapterクラスを示します。
例19-40 プロセス・トレイン・ノードを収容するためのTrainModelAdapter.java
package oracle.srdemo.view.menu;
import oracle.adf.view.faces.model.MenuModel;
import oracle.adf.view.faces.model.ProcessMenuModel;
...
public class TrainModelAdapter implements Serializable {
private String _propertyName = null;
private Object _instance = null;
private transient MenuModel _model = null;
private Object _maxPathKey = null;
public MenuModel getModel() throws IntrospectionException {
if (_model == null)
{
_model = new ProcessMenuModel(getInstance(),
getViewIdProperty(),
getMaxPathKey());
}
return _model;
}
public String getViewIdProperty() {
return _propertyName;
}
/**
* Sets the property to use to get at view id
* @param propertyName
*/
public void setViewIdProperty(String propertyName) {
_propertyName = propertyName;
_model = null;
}
public Object getInstance() {
return _instance;
}
/**
* Sets the treeModel
* @param instance must be something that can be converted into a TreeModel
*/
public void setInstance(Object instance) {
_instance = instance;
_model = null;
}
public Object getMaxPathKey()
{
return _maxPathKey;
}
public void setMaxPathKey(Object maxPathKey)
{
_maxPathKey = maxPathKey;
}
}
ProcessMenuModelを使用せずに独自のメニュー・モデルを記述する場合は、ProcessUtilsを使用して、ページ・アクセスを制御するためのPlusOneまたはMaxVisitedの動作を実装できます。これらのプロセス動作を使用してページ・アクセスを制御する方法の詳細は、19.5.1.1.1項「ページ・アクセスの制御について」を参照してください。
手順4で作成したクラスを参照するマネージドBeanを構成します。これは、processTrainコンポーネントがバインドされるBeanです。
このBeanはインスタンス化されたときに、instanceプロパティ値がトレイン・リストを作成する(手順3で構成した)マネージドBeanに設定されている必要があります。また、インスタンス化されたBeanでは、viewIdProperty値が、手順1で作成したBeanのviewIdプロパティに設定されている必要があります。例19-41に、プロセス・トレイン・モデルを作成するためのマネージドBeanコードを示します。
例19-41 faces-config.xmlファイル内のプロセス・トレイン・モデルに対するマネージドBean
<!-- create the train menu model -->
<managed-bean>
<managed-bean-name>createTrainMenuModel</managed-bean-name>
<managed-bean-class>
oracle.srdemo.view.menu.TrainModelAdapter</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
<managed-property>
<property-name>viewIdProperty</property-name>
<value>viewId</value>
</managed-property>
<managed-property>
<property-name>instance</property-name>
<value>#{createTrainNodes}</value>
</managed-property>
</managed-bean>
ユーザーに現在表示されているページに基づいて、ユーザーがアクセスできるページを制御する場合、ADF Facesにより提供される2つのプロセス・シナリオ(Max VisitedまたはPlus One)のいずれかを使用できます。
たとえば、プロセス・トレインに5つのページまたはノードがある場合に、ユーザーがページ1からページ4に順次ナビゲートしたとします。ページ4で、ユーザーはページ2に直接戻ります。この場合、使用したプロセス・シナリオによって、ユーザーが次に表示できるページが異なります。
Max Visitedプロセスでは、現在のページ2から、ユーザーは、ページ1に戻る、ページ3に進む、またはページ4にジャンプすることができます。つまり、Max Visitedプロセスでは、ユーザーは前のページに戻るか、またはすでに表示した最も遠いページまでの任意のページに進むことができます。ユーザーは、ページ2からページ5にジャンプすることはできません。ページ5はまだ表示されていないためです。
同じ状況を考えた場合、Plus Oneプロセスでは、ユーザーはページ3に進むか、ページ1に戻るしかできません。つまり、Plus Oneプロセスでは、ユーザーは前のページに戻るか、現在表示されているトレイン上のノードより1つ先のノードにしか進めません。ユーザーは、ページ4を以前に表示した場合でも、ページ4にはジャンプできません。
Max Visitedプロセスを使用する場合、faces-config.xml内でcreateTrainMenuModelマネージドBeanに対して次のコードSnippetのようなコードを追加します(例19-41を参照)。
<managed-property> <property-name>maxPathKey</property-name> <value>TRAIN_DEMO_MAX_PATH_KEY</value> </managed-property>
maxPathKey値がProcessMenuModelに渡されるため、ADF FacesはMax Visitedプロセスを使用します(例19-40を参照)。
faces-config.xmlにはmaxPathKey管理対象プロパティの設定がないため、maxPathKeyに対してnullが渡されます。このため、「Create New Service Request」プロセスではPlus Oneプロセスが使用されます。nullが渡された場合、ADF FacesはPlusOneプロセスを使用します。
これらのプロセス・シナリオは、processTrainコンポーネント内で使用されているコマンド・コンポーネントのimmediateおよびreadOnly属性にも影響を与えます。詳細は、19.5.1.2.1項「immediateおよびreadOnly属性について」を参照してください。
各トレイン・ノードは、それぞれ独自のページを持ちます。プロセス・トレインを表示するには、各ページでprocessTrainコンポーネントをプロセス・トレイン・モデルにバインドします(例19-42を参照)。
processTrainコンポーネントは通常、panelPageまたはpageコンポーネントのlocationファセットに挿入されます。メニュー・コンポーネントと同様に、processTrainコンポーネントは、1つのcommandMenuItemコンポーネントを受け入れるnodeStampファセットを持ちます。これは、トレイン・ノードの下に表示される実際のラベルと、そのラベルが起動されたときのナビゲーション結果を提供するcommandMenuItemコンポーネントです。
例19-42 SRCreate.jspxファイル内のprocessTrainコンポーネント
<af:panelPage..>
...
<f:facet name="location">
<af:processTrain var="train"
value="#{createTrainMenuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{train.label}"
action="#{train.getOutcome}"
readOnly="#{createTrainMenuModel.model.readOnly}"
immediate="false"/>
</f:facet>
</af:processTrain>
</f:facet>
...
</af:panelPage>
|
注意: プロセス・トレイン・モデルはトレイン・ノード・リンク、ノードの順序、およびノードを有効にするか、無効にするか、あるいは選択状態にするかを動的に決定するため、各ページのプロセス・トレインには同じコードを使用できます。 |
通常、processTrainコンポーネントはprocessChoiceBarコンポーネントとあわせて使用します。processChoiceBarコンポーネントも、同じプロセス・トレイン・モデルにバインドされるコンポーネントですが、複数ページ・プロセスを順に実行していくためのユーザーのナビゲーション選択項目を追加します。例19-43に、SRCreate.jspxページ内のprocessChoiceBarコンポーネントのコードを示します。processChoiceBarコンポーネントは通常、panelPageまたはpageコンポーネントのactionsファセットに挿入されます。
例19-43 SRCreate.jspxファイル内のprocessChoiceBarコンポーネント
<af:panelPage ..>
<f:facet name="actions">
<af:panelButtonBar>
<af:commandButton text="#{res['srdemo.cancel']}"
action="GlobalHome"
actionListener=
"#{bindings.cancelNewServiceRequest.exec}"
immediate="true"/>
<af:processChoiceBar var="choice"
value="#{createTrainMenuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{choice.label}"
action="#{choice.getOutcome}"
readOnly="#{createTrainMenuModel.model.readOnly}"
immediate="false"/>
</f:facet>
</af:processChoiceBar>
</af:panelButtonBar>
</f:facet>
...
</af:panelPage>
図19-13および図19-14に示されているように、processChoiceBarコンポーネントは、プロセス内を前後にナビゲートするための「Continue」ボタンおよび「Back」ボタンを自動的に提供します。これらのボタンに対するコードを記述する必要はありません。追加のボタン(図19-14の「Cancel」ボタンと「Submit Request」ボタンなど)を提供する場合は、panelButtonBarを使用して、ボタン・コンポーネントおよびprocessChoiceBarコンポーネントをレイアウトします。
|
注意: 2つのページのみで構成される複数ページ・プロセスの場合、ADF Facesは、進むボタンのラベルとして「Continue」を使用します。プロセス内に3ページ以上ある場合、進むボタンのラベルは「Next」です。 |
ADF Facesにより提供される2つのプロセス(19.5.1.1.1項「ページ・アクセスの制御について」を参照)は、processTrain内で使用されているcommandMenuItemコンポーネントのimmediate属性とreadOnly属性の両方に影響を与えます。processTrainをプロセス・トレイン・モデルにバインドする場合、ノードのimmediateまたはreadOnly属性をモデルのimmediateまたはreadOnly属性にバインドできます。この場合、ProcessMenuModelクラスはロジックを使用して、immediateまたはreadOnly属性の値を決定します。
現在のページのデータを検証する必要がない場合は、immediate属性をtrueに設定する必要があります。たとえば、Plus Oneシナリオ(19.5.1.1.1項を参照)では、ユーザーがページ4を表示してからページ2に戻った場合、ページ1またはページ3を表示するときにデータを検証しなくてもよいように、ユーザーは後で再びページ4に戻る必要があります。しかし、ページ5に進むときにはデータを検証する必要があります。
ProcessMenuModelクラスは、次のロジックを使用してimmediate属性の値を決定します。
Plus One: immediateは、前のステップに対してはtrueに設定され、それ以外に対してはfalseに設定されます。
Max Visited: 現在のページと、表示した最大ページが同じである場合、動作はPlus Oneシナリオと同じです。現在のページが最大ページよりも前にある場合、immediateはfalseに設定されます。
readOnly属性がtrueに設定されるのは、現在のページからプロセスのそのページを表示できない場合のみです。ProcessMenuModelクラスは、次のロジックを使用してreadOnly属性の値を決定します。
Plus One: 次に表示可能なページよりも後のすべてのページに対して、readOnlyはtrueになります。
Max Visited: 現在のステップと、表示した最大ページが同じである場合、動作はPlus Oneシナリオと同じです。現在のページが、表示した最大ページよりも前にある場合、最大ページよりも後のすべてのページに対してreadOnlyはtrueに設定されます。
ナビゲーション・ケース内の<from-outcome>および<to-view-id>の値は、プロセス・トレイン・モデル内に設定したプロパティと一致している必要があります。
SRDemoアプリケーションでは、「Create New Service Request」プロセスの最初のページでグローバル・ナビゲーション・ルールを使用しています。これは、SRCreate.jspxページにはアプリケーション内のすべてのページからアクセスできるためです。プロセスの2ページ目のSRCreateConfirm.jspxは、SRCreate.jspxページからしかアクセスできないため、グローバル・ナビゲーション・ルールには含められていません。例19-44に、プロセスのナビゲーション・ルールとナビゲーション・ケースを示します。
例19-44 faces.config.xmlファイル内のプロセス・トレイン・ノードに対するナビゲーション・ルール
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>GlobalCreate</from-outcome>
<to-view-id>/app/SRCreate.jspx</to-view-id>
</navigation-case>
...
</navigation-rule>
...
<navigation-rule>
<from-view-id>/app/SRCreate.jspx</from-view-id>
<navigation-case>
<from-outcome>Continue</from-outcome>
<to-view-id>/app/SRCreateConfirm.jspx</to-view-id>
</navigation-case>
...
</navigation-rule>
<navigation-rule>
<from-view-id>/app/SRCreateConfirm.jspx</from-view-id>
<navigation-case>
<from-outcome>Back</from-outcome>
<to-view-id>/app/SRCreate.jspx</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>Complete</from-outcome>
<to-view-id>/app/SRCreateDone.jspx</to-view-id>
</navigation-case>
</navigation-rule>
TrainModelAdapterクラスがマネージドBeanとして使用されるため、Javaは自動的に、引数のないコンストラクタをTrainModelAdapterに追加します。TrainModelAdapterは、createTrainMenuModelマネージドBeanを介してプロセス・トレイン・モデル(ProcessMenuModelインスタンス)を構築します。createTrainNodesマネージドBeanは、トレイン・ノード・リストを作成してトレイン・モデル内に挿入します。このトレイン・モデルによって、プロセスを順に実行するとトレイン上のノードが適切に強調表示および有効化されます。
個々のトレイン・ノード・マネージドBean(たとえば、createTrain_Step1)は、トレイン・モデルがトレイン・ノードを動的に生成するときに使用するlabel、viewIdおよびoutcomeの値でインスタンス化されます。デフォルトのJSF actionListenerメカニズムでは、outcomeの値を使用してページ・ナビゲーションが処理されます。
SRDemoアプリケーションでは、個々のトレイン・ノード・マネージドBeanは、resourcesマネージドBeanを介してリソース・バンドル内のStringリソースにアクセスするため、実行時には適切なノード・ラベルが動的に取得されて表示されます。
実行時、maxPathKeyが(faces-config.xml内に設定されている)値を持つ場合、ADF FacesはMax Visitedプロセス・シナリオを使用します。(SRDemoアプリケーションの場合と同様に)maxPathKeyがnullの場合、ADF FacesはPlus Oneプロセスを使用して、現在のページからのページ・アクセスを制御します。
menuTabコンポーネントと同様に、processTrainおよびprocessChoiceBarコンポーネントは、1つのcommandMenuItemコンポーネントを取るnodeStampファセットを持ちます。trainを変数として使用し、processTrainコンポーネントをプロセス・トレイン・モデルにバインドすれば、commandMenuItemコンポーネントを1つ使用するだけで、すべてのトレイン・ノード項目を表示できます。これには、コマンド・コンポーネントでtext値として#{train.label}を使用し、action値として#{train.getOutcome}を使用します。また同様に、choiceを変数として使用し、processChoiceBarコンポーネントをプロセス・トレイン・モデルにバインドすれば、commandMenuItemコンポーネントを1つ使用するだけで、すべての項目をメニュー・オプションとして表示できます。これには、text値として#{choice.label}を使用し、action 値として#{choice.getOutcome}を使用します。
ノードの有効化および無効化は、MenuItemクラスではなく、現在のビューに基づくプロセス・トレイン・モデルによって制御されます。これには、processTrainまたはprocessChoiceBarコンポーネントのreadOnly属性にEL式#{createTrainMenuModel.model.readOnly}を使用します。
|
ヒント: ドロップダウン・メニュー内の無効化された項目をサポートしていないブラウザでは、無効化されたメニュー選択項目は表示されません。ドロップダウン・メニュー内の無効化されている項目をサポートするブラウザでは、使用できない項目は無効化された状態で表示されます。 |
ProcessMenuModelクラスは、動的メニューを作成する場合に使用されるViewIdPropertyMenuModelクラスを拡張します(19.2項「動的なナビゲーション・メニューの使用」を参照)。メニューやメニュー項目と同様に、トレイン上の各ノードはメニュー項目として定義されます。しかし、メニューの場合はメニュー項目が中間メニュー・ツリー・オブジェクト(MenuTreeModelAdapter)にまとめて挿入されますが、トレイン・ノードの全リストはArrayListにまとめて挿入されてから、TrainModelAdapterクラスに挿入されます。ただし、ViewIdPropertyMenuModelとProcessMenuModelはいずれも、リストを取得してそれを内部でツリーに変換することに注意してください。
SRDemoアプリケーションでは、どのユーザーでも新規のサービス・リクエストを作成できるため、トレイン上のノードはユーザー・ロールにより保護されていません。つまり、トレイン・モデルはapplicationスコープのマネージドBeanとして格納され、すべてのユーザーがこれを共有することができます。ユーザー・ロールによっては使用できないタブもあるため、メニュー・タブ項目はユーザー・ロールにより保護されています。このため、メニュー・モデルはsessionスコープのマネージドBeanとして格納されます。
新しいページをプロセス・トレインに追加するには、そのページの新しいマネージドBeanを構成し(例19-38)、新しいマネージドBeanをトレイン・リストに追加し(例19-39)、新しいページのナビゲーション・ケースを追加(例19-44)します。
ファイル・アップロードは、多くのWebアプリケーションで必要とされる機能です。サーブレット、JSP、JSF 1.1.xのような標準的なJ2EEテクノロジでは、ファイル・アップロードは直接にはサポートされていません。ただし、ADF Facesフレームワークでは、inputFileコンポーネントを介してコンポーネント・レベルでファイル・アップロードのサポートが統合されています。
ファイル・アップロード中、ADF Facesにより着信ファイルが一時的にメモリーまたはディスクに格納されます。デフォルトのディレクトリ格納場所と、1つのファイル・アップロード・リクエストで使用可能なディスク領域およびメモリーのデフォルト値を設定できます。
図19-16は、SRDemoアプリケーションのSRMain.jspxページを示しています。このページから、ユーザーは特定のサービス・リクエストに対するファイルをアップロードできます。
ユーザーが「Upload a document」をクリックすると、ポップアップ・ダイアログ内にアップロード・フォームが表示されます(図19-17)。
ユーザーはアップロードするファイルのフルパス名を入力するか、「Browse」をクリックしてファイルを見つけてから選択します。「Begin Upload」をクリックすると、ADF Facesにより、選択したファイルが自動的にアップロードされます。アップロードが正常に完了すると、ADF Facesによりアップロード・ファイルの情報が表示されます(図19-18)。なんらかの理由でアップロードに失敗すると、同じポップアップ・ダイアログ内にエラー・スタック・トレースが表示されます。
JSFアプリケーション内でファイル・アップロードをサポートするには、次の操作を実行します。
ADF Facesフィルタがインストールされていることを確認します。
ADF Facesフィルタは、ADF Facesが適切に初期化されるようにAdfFacesContextオブジェクトを設定するサーブレット・フィルタです。初めてJSFページにADF Facesコンポーネントを挿入すると、JDeveloperにより自動的にこのフィルタがweb.xmlにインストールされます。例19-45に、web.xml内のADF Facesフィルタおよびマッピング構成の設定を示します。
例19-45 web.xmlファイル内のADF Facesフィルタ
<!-- Installs the ADF Faces Filter -- > <filter> <filter-name>adfFaces</filter-name> <filter-class>oracle.adf.view.faces.webapp.AdfFacesFilter</filter-class> </filter> <!-- Adds the mapping to ADF Faces Filter -- > <filter-mapping> <filter-name>adfFaces</filter-name> <servlet-name>Faces Servlet</servlet-name> </filter-mapping>
web.xml内で、アップロード・ファイルの格納場所のコンテキスト初期化パラメータを設定します。アップロード・ファイルを保存する場所は、自由に指定できます。例19-46に、SRDemoアプリケーション内で使用されている、アップロード・ファイルのコンテキスト・パラメータを示します。
アップロード・ファイルを処理するためのバッキングBeanを作成します。例19-47に、faces-config.xml内のSRDemoのファイル・アップロード・ページに対するマネージドBeanコードを示します。
JSFページでは、ファイル・アップロードに対してaf:formまたはh:formを使用できます。ファイル・アップロードをサポートするには、次のコードSnippetに示すように、必ず外包フォームを設定してください。
<af:form usesUpload="true"/> .. <h:form enctype="multipart/form-data"/>
inputFileコンポーネントを使用して、ラベル付きの標準的な入力フィールドと「Browse」ボタンを提供します(図19-17を参照)。
inputFileコンポーネントにより、ファイルのアップロード時に標準的な値変更イベントが配信され、アップロード・コンテンツの処理が管理されます。コンテンツを処理する方法は、自由に指定できます。
ファイル・アップロードを処理するには、イベントを処理するための値変更リスナー・メソッドをバッキングBean内に実装するか、inputFileのvalue属性をタイプoracle.adf.view.faces.model.UploadedFileのマネージドBeanプロパティに直接バインドします。どちらの場合も、アップロード・ファイルを処理するための独自のJavaコードをバッキングBean内に記述する必要があります。
次のコードSnippetは、inputFileコンポーネントをタイプoracle.adf.view.faces.model.UploadedFileのマネージドBeanプロパティにバインドする場合のコードです。
<af:inputFile value="#{myuploadBean.myuploadedFile}".../>
SRDemoファイル・アップロード・フォームでは、値変更リスナー・メソッドを使用しています。例19-48に、inputFileコンポーネントのvalueChangeListener属性内のメソッド・バインディング式に対するコードを示します。
ページのバッキングBean内に、アップロード・コンテンツを処理するためのコードを記述します。たとえば、コンテンツをファイル・システム内のローカル・ディレクトリに書き込むことなどができます。例19-49に、SRDemoアプリケーションでのファイル・アップロードの値変更イベントを処理する値変更リスナー・メソッドを示します。
例19-49 ファイル・アップロード・イベントを処理するための値変更リスナー・メソッド
public void fileUploaded(ValueChangeEvent event) {
InputStream in;
FileOutputStream out;
// Set fileUPloadLoc to "SRDemo.FILE_UPLOADS_DIR" context init parameter
String fileUploadLoc =
FacesContext.getCurrentInstance().getExternalContext().
getInitParameter("SRDemo.FILE_UPLOADS_DIR");
if (fileUploadLoc == null) {
// Backup value if context init parameter not set.
fileUploadLoc = "/tmp/srdemo_fileuploads";
}
//get svrId and append to file upload location
DBSequence svrId =
(DBSequence)ADFUtils.getBoundAttributeValue("SvrId");
fileUploadLoc += "/sr_" + svrId + "_uploadedfiles";
// Create upload directory if it does not exists.
boolean exists = (new File(fileUploadLoc)).exists();
if (!exists) {
(new File(fileUploadLoc)).mkdirs();
}
UploadedFile file = (UploadedFile)event.getNewValue();
if (file != null && file.getLength() > 0) {
FacesContext context = FacesContext.getCurrentInstance();
FacesMessage message =
new FacesMessage(JSFUtils.getStringFromBundle("srmain.srfileupload.success") +
" " + file.getFilename() + " (" +
file.getLength() + " bytes)");
context.addMessage(event.getComponent().getClientId(context),
message);
try {
out = new FileOutputStream(fileUploadLoc + "/" + file.getFilename());
in = file.getInputStream();
for (int bytes = 0; bytes < file.getLength(); bytes++) {
out.write(in.read());
}
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
} else {
// need to check for null value here as otherwise closing
// the dialog after a failed upload attempt will lead to
// a nullpointer exception
String filename = file != null ? file.getFilename() : null;
String byteLength = file != null ? "" + file.getLength() : "0";
FacesContext context = FacesContext.getCurrentInstance();
FacesMessage message =
new FacesMessage(FacesMessage.SEVERITY_WARN, JSFUtils.getStringFromBundle("srmain.srfileupload.error") +
" " + filename + " (" + byteLength +
" bytes)", null);
context.addMessage(event.getComponent().getClientId(context),
message);
}
}
commandButtonコンポーネントを使用して、フォームを送信します。例19-50に、SRDemoファイル・アップロード・フォーム内のcommandButtonコードと、ページのバッキングBean内のアクション・メソッド・コードを示します。
例19-50 コマンド・ボタンおよびアクション・メソッドのコード
<af:commandButton text="#{res['srfileupload.uploadbutton']}"
action="#{backing_SRFileUpload.UploadButton_action}"/>
...
...
public String UploadButton_action() {
if (this.getSrInputFile().getValue() == null){
FacesContext context = FacesContext.getCurrentInstance();
FacesMessage message =
new FacesMessage(FacesMessage.SEVERITY_WARN, JSFUtils.getStringFromBundle("srmain.srfileupload.emptyfielderror"), null);
context.addMessage(this.getSrInputFile().getId(), message);
}
return null;
}
ポップアップ・ダイアログを使用する場合は、ユーザーがダイアログを閉じるためのcommandLinkコンポーネントを追加します。ポップアップ・ダイアログを閉じる方法の詳細は、19.3.1.3項「ダイアログ・ページを作成し、ダイアログ値を返す」を参照してください。例19-51に、ページのバッキングBean内のcommandLinkコンポーネントおよびアクション・メソッドを示します。
SRDemoアプリケーションにより、アップロード・ファイルを格納するためのディレクトリ(たとえば、C:\tmp\srdemo_fileuploads)が作成されます。サービス・リクエストに対するアップロード・ファイルは、サービス・リクエストIDから始まるサブディレクトリ(たとえば、C:\tmp\srdemo_fileuploads\sr_103_uploadedfiles)に格納されます。
oracle.adf.view.faces.webapp.UploadedFileProcessor APIにより、ファイル・アップロードの処理が行われます。各アプリケーションは、AdfFacesContextからアクセス可能なのUploadedFileProcessorインスタンスを1つずつ持ちます。
UploadedFileProcessorにより、着信リクエストからの各アップロード・ファイルが処理されます。着信ストリームはoracle.adf.view.faces.model.UploadedFileに変換され、現在のリクエストの継続中、コンテンツが使用可能になります。つまり、inputFileコンポーネントのvalue属性は、自動的にUploadedFileのインスタンスに設定されます。inputFileコンポーネントの値がタイプoracle.adf.view.faces.model.UploadedFileのマネージドBeanプロパティにバインドされている場合、ADF FacesはモデルにUploadedFileオブジェクトを設定します。
oracle.adf.view.faces.model.UploadedFile APIでは、1つのファイルのコンテンツが記述されています。これにより、ファイル名、MIMEタイプ、ファイル・サイズだけでなく、ファイルの実際のバイト・ストリームがわかります。UploadedFileは、ファイル・システム内にファイルとして格納することも、メモリーに格納することもできます。APIではその違いは表示されません。
ADF Facesでは、アップロード・ファイルでハード・ドライブやメモリーを一杯にしようとするDoS攻撃を未然に防ぐため、許容される着信リクエストのサイズが制限されています。デフォルトでは、1つのリクエストで最初の100KBがメモリーに格納されます。これが一杯になると、ディスク領域が使用されます。この場合も、デフォルトでは、すべてのファイルに対し、1つのリクエストで2,000KBのディスク領域に制限されます。デフォルトのディスク領域およびメモリーの制限に達すると、AdfFacesFilterによりEOFExceptionがスローされます。デフォルト値を変更するには、19.6.4項「ファイル・アップロードの初期化パラメータの構成」を参照してください。
ADF Facesのファイル・アップロードを使用する場合は、次の点を考慮してください。
ほとんどのアプリケーションでは、デフォルトのUploadedFileProcessorインスタンスを置き換える必要はありません。ただし、アプリケーションで非常に大きなファイルのアップロードをサポートする必要がある場合は、デフォルトのプロセッサをカスタムのUploadedFileProcessor実装で置き換えることもできます。詳細は、19.6.5項「カスタムのアップロード・ファイル・プロセッサの構成」を参照してください。
ADF Facesフィルタを使用すると、リクエストの完了後、確実にUploadedFileコンテンツがクリーン・アップされます。このため、複数のリクエストにわたってUploadedFileオブジェクトをキャッシュすることはできません。ファイルを保持する必要がある場合は、リクエストが完了する前にファイルを永続記憶域にコピーする必要があります。
ファイル・アップロード中、ADF Facesにより着信ファイルが一時的にディスクまたはメモリーに格納されます。デフォルトでは、javax.servlet.context.tempdirプロパティで指定されているアプリケーション・サーバーの一時ディレクトリに設定されます。そのプロパティが設定されていない場合は、システムのjava.io.tempdirプロパティが使用されます。
必要な場合、デフォルトの一時格納場所と、1つのファイル・アップロード・リクエストで使用可能なディスク領域およびメモリーのデフォルト値を設定できます。web.xml内で、次のファイル・アップロード・コンテキスト・パラメータを指定できます。
oracle.adf.view.faces.UPLOAD_TEMP_DIR: ファイル・アップロード中に一時ファイルを格納するディレクトリを指定します。デフォルトは、ユーザーの一時ディレクトリです。
oracle.adf.view.faces.UPLOAD_MAX_DISK_SPACE: 1回のリクエストでアップロード・ファイルを格納するために使用できる最大ディスク領域を指定します。デフォルトは2000Kです。
oracle.adf.view.faces.UPLOAD_MAX_MEMORY: 1回のリクエストでアップロード・ファイルを格納するために使用できる最大メモリー容量を指定します。デフォルトは100Kです。
例19-52に、web.xml内で使用するファイル・アップロードのコンテキスト初期化パラメータを示します。
例19-52 web.xmlファイル内のファイル・アップロードのコンテキスト・パラメータ
<context-param> <param-name>oracle.adf.view.faces.UPLOAD_TEMP_DIR</param-name> <param-value>/tmp/Adfuploads</param-value> </context-param> <context-param> <param-name>oracle.adf.view.faces.UPLOAD_MAX_DISK_SPACE</param-name> <param-value>10240000</param-value> </context-param> <context-param> <param-name>oracle.adf.view.faces.UPLOAD_MAX_MEMORY</param-name> <param-value>5120000</param-value> </context-param>
|
注意: ファイル・アップロードの初期化パラメータは、デフォルトのUploadedFileProcessorによってのみ処理されます。デフォルトのプロセッサをカスタムのUploadedFileProcessor実装で置き換えた場合、これらのパラメータは処理されません。 |
ほとんどのアプリケーションでは、ADF Facesにより提供されるデフォルトのUploadedFileProcessorインスタンスを置き換える必要はありません。ただし、アプリケーションで非常に大きなファイルのアップロードをサポートする必要がある場合や、アプリケーションがファイル・アップロードに大きく依存している場合、デフォルトのプロセッサをカスタムのUploadedFileProcessor実装で置き換えることもできます。たとえば、リクエスト中にADF Facesで一時記憶域を扱わず、ファイルを即時に最終宛先に格納するような実装を使用して、パフォーマンスの向上をはかることができます。
デフォルトのプロセッサを置き換えるには、adf-faces-config.xml内で<uploaded-file-processor>要素を使用してカスタム実装を指定します。例19-53に、カスタムのUploadedFileProcessor実装を登録するためのコードを示します。
例19-53 adf-faces-config.xmlファイル内のカスタムのアップロード・ファイル・プロセッサの登録
<adf-faces-config xmlns="http://xmlns.oracle.com/adf/view/faces/config">
...
<!-- Use my UploadFileProcessor class -->
<uploaded-file-processor>
com.mycompany.faces.myUploadedFileProcessor
</uploaded-file-processor>
...
</adf-faces-config>
|
ヒント: web.xml内に指定されたファイル・アップロードの初期化パラメータはすべて、デフォルトのUploadedFileProcessorによってのみ処理されます。デフォルトのプロセッサをカスタムのUploadedFileProcessor実装で置き換えた場合、ファイル・アップロード・パラメータは処理されません。 |
リストから単一値を選択するためのADF Facesのリスト・コンポーネントには、selectOneRadio、selectOneChoiceおよびselectOneListboxが含まれます。これらのコンポーネントは、標準的なJSFリスト・コンポーネントと同様に機能します。ただし、ADF Facesのリスト・コンポーネントには追加の機能(ラベルとメッセージの表示、自動フォーム送信、部分ページ・レンダリングのサポートなど)が備わっています。
SRDemoアプリケーションのSRStaffSearchページでは、ユーザーが検索を実行するスタッフ・ロールを選択できるように、selectOneRadioコンポーネントを使用しています。スタッフ・ロールのリストは、開発者によって用意された値の静的リストにバインドされます。SRCreateページでは、ユーザーが実行時に動的に移入される値リストから機器を選択できるように、selectOneListboxコンポーネントを使用しています。SRSkillsページでは、ユーザーがコレクション内のオブジェクト全体を横断できるように、selectOneChoiceコンポーネントを使用しています。これらすべての場合で、f:selectItemsタグを使用して、表示および選択するリスト項目を指定します。
SRStaffSearchページでは、ユーザーが検索を実行するスタッフ・ロールを選択できるように、selectOneRadioコンポーネントを使用しています。たとえば、ユーザーは、管理者または技術者ロールを持つスタッフ・メンバーに検索対象を限定できます。図19-19は、SRDemoアプリケーションの「Search for Staff」フォームを示しています。
「Search for Staff」フォームでは、ExecuteWithParams操作を介して一致するオブジェクトを検索するための名前付きバインド変数を持つビュー・オブジェクトからの問合せを使用します。パラメータを使用して検索を作成する方法の詳細は、18.4.1項「パラメータ付き検索フォームの作成方法」を参照してください。
SRStaffSearchページ定義ファイルでは、例19-54のように、変数StaffListByEmailNameRole_RoleはStaffListByEmailNameRoleビュー・オブジェクトのRole名前付きバインド変数にバインドされています。StaffListByEmailNameRoleビュー・オブジェクトは、この検索ページで使用されるバインド変数を追加するためにStaffListビュー・オブジェクトを拡張します。
イテレータStaffListByEmailNameRoleIteratorは、StaffListByEmailNameRoleコレクションを反復処理します。StaffListByEmailNameRoleIteratorは、ExecuteWithParamsアクションにも関連付けられています。このアクションは、アクションの起動方法およびアクションが取得するパラメータの詳細をカプセル化します。NamedData要素は、ExecuteWithParamsアクションに渡されるパラメータを示します。
ユーザーが「Role」ラジオ・ボタンを選択すると、SRStaffSearchページのselectOneRadioコンポーネントにより、選択した値が適切なページ定義変数に移入されます。
変数イテレータと変数の詳細は、12.5.2.2項「executables要素で定義されるバインディング・オブジェクト」を参照してください。
例19-54 SRStaffSearchページ定義ファイル
<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
version="10.1.3.35.83" id="app_staff_SRStaffSearchPageDef"
Package="oracle.srdemo.view.pageDefs" ...>
<parameters/>
<executables>
<iterator id="StaffListByEmailNameRoleIterator"
Binds="StaffListByEmailNameRole"
RangeSize="10" DataControl="SRService"/>
<variableIterator id="variables">
<variableUsage DataControl="SRService"
Binds="StaffListbyEmailNameRole.variablesMap.Role"
Name="StaffListByEmailNameRole_Role" IsQueriable="false"/>
...
<variableUsage DataControl="SRService"
Binds="StaffListByEmailNameRole.variablesMap.TheFirstName"
Name="StaffListByEmailNameRole_TheFirstName"
IsQueriable="false"/>
<variableUsage DataControl="SRService"
Binds="StaffListByEmailNameRole.variablesMap.TheLastName"
Name="StaffListByEmailNameRole_TheLastName"
IsQueriable="false"/>
</variableIterator>
...
</executables>
<bindings>
<action id="ExecuteWithParams" IterBinding="StaffListByEmailNameRoleIterator"
InstanceName="SRService.StaffListByEmailNameRole"
DataControl="SRService" RequiresUpdateModel="true"
Action="95">
<NamedData NDName="Role" NDType="java.lang.String"
NDValue="${bindings.StaffListByEmailNameRole_Role}"/>
...
<NamedData NDName="TheFirstName" NDType="java.lang.String"
NDValue="${bindings.StaffListByEmailNameRole_TheFirstName}"/>
<NamedData NDName="TheLastName" NDType="java.lang.String"
NDValue="${bindings.StaffListByEmailNameRole_TheLastName}"/>
</action>
...
<attributeValues id="TheFirstName" IterBinding="variables">
<AttrNames>
<Item Value="StaffListByEmailNameRole_TheFirstName"/>
</AttrNames>
</attributeValues>
<attributeValues id="TheLastName" IterBinding="variables">
<AttrNames>
<Item Value="StaffListByEmailNameRole_TheLastName"/>
</AttrNames>
</attributeValues>
...
</bindings>
</pageDefinition>
次の手順では、ExecuteWithParams操作を使用するパラメータ検索フォームがすでに作成されていることを前提としています。
固定の値リストにバインドされたリストを作成するための変数の使用方法:
JSFページをビジュアル・エディタで開きます。
必要な場合、選択リスト・コンポーネントをかわりに使用するために、Roleフィールド用として作成されているinputTextコンポーネントを削除します。
データ・コントロール・パレットで、「StaffListByEmailNameRole」→「Operations」→「ExecuteWithParams」→「Parameters」を拡張します。
StaffListByEmailNameRoleは、Role名前付きバインド変数が含まれるビュー・オブジェクトです。
Roleパラメータをページ上にドラッグ・アンド・ドロップし、ポップアップ・メニューから「作成」→「単一選択」→「ADFラジオ・ボタンを1つ選択」を選択します。「リスト・バインディング・エディタ」が表示されます(図19-20を参照)。
「リスト・バインディング・エディタ」で、「ベース・データソース」ドロップダウン・リストで「変数」が選択されていることを確認します。
「ベース・データソース」は、実行時にユーザーによって選択される値を受け取るターゲットです。
「固定リスト」ラジオ・ボタンを選択します。
「固定リスト」オプションを使用すると、ユーザーは、事前定義されたリストから値を選択できます。別のデータソースから値を取得せずに、自分でコーディングした値リストでデータ・オブジェクト属性を更新する場合は、このオプションを使用すると便利です。
「ベース・データソース属性」ドロップダウン・リストから、「StaffListByEmailNameRole_Role」を選択します。
「ベース・データソース属性」は、ターゲットの変数名です。
「一連の値」ボックスに次の値を入力します。[Enter]を押して値を設定してから、次の値を入力します。
Technician
Manager
値を入力する順序は、実行時にselectOneRadioコントロールにリスト項目が表示される順序です。
「リスト・アイテム」セクションで、「「選択なし」アイテム」ドロップダウン・リストから「ラベル付きアイテムを含める」を選択します。次に、その横のボックスにAnyと入力します。
selectOneRadioコンポーネントではnull値がサポートされています。このため、ユーザーが項目を選択しない場合、その項目のラベルがブランクとして表示され、コンポーネントの値がデフォルトで空の文字列に設定されます。ブランクまたは空の文字列を使用せずに、null値を表す文字列を指定できます。デフォルトでは、手順8で定義した値リストの先頭に新しい文字列が表示されます。
データ・コントロール・パレットからドラッグ・アンド・ドロップを行うと、JDeveloperによって様々な処理が自動的に行われます。データ・コントロール・パレットの使用時に実行される処理と作成される内容の詳細は、12.2.3項「データ・コントロール・パレットの使用時に行われる処理」を参照してください。
例19-55に、「リスト・バインディング・エディタ」を完了した後のselectOneRadioコンポーネントのコードを示します。
例19-55 バインディングを完了した後のSelectOneRadioコンポーネント
<af:selectOneRadio value="#{bindings.Role.inputValue}"
label="#{bindings.Role.label}: ">
<f:selectItems value="#{bindings.Role.items}"/>
</af:selectOneRadio>
選択項目のリストを提供するためのf:selectItemsタグは、バインディング・コンテナ内のRoleリスト・バインディング・オブジェクトのitemsプロパティにバインドされています。
JDeveloperにより、ページ定義ファイルのbindings要素内にRoleリスト・バインディング・オブジェクト定義が追加されます(例19-56を参照)。
例19-56 ページ定義ファイル内の固定リストに対するリスト・バインディング・オブジェクト
<bindings>
...
<list id="Role" IterBinding="variables" ListOperMode="0" StaticList="true"
NullValueFlag="1">
<AttrNames>
<Item Value="StaffListByEmailNameRole_Role"/>
</AttrNames>
<ValueList>
<Item Value="Any"/>
<Item Value="Technician"/>
<Item Value="Manager"/>
</ValueList>
</list>
...
</bindings>
list要素で、id属性は、リスト・バインディング・オブジェクトの名前を指定します。IterBinding属性は、現在の行が、バインディング・コンテナの各変数を表す属性の行である、変数イテレータを参照します。変数イテレータは、他のデータ・コレクションの場合と同じ方法で変数値をバインディングに公開します。AttrNames要素は、イテレータにより戻される属性値を指定します。ValueList要素は、選択可能な固定の値リストが表示されることを指定します。
実行時に、リストから値を選択すると、Roleリスト・バインディング・オブジェクトのターゲットであるベース・データソース属性StaffListByEmailNameRole_Roleが選択した値に更新されます。
ページ定義ファイルおよびADFデータ・バインディング式の詳細は、12.5項「ページ定義ファイルでの作業」および12.6項「ADFデータ・バインディングEL式の作成」を参照してください。
静的リストから値を取得せずに、実行時に選択リスト・コンポーネントに動的に値を移入することもできます。動的リストにバインドされたリスト・コンポーネントの作成手順は、固定リストにバインドされたリスト・コンポーネントの作成手順とほとんど同じです。違う点は、2つのデータソース(動的な値リストを提供するリスト・データソース用のデータソースと、ユーザーの選択に基づいて更新されるベース・データソース用のデータソース)を定義することです。
図19-21のようにSRCreateページでは、ユーザーが動的に移入される製品リストから機器を選択できるように、selectOneListboxコンポーネントを使用しています。ユーザーにリストから機器(製品)を選択させるために、リスト・データソースはProductListコレクションです。選択した値を使用して更新するベース・データソースは、Globalsコレクションです。Globalsビュー・オブジェクトは一時ビュー・オブジェクトです。つまり、一時属性のみが含まれるビュー・オブジェクトです。一時ビュー・オブジェクトは、アプリケーションの様々なページに表示する必要がある一時値をデータ・モデルで保持する場合に有用です。
動的な値リストにバインドされたリスト・ボックスの作成方法:
JSFページをビジュアル・エディタで開きます。
データ・コントロール・パレットから、ベース・データソースの属性をページにドラッグ・アンド・ドロップします。ポップアップ・メニューから「作成」→「単一選択」→「ADFリストボックスを1つ選択」を選択します。「リスト・バインディング・エディタ」が表示されます(図19-22を参照)。
たとえば、Globalsコレクションを拡張し、ProductId属性をページにドラッグ・アンド・ドロップできます。
「リスト・バインディング・エディタ」で、「ベース・データソース」ドロップダウン・リストは、ドラッグした属性のコレクションにデフォルト設定されます(SRService.Globalsなど)。
「ベース・データソース」は、実行時にユーザーが選択したリスト値で更新されるソースです。
「動的リスト」ラジオ・ボタンを選択します。
「動的リスト」オプションを使用すると、別のバインド値セットから更新される1つ以上のベース・データソース属性を指定できます。
「リスト・データソース」ドロップダウン・リストの横にある「追加」をクリックし、リスト・データソースを選択します。「データソースの追加」ダイアログで、動的にリスト値を提供するコレクションを選択します。たとえば、「データソースの追加」ダイアログで、SRServiceを拡張し、ProductListを選択します。選択したコレクションのデフォルトのイテレータ名のままにします。
「リスト・データソース」は、実行時にリスト・ボックスに表示されるリスト値を提供するソースです。
|
注意: リスト・コレクションとベース・コレクションはマスター/ディテール関係になっている必要はありませんが、リスト・コレクション内の項目のタイプはベース・コレクション属性と同じである必要があります。 |
手順2でGlobalsのProductId属性をドラッグし、手順5でProductListコレクションを選択した場合、マッピング領域では、デフォルトのベース・データソース属性はProductId、デフォルトのリスト・データソース属性はProdIdになります。このマッピングにより、更新するベース・ソース属性に対するリスト・ソース属性が指定されます。必要に応じて、「追加」をクリックし、ドロップダウン・リストを使用して別のマッピングを追加します。
たとえば、「ベース・データソース属性」ドロップダウン・リストから「ProductName」を選択し、「リスト・データソース属性」から「Name」を選択します。
「リスト・アイテム」セクションで、「属性の表示」ドロップダウン・リストから目的のリスト・データソース属性を選択します。このリスト・データソース属性により、実行時に表示されるリスト値が移入されます。
たとえば、ユーザーにリストから製品名を選択させる場合、「属性の表示」ドロップダウン・リストから「Name」を選択します。
データ・コントロール・パレットからドラッグ・アンド・ドロップを行うと、JDeveloperによって様々な処理が自動的に行われます。データ・コントロール・パレットの使用時に実行される処理と作成される内容の詳細は、12.2.3項「データ・コントロール・パレットの使用時に行われる処理」を参照してください。
例19-57に、「リスト・バインディング・エディタ」を完了した後のselectOneListboxコンポーネントのコードを示します。
例19-57 バインディングを完了した後のSelectOneListboxコンポーネント
<af:selectOneListbox value="#{bindings.GlobalsProductId.inputValue}"
label="#{bindings.GlobalsProductId.label}">
<f:selectItems value="#{bindings.GlobalsProductId.items}"/>
</af:selectOneListbox>
選択項目のリストを提供するためのf:selectItemsタグは、バインディング・コンテナ内のGlobalsProductIdリスト・バインディング・オブジェクトのitemsプロパティにバインドされています。ADFデータ・バインディング式の詳細は、12.6項「ADFデータ・バインディングEL式の作成」を参照してください。
JDeveloperにより、ページ定義ファイルのexecutables要素内にイテレータ・バインディング・オブジェクトの定義が、bindings要素内にリスト・バインディング・オブジェクトの定義が追加されます(例19-58を参照)。
例19-58 ページ定義ファイル内の動的リストに対するリスト・バインディング・オブジェクト
<executables>
...
<iterator id="ProductListIterator" Binds="ProductList" RangeSize="-1"
DataControl="SRService"/>
<iterator id="GlobalsIterator" RangeSize="10" Binds="Globals"
DataControl="SRService"/>
</executables>
<bindings>
...
<list id="GlobalsProductId" IterBinding="GlobalsIterator" StaticList="false"
ListOperMode="0" ListIter="ProductListIterator">
<AttrNames>
<Item Value="ProductId"/>
<Item Value="ProductName"/>
</AttrNames>
<ListAttrNames>
<Item Value="ProdId"/>
<Item Value="Name"/>
</ListAttrNames>
<ListDisplayAttrNames>
<Item Value="Name"/>
</ListDisplayAttrNames>
</list>
...
</bindings>
デフォルトでは、ProductListイテレータ・バインディングのiterator要素のRangeSize属性が値-1に設定されるため、イテレータは選択対象として有効な製品の完全なリストを提供できます。list要素で、id属性は、リスト・バインディング・オブジェクトの名前を指定します。IterBinding属性は、Globalsコレクションを反復処理するイテレータを参照します。ListIter属性は、ProductListコレクションを反復処理するイテレータを参照します。AttrNames要素は、ベース・イテレータにより戻されるベース・データソース属性を指定します。ListAttrNames要素は、ベース・データソース属性にマッピングされるリスト・データソース属性を定義します。ListDisplayAttrNames要素は、実行時にユーザーに表示される値をリストに移入するリスト・データソース属性を指定します。
ページ定義ファイルの詳細は、12.5項「ページ定義ファイルでの作業」を参照してください。
SRSkillsページには、ナビゲーション・リスト・バインディングを使用するselectOneChoiceコンポーネントがあります。ナビゲーション・リスト・バインディングを使用して、ユーザーがコレクション内のオブジェクトを移動できるようにします。ユーザーがナビゲーション・リスト・コンポーネントを使用して現在のオブジェクト選択を変更すると、属性を介して同じコレクションにバインドされている他のコンポーネントも、新しく選択したオブジェクトから表示されます。また、現在の行を変更するコレクションがデータ・モデルのマスター/ディテール関係のマスター・ビュー・オブジェクト・インスタンスである場合、ディテール・ビュー・オブジェクト・インスタンスの行セットが自動的に更新され、新しい現在のマスター行に適したデータが表示されます。後半の図19-26のように、管理者がドロップダウン・リストから技術者名を選択すると、ナビゲーション・リスト・コンポーネントの下にあるシャトル・コンポーネントが更新され、選択した技術者の製品スキルが表示されます。
技術者名を選択するためのドロップダウン・リストは、StaffListコレクションから導出されます。このコレクションには、ビュー・リンクExpertiseAreasForStaffMemberを介して作成されるディテール・コレクションStaffExpertiseAreasがあります。
ナビゲーション・リスト・バインディングを使用するリストの作成方法:
データ・コントロール・パレットで、「SRService」を拡張します。目的のコレクションをページ上にドラッグ・アンド・ドロップし、ポップアップ・メニューから「作成」→「移動」→「ADFナビゲーション・リスト」を選択します。「リスト・バインディング・エディタ」が表示されます(図19-23を参照)。
たとえば、ユーザーが技術者名を検索して選択できるようにするために、StaffListをドロップできます。
「属性の表示」セクションで、リストに表示しないStaffListコレクションの属性をダブルクリックし、これらを「使用可能な属性」セクションに移動します。
「イテレータの選択」ドロップダウン・リストに、コレクションのデフォルトのイテレータ名が表示されます。デフォルトのままにするか、「新規」をクリックしてイテレータの新しい名前を作成します。
データ・コントロール・パレットからドラッグ・アンド・ドロップを行うと、JDeveloperによって様々な処理が自動的に行われます。データ・コントロール・パレットの使用時に実行される処理と作成される内容の詳細は、12.2.3項「データ・コントロール・パレットの使用時に行われる処理」を参照してください。
ナビゲーション・リスト・バインディングを使用する場合、デフォルトでは、selectOneChoiceコンポーネントを使用してリストが作成されます。
例19-59に、「リスト・バインディング・エディタ」を完了した後のselectOneChoiceコンポーネントのコードを示します。
例19-59 バインディングを完了した後のselectOneChoiceコンポーネント
<af:selectOneChoice id="navList1" autoSubmit="true"
value="#{bindings.StaffList.inputValue}"
label="#{bindings.StaffList.label}"
<f:selectItems value="#{bindings.StaffList.items}"/>
</af:selectOneChoice>
f:selectItemsタグは、移動および選択対象の項目リストを提供します。JDeveloperにより、ページ定義ファイルのbindings要素内にリスト・バインディング・オブジェクトが、executables要素内にイテレータ・バインディング・オブジェクトが追加されます(例19-60を参照)。
例19-60 ページ定義ファイル内のナビゲーション・リストに対するリスト・バインディング・オブジェクト
<executables>
<iterator id="StaffListIterator" Binds="StaffList" RangeSize="10"
DataControl="SRService"/>
...
</executables>
<bindings>
<list StaticList="false" ListOperMode="1" id="StaffList"
IterBinding="StaffListIterator">
<AttrNames>
<Item Value="FirstName"/>
<Item Value="LastName"/>
</AttrNames>
</list>
...
</bindings>
list要素で、id属性は、リスト・バインディング・オブジェクトの名前を指定します。IterBinding属性は、StaffListコレクションを反復処理するイテレータを参照します。AttrNames要素は、イテレータにより戻される属性を指定します。
ページ定義ファイルおよびADFデータ・バインディング式の詳細は、12.5項「ページ定義ファイルでの作業」および12.6項「ADFデータ・バインディングEL式の作成」を参照してください。
selectManyShuttleおよびselectOrderShuttleコンポーネントは、2つのリスト・ボックスと、ユーザーが先行(選択可能)リスト・ボックスから複数の項目を選択して項目を後続(選択済)リスト・ボックスに移動または往復させる(またはその逆を行う)ことのできるボタンをレンダリングします。図19-24は、レンダリングされたselectManyShuttleコンポーネントの例を示しています。リスト・ボックスの上に表示されるヘッダーには任意のテキストを指定できます。
|
注意: 項目をリスト間で往復させるには、提供された「Move」および「Remove」ボタンを使用する以外に、いずれかのリストで項目をダブルクリックする方法もあります。一方のリストで項目をダブルクリックすると、その項目はもう1つのリストに移動します。たとえば、先行リスト内の項目をダブルクリックすると、その項目は自動的に後続リストに移動します。その逆も同様です。 |
selectManyShuttleとselectOrderShuttleで違う点は、selectOrderShuttleコンポーネントでは、ユーザーは横の上下矢印を使用して後続リスト・ボックス内の項目を並替えできることのみです(図19-25を参照)。
SRDemoアプリケーションでは、SRSkillsページで、管理者が技術者に製品スキルを割り当てることができるようにselectManyShuttleコンポーネントを使用しています。図19-26は、サンプル・アプリケーションで作成されるSRSkillsページを示しています。左側の先行リスト・ボックスには「washing machine」や「dryer」などの製品が表示され、右側の後続リスト・ボックスには技術者に保守スキルがある製品が表示されます。
SRSkillsページにアクセスできるのは、管理者ロールを持つユーザーのみです。製品スキルの割当てを確認して変更するには、管理者がまず、シャトル・コンポーネントの上のドロップダウン・リストから技術者の名前を選択します。すると、アプリケーションにより、後続リスト(「Assigned Skills」)内にその技術者の既存のスキル割当てが表示されます。
選択した技術者の新しいスキル割当てを追加するには、管理者が先行リスト(「Available Products」)から製品を選択し、「Move」ボタンをクリックします。
「Assigned Skills」リストからスキルを削除するには、管理者が後続リストから製品を選択し、「Remove」ボタンをクリックします。
先行リストと後続リストの下に、製品の説明を表示するためのオプションのボックスがあります。製品の説明を表示するには、管理者がいずれかのリスト・ボックスから項目を選択します。するとアプリケーションにより、図19-27のように、そのリストの下のボックスに製品の説明が表示されます。
他のADF Faces選択リスト・コンポーネントと同様に、selectManyShuttleコンポーネントはf:selectItemsタグを使用して、先行リスト内で表示および選択するための使用可能な項目のリストを提供できます。
f:selectItemsタグをバインドする前に、シャトルを必要とするページによって使用される汎用クラスを作成します。このクラスで、使用可能なすべての選択肢(先行リストまたは使用可能な製品)のリストおよび選択された選択肢(後続リストまたは割り当てられたスキル)のリスト用として使用するビュー・オブジェクト・インスタンス名を記述するプロパティの、getterメソッドおよびsetterメソッドを宣言して組み込みます。例19-61に、SRSkillsページでシャトル・コンポーネントの移入および選択の状態を管理するために作成されたShuttlePageBackingBeanBaseクラスを示します。
例19-61 ShuttlePageBackingBeanBaseクラス
package oracle.srdemo.view.util;
import java.util.List;
import javax.faces.event.ValueChangeEvent;
public class ShuttlePageBackingBeanBase {
String allItemsIteratorName;
String allItemsValueAttrName;
String allItemsDisplayAttrName;
String allItemsDescriptionAttrName;
String selectedValuesIteratorName;
String selectedValuesValueAttrName;
List selectedValues;
List allItems;
private boolean refreshSelectedList = false;
public void setAllItemsIteratorName(String allItemsIteratorName) {
this.allItemsIteratorName = allItemsIteratorName;
}
public String getAllItemsIteratorName() {
return allItemsIteratorName;
}
// other getter and setter methods are omitted
public void setSelectedValues(List selectedValues) {
this.selectedValues = selectedValues;
}
public void refreshSelectedList(ValueChangeEvent event) {
refreshSelectedList = true;
}
public List getSelectedValues() {
if (selectedValues == null || refreshSelectedList) {
selectedValues = ADFUtils.
attributeListForIterator(selectedValuesIteratorName,
selectedValuesValueAttrName);
}
return selectedValues;
}
public void setAllItems(List allItems) {
this.allItems = allItems;
}
public List getAllItems() {
if (allItems == null) {
allItems = ADFUtils.selectItemsForIterator(allItemsIteratorName,
allItemsValueAttrName,
allItemsDisplayAttrName,
allItemsDescriptionAttrName);
}
return allItems;
}
}
getAllItems()メソッドは、シャトルの先行リストを移入します。getSelectedValues()メソッドもListを戻しますが、このリストはシャトルの後続リスト内の項目を定義します。ShuttlePageBackingBeanBaseクラスは、ADFUtilsクラス内の複数のユーティリティ・メソッドをコールしています。
ShuttlePageBackingBeanBaseクラスを拡張するSRSkillsページのバッキングBean(backing_SRSkills)には、ベースBeanの複数のプロパティ値が挿入されます。例19-62に、シャトル・コンポーネントを操作するためにfaces-config.xml内で構成されているマネージドBeanおよび管理プロパティを示します。
例19-62 faces-config.xmlファイル内のシャトル・コンポーネントに対するマネージドBean
<managed-bean>
<managed-bean-name>backing_SRSkills</managed-bean-name>
<managed-bean-class>oracle.srdemo.view.backing.SRSkills</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>allItemsIteratorName</property-name>
<value>ProductListIterator</value>
</managed-property>
<managed-property>
<property-name>allItemsValueAttrName</property-name>
<value>ProdId</value>
</managed-property>
<managed-property>
<property-name>allItemsDisplayAttrName</property-name>
<value>Name</value>
</managed-property>
<managed-property>
<property-name>allItemsDescriptionAttrName</property-name>
<value>Description</value>
</managed-property>
<managed-property>
<property-name>selectedValuesIteratorName</property-name>
<value>StaffExpertiseAreasIterator</value>
</managed-property>
<managed-property>
<property-name>selectedValuesValueAttrName</property-name>
<value>ProdId</value>
</managed-property>
</managed-bean>
SRSkillsページでは、次のイテレータ・オブジェクトが使用されます。
StaffListIterator: シャトルの上のドロップダウン・リスト内の技術者名を提供するStaffListコレクションを反復処理します。
StaffExpertiseAreasIterator: 選択した技術者名に割り当てられるスキルのリストを提供するStaffExpertiseAreasコレクションを反復処理します。
ProductListIterator: 製品名を提供するProductListコレクションを反復処理します。
SRSkillsページのすべてのバインディングは、app_management_SRSkillsPageDef.xmlファイルに定義されます。例19-63に、SRSkillsページのページ定義ファイルの一部を示します。
例19-63 SRSkillsページのページ定義ファイル
<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
version="10.1.3.36.2" id="app_management_SRSkillsPageDef"
Package="oracle.srdemo.view.pageDefs">
<parameters/>
<executables>
<iterator id="StaffListIterator" Binds="StaffList" RangeSize="10"
DataControl="SRService"/>
<iterator id="StaffExpertiseAreasIterator" Binds="StaffExpertiseAreas"
RangeSize="10" DataControl="SRService"/>
<iterator id="ProductListIterator" Binds="ProductList" RangeSize="10"
DataControl="SRService"/>
</executables>
...
</pageDefinition>
次の手順では、ドロップダウン・リストから技術者を選択するためのselectOneChoiceコンポーネントがすでに作成されていることを前提としています。ドロップダウン・リストの作成方法の詳細は、19.7.5項「ナビゲーション・リスト・バインディングを持つリストの作成方法」を参照してください。
この手順では、ページ定義ファイル(例19-63)で関連するイテレータ・バインディングと、ShuttlePageBackingBeanBaseクラス(例19-61)と同様のクラスをすでに作成し、faces-config.xml(例19-62)で必要なマネージドBeanおよび管理プロパティをすでに構成してあることも前提としています。
ナビゲーション・リスト・コンポーネントに関連付けられたシャトル・コンポーネントの作成方法:
ナビゲーション・リスト・コンポーネントが含まれるJSFページで、selectOneChoiceコンポーネントを選択します。プロパティ・インスペクタで、Id属性に値(technicianListなど)を設定し、ValueChangeListener属性をbacking_SRSkillsマネージドBeanのrefreshSelectedList()メソッドに設定します(例: #{backing_SRSkills.refreshSelectedList})。
コンポーネント・パレットの「ADF Faces Core」ページから、「SelectManyShuttle」をページ上にドラッグ・アンド・ドロップします。JDeveloperにより、「SelectManyShuttleの挿入」ダイアログが表示されます(図19-28)。
「リストにバインド(項目を選択)」を選択し、「バインド」をクリックして「式ビルダー」を開きます。
「式ビルダー」で、「JSFマネージドBean」→「backing_SRSkills」を拡張します。「allItems」をダブルクリックして、式#{backing_SRSkills.allItems}を構築します。「OK」をクリックします。
これにより、f:selectItemsタグが、シャトルの先行リストにデータを移入するgetAllItems()メソッドにバインドされます。
「SelectManyShuttleの挿入」ダイアログで、「共通プロパティ」をクリックします。「値」フィールドの横の「バインド」をクリックして、再び「式ビルダー」を開きます。
「式ビルダー」で、「JSFマネージドBean」→「backing_SRSkills」を拡張します。「selectedValues」をダブルクリックして、式#{backing_SRSkills.selectedValues}を構築します。「OK」をクリックします。
これにより、selectManyShuttleコンポーネントのvalue属性が、シャトルの後続リストにデータを移入するgetSelectedValues()メソッドにバインドされます。
「SelectManyShuttleの挿入」ダイアログを閉じます。
プロパティ・インスペクタで、selectManyShuttleコンポーネントのpartialTriggers属性を、技術者名のナビゲーション・ドロップダウン・リストを提供するselectOneChoiceコンポーネントのID(technicianListなど)に設定します。
例19-64に、「SelectManyShuttleの挿入」ダイアログを完了した後のselectManyShuttleコンポーネントのコードを示します。
例19-64 SRSkills.jspxファイル内のselectManyShuttleコンポーネント
<af:selectManyShuttle value="#{backing_SRSkills.selectedValues}" partialTriggers="technicianList" ... <f:selectItems value="#{backing_SRSkills.allItems}"/> </af:selectManyShuttle>
シャトル・コンポーネントの使用方法の詳細は、次のサイトでADF Faces Coreのタグを参照してください。
SRSkillsページが最初にアクセスされると、イテレータStaffListIteratorが実行され、StaffListコレクションが反復処理されます。デフォルトでは、selectOneChoiceコンポーネントに表示される最初の項目が選択されます。これは、このコンポーネントには、値が常にイテレータの現在の行に設定されるナビゲーション・リスト・バインディングがあるためです。初回の選択時にStaffListコレクションとStaffExpertiseAreasコレクション間のマスター/ディテールが自動的に調整されます。これにより、後続リストで選択された技術者の製品スキルによってselectManyShuttleコンポーネントが更新されます。
管理者がナビゲーション・ドロップダウン・リストから別の技術者を選択すると、selectOneChoiceコンポーネントにより、StaffListイテレータの現在の行で新しい選択が行われます。selectOneChoiceコンポーネントはselectManyShuttleコンポーネント上でpartialTriggersコンポーネントとしてリストされているため、シャトルは、後続リストで新しく選択された技術者の製品スキルを使用して再レンダリングされます。
selectManyShuttleコンポーネント上のpartialTriggers属性設定により、先行および後続リストで更新された値を使用してシャトルが再表示されます。
「Save skill changes」コマンド・ボタン(図19-26を参照)をクリックすると、現在の技術者に関連付けられている製品ID(割り当てられているスキル)のListが取得され、updateSkillsForCurrentStaff()メソッドに送信されます。
例19-65に、SRSkills.jspxページ内のcommandButtonコンポーネントのコードを示します。