この章では、ADF Facesコンポーネントを使用してSRDemoアプリケーションの機能の一部を作成する方法について説明します。
この章の内容は次のとおりです。
ADF Facesコンポーネントは、ユーザーの操作を単純化します。たとえば、inputFileを使用するとファイルのアップロードが可能になります。また、selectInputTextには、選択された値に応じてポップアップ・ウィンドウにナビゲートしたり、最初のページに戻るためのダイアログ・サポートが組み込まれています。ほとんどのADF Facesコンポーネントは最小限のJavaコーディングで即時利用できますが、Backing Bean内の追加のコーディングとfaces-config.xml内の構成が必要となるものもあります。
SRDemoページではカスタム・スキンを使用していますが、この章では、レンダリングされたUIコンポーネントの説明および図にデフォルトのOracleスキンを使用します。
この章では次の内容について説明します。
メニュー・モデルを使用して動的なナビゲーション・メニューを作成する方法
コマンド・コンポーネントを使用してポップアップ・ダイアログを作成する方法
部分トリガーおよびイベントを使用して部分ページ・レンダリングを明示的に有効化する方法
プロセス・トレイン・モデルを使用して複数ページ・プロセスを作成する方法
ファイル・アップロード・サポートを提供する方法
静的および動的な値リストを備えたリストと、ナビゲーション・リスト・バインディングを作成する方法
リスト・アイテムを表示および移動するためのシャトルを作成する方法
SRDemoページでは、panelPageコンポーネントを使用して、ページ・ナビゲーションのための階層メニュー・システムを備えたページをレイアウトしています。図11-1は、SRDemoアプリケーションのメニュー階層から選択可能なメニュー項目を持つ「Management」ページを示しています。通常、メニュー階層はグローバル・ボタン、メニュー・タブ、およびメニュー・タブの下のメニュー・バーで構成されます。
手動での作成。個々のメニュー項目コンポーネントを各メニュー・コンポーネントに挿入し、各ページ上で現在のメニュー項目を選択済としてマーク付けします。
宣言的に作成。各メニュー・コンポーネントをメニュー・モデル・オブジェクトにバインドし、現在の項目を選択済として設定することを含め、メニュー・モデルを使用して適切なメニュー項目を表示します。
SRDemoアプリケーションで表示されるほとんどのページでは、(メニュー・モデルとマネージドBeanを使用する)宣言的な手法を採用してメニュー階層を動的に生成しています。
panelPageコンポーネントでは、ユーザーがアプリケーション内の関連ページをすばやく表示できる階層構造のナビゲーション・メニューを作成するためのmenu1およびmenu2ファセットがサポートされています。
menu1ファセットは、メニュー・タブとしてレンダリングされる一連のメニュー項目をレイアウトするmenuTabsコンポーネントを取ります。同様に、menu2ファセットは、メニュー・タブの下のバーにメニュー項目をレンダリングするmenuBarコンポーネントを取ります。
グローバル・ボタンは、「Help」ボタンのように、アプリケーションのどのページからも常に選択可能なボタンです。panelPageのmenuGlobalファセットは、一連のボタンをレイアウトするmenuButtonsコンポーネントを取ります。
|
注意: SRDemoアプリケーションのグローバル・ボタンは動的に生成されるのではなく、各ページにハードコードされます。一部のページでは、menuTabsおよびmenuBarコンポーネント内に、キャッシュ可能なフラグメントが使用されています。この章では動的メニューの作成方法を説明することが目的なので、説明とコード・サンプルにグローバル・ボタンは含まれていますが、キャッシングは含まれていません。キャッシングの詳細は、第15章「キャッシングによるアプリケーション・パフォーマンスの最適化」を参照してください。 |
階層メニューを動的に表示するには、メニュー・モデルを構築し、(menuTabsやmenuBarなどの)メニュー・コンポーネントをメニュー・モデルにバインドします。実行時に、メニュー・モデルによってページの階層メニュー選択項目が生成されます。
動的ナビゲーション・メニューを作成する手順:
メニュー・モデルを作成します。(11.2.1.1項「メニュー・モデルの作成」を参照。)
メニュー選択項目、またはメニュー階層内の項目ごとにJSFページを作成します。(11.2.1.2項「各メニュー項目のJSFページの作成」を参照。)
メニュー項目ごとに、ナビゲーション・ケースを持つグローバル・ナビゲーション・ルールを1つ作成します。(11.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プロパティなど)を定義します。例11-1に、SRDemoアプリケーションで使用されているMenuItemクラスを示します。
例11-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で作成したメニュー項目クラスのインスタンスである必要があります。例11-2に、SRDemoアプリケーション内のすべてのメニュー項目に対するマネージドBeanコードを示します。項目が子項目を持つ場合、リスト・エントリには、子のマネージドBeanを適切な順序でリストします。たとえば、「Management」メニュー・タブ項目は2つの子を持ちます。
通常、各Beanにはそのスコープとしてnoneを割り当てる必要があります。ただし、SRDemoアプリケーションでは、メニュー項目が動的に作成されるときにセキュリティ属性が割り当てられるため、メニュー項目にはsessionスコープのマネージドBeanを使用しています。また、SRDemoアプリケーションでは、現在ログインしているユーザーのユーザー・ロール情報を保持するために、sessionスコープのUserInfo Beanを使用しています。ユーザーのログイン時に表示されるメニュー項目は、このユーザー・ロール情報を使用して決定されます。たとえば、「Management」メニュー・タブは、管理者のユーザー・ロールを持つユーザーのみに表示されます。JSFでは、noneスコープのBeanからsessionスコープのマネージドBeanを参照することはできません。このため、SRDemoアプリケーションでは、メニュー・システムに対してすべてsessionスコープのマネージドBeanを使用しています。
例11-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>
|
注意: 図11-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インスタンスを構成するクラスを作成します。このインスタンスはメニュー・システムのツリー階層全体を表します。後でこれをメニュー・モデルに挿入します。例11-3に、SRDemoアプリケーションで使用されているMenuTreeModelAdapterクラスを示します。
例11-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プロパティの値として(適切な順序でリストした)ルート・ページのリストが使用されている必要もあります。例11-2に示したように、ルート・ページはグローバル・ボタン・メニュー項目であり、第1レベルのメニュー・タブ項目です。例11-4に、メニュー・ツリー・モデルを作成するためのマネージドBeanを示します。
例11-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インスタンスを構成するクラスを作成します。このインスタンスにより、メニュー・ツリー・モデルからメニュー・モデルが作成されます。例11-5に、SRDemoアプリケーションで使用されているMenuModelAdapterクラスを示します。
例11-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プロパティに設定されている必要があります。例11-6に、メニュー・モデルを作成するためのマネージドBeanコードを示します。
例11-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スコープに互換性があることが必要です。表11-1に、互換性のあるBeanスコープを示します。
SRDemoアプリケーションのすべてのラベルに対するStringリソースは、リソース・バンドルに含まれています。このリソース・バンドルはfaces-config.xml内で構成されています。前述のように、各メニュー項目はsessionスコープのマネージドBeanとして定義され、メニュー項目の様々な属性(typeやlabelなど)はマネージドBeanプロパティを介して定義されています。メニュー項目のマネージドBeanがリソース・バンドルから使用するラベルにアクセスするには、バンドルへのアクセスを提供するマネージドBeanを構成する必要があります。
SRDemoアプリケーションでは、ResourceAdapterクラスが、resourcesというマネージドBeanを介してEL式内でリソース・バンドルを公開しています。例11-7に、ResourceAdapterクラスと、バンドルからStringを取得するJSFUtils.getStringFromBundle()メソッドを示します。
例11-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
}
例11-8に、他のマネージドBeanに対してStringリソースへのアクセスを提供するresourcesというマネージドBeanコードを示します。
例11-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などの)メニュー・コンポーネントをメニュー・モデルにバインドします。例11-9に、コンポーネントをメニュー・モデルにバインドするmenuTabsコンポーネント・コードを示します。
例11-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>
例11-10に示すように、各メニュー・コンポーネントは、1つのcommandMenuItemコンポーネントを取るnodeStampファセットを持ちます。変数を使用し、メニュー・コンポーネントをモデルにバインドすれば、commandMenuItemコンポーネントを1つ使用するだけで、メニュー内のすべての項目を表示できます。これには、commandMenuItemコンポーネントで、text値に#{var.label}のようなEL式を使用し、action値に#{var.getOutcome}のようなEL式を使用します。これは、メニュー項目に表示される実際のラベルと、そのメニュー項目が起動されたときのナビゲーション結果を提供するcommandMenuItemコンポーネントです。
例11-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属性によって指定します。
例11-1ではMenuItemクラスを使用しています。グローバル項目の場合、rendered属性を変数のtypeプロパティにバインドし、このプロパティをglobalに設定します。非グローバル項目以外の場合、rendered属性を変数のshownプロパティとtypeプロパティにバインドし、typeプロパティをdefaultに設定します。非グローバル項目の場合は、さらにdisabled属性を変数のreadOnlyプロパティにバインドします。例11-11に、menuTabs(非グローバル・コンポーネント)およびmenuButtons(グローバル・コンポーネント)に対してこれを行う方法を示します。
例11-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になります。例11-12に、panelPageコンポーネントのメニュー・コードの一部を示します。
例11-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つ作成します。グローバル・ナビゲーション・ルールには子メニュー項目は含めません。例11-13に示すように、子メニュー項目を持つメニュー項目(たとえば、「Management」メニュー・タブは子メニュー・バー項目を持つ)に対して、親項目からのすべてのナビゲーション・ケースを含むナビゲーション・ルールを作成します。
例11-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属性によって定義されます。メニューのレンダリングが完了すると、このプロパティは削除されます(または以前の値に戻ります)。例11-14では、各メニュー・バー項目のデータがELプロパティmenuSubTabの下に配置されます。nodeStampは、menuSubTabプロパティから詳細なプロパティを取得して、各項目のデータを表示します。
例11-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()というメソッドがあります。このメソッドは、フォーカスを得るページを決定し、ノードがそのフォーカス・パス上にあれば自動的にそのノードをレンダリングします。
メニュー・モデルを使用せずに、手動でメニューを作成する場合もあります。
図11-2に示すように、第1レベルのメニュー・タブ「My Service Requests」には、複数の項目を持つ第2レベルのメニュー・バーが1つあります。「My Service Requests」から、未処理、保留中、処理済またはすべてのサービス・リクエストを表示できます。これらは、左から順に1、2、3および4番目のメニュー・バー項目として表示されます。各ビューは、実際にはSRList.jspxページから生成されます。
SRList.jspxページでは、menuBarコンポーネントをメニュー・モデルにバインドし、nodeStampを使用してメニュー項目を生成するのではなく、個々の子commandMenuItemコンポーネントを使用してメニュー項目を表示しています。これは、コマンド・コンポーネントが、ナビゲート先のリクエストのタイプ(たとえば、未処理、保留中、処理済またはすべてのサービス・リクエスト)を決定するために値を必要とするためです。例11-15に、SRList.jspxページで使用されているmenuBarコンポーネントのコードの一部を示します。
例11-15 子commandMenuItemコンポーネントを持つmenuBarコンポーネント
<af:menuBar>
<af:commandMenuItem text="#{res['srlist.menubar.openLink']}"
disabled="#{!bindings.findServiceRequests.enabled}"
selected="#{userState.listModeOpen}"
actionListener="#{bindings.findServiceRequests.execute}">
<af:setActionListener from="#{'Open'}"
to="#{userState.listMode}"/>
</af:commandMenuItem>
<af:commandMenuItem text="#{res['srlist.menubar.pendingLink']}"
disabled="#{!bindings.findServiceRequests.enabled}"
selected="#{userState.listModePending}"
actionListener="#{bindings.findServiceRequests.execute}"
<af:setActionListener from="#{'Pending'}"
to="#{userState.listMode}"/>
</af:commandMenuItem>
...
<af:commandMenuItem text="#{res['srlist.menubar.allRequests']}"
selected="#{userState.listModeAll}"
disabled="#{!bindings.findServiceRequests.enabled}"
actionListener="#{bindings.findServiceRequests.execute}">
<af:setActionListener from="#{'%'}"
to="#{userState.listMode}"/>
</af:commandMenuItem>
...
</af:menuBar>
af:setActionListenerタグは、ナビゲーション前にActionSourceコンポーネントの値を宣言的に設定し、適切なリスト・モード値をuserStateマネージドBeanに渡します。sessionスコープのuserStateマネージドBeanは、ページの現在のリスト・モードを保存します。
commandMenuItemコンポーネントが起動されると、findServiceRequestsメソッドがリスト・モード値を使用して実行され、値に一致するコレクションが返されます。また、commandMenuItemコンポーネントはuserSystemState Bean内の簡易関数を使用して、メニュー項目を選択済としてマーク付けするかどうかを評価します。
新しいページを、現在のページと同じウィンドウではなく、別のポップアップ・ダイアログ内に表示する場合があります。ポップアップ・ダイアログでは、ユーザーに情報を入力または選択させてから、その情報を使用する元のページに戻らせることができます。通常は、ポップアップ・ダイアログを起動してプロセスを管理するためにJavaScriptを使用する必要がありました。また、PDAなどの特定のクライアント・デバイスではポップアップ・ダイアログがサポートされていないため、このようなケースを管理するためのコードを作成する必要もありました。ADF Facesにはダイアログ・フレームワークが備わっており、JavaScriptを使用せずにポップアップ・ダイアログおよびプロセスの起動と管理を容易に行うことができます。
受注を確認しようとするユーザーにログインを要求する単純なアプリケーションについて考えてみます。図11-3は、5つのページ(login.jspx、orders.jspx、new_account.jspx、account_details.jspxおよびerror.jspx)からなるアプリケーションのページ・フローを示しています。
既存のユーザーが正常にログインすると、アプリケーションにより「Orders」ページが表示され、ユーザーの受注が(存在していれば)表示されます。ユーザーが正常にログインできなかった場合、ポップアップ・ダイアログに「Error」ページが表示されます(図11-4)。
「Error」ページには「Cancel」ボタンがあります。ユーザーが「Cancel」をクリックすると、ポップアップ・ダイアログが閉じ、アプリケーションは「Login」ページ(図11-5)に戻ります。
新規のユーザーが「Login」ページで「New user」リンクをクリックすると、ポップアップ・ダイアログ内に「New Account」ページが表示されます(図11-6)。
ファースト・ネームやラスト・ネームなどの情報を入力した後、ユーザーが「Details」ボタンをクリックすると、同じポップアップ・ダイアログ内に「Account Details」ページが表示されます(図11-7)。ユーザーは「Account Details」ページでその他の情報を入力し、新しいログイン・アカウントのパスワードを確認入力します。「Account Details」ページには、2つのボタン(「Cancel」および「Done」)があります。
新規のユーザーが新しいログイン・アカウントの作成を中止しようとして「Cancel」をクリックした場合、ポップアップ・ダイアログが閉じ、アプリケーションは「Login」ページに戻ります。新規のユーザーが「Done」をクリックした場合、ポップアップ・ダイアログが閉じ、アプリケーションは「Login」ページに戻ります。このページの「Username」フィールドにユーザーの名前が移入されます(図11-8)。これで、新規のユーザーは、新しいパスワードを入力してログインできるようになります。
ADF Facesには、アプリケーションで簡単にポップアップ・ダイアログをサポートできるように、ActionSourceを実装するコンポーネント(commandButtonやcommandLinkなど)に対するダイアログ機能が組み込まれています。ActionSourceコンポーネントからポップアップ・ダイアログ内にページを起動するかどうかをADF Facesに判断させるためには、次の4つの条件が満たされている必要があります。
コマンド・コンポーネントのアクション結果が"dialog:"で始まっていること
コマンド・コンポーネントのuseWindow属性が"true"であること
クライアント・デバイスでポップアップ・ダイアログがサポートされていること
|
注意: useWindowがfalseの場合や、クライアント・デバイスでポップアップ・ダイアログがサポートされていない場合、ADF Facesでは自動的に、ポップアップを使用せずに現在のウィンドウ内にページが表示されます。この処理を実現するためにコードを変更する必要はありません。 |
ポップアップ・ダイアログ内に表示されるページは、通常のJSFページです。しかし、この章ではポップアップ・ダイアログの実装方法を説明することを目的としているため、ポップアップ・ダイアログ内に表示されるページをダイアログ・ページと表記し、ポップアップ・ダイアログが起動される前のページを元ページと表記します。ダイアログ・プロセスは、元ページがダイアログ(1つのダイアログ・ページが含まれる場合も、一連のダイアログ・ページが含まれる場合もある)を起動した時点で開始され、ユーザーがダイアログを閉じて元ページに戻った時点で終了します。
アプリケーションでポップアップ・ダイアログをサポートするための操作は、次のとおりです。
ダイアログを起動するためのJSFナビゲーション・ルールを定義します。
ダイアログを起動する元のJSFページを作成します。
ダイアログ・ページを作成し、ダイアログ値を返します。
戻り値を処理します。
値をダイアログに渡します。
これらの操作は、任意の順序で実行できます。
ポップアップ・ダイアログのナビゲーションを管理するには、特殊なdialog:結果を持つ標準的なJSFナビゲーション・ルールを定義します。ダイアログ・サンプル・アプリケーション(図11-3)を使用すると、「Login」ページからは次の3つのナビゲーション結果が考えられます。
同じウィンドウ内に「Orders」ページを表示(ログインの正常完了)
ポップアップ・ダイアログ内に「Error」ダイアログ・ページを表示(ログインの失敗)
ポップアップ・ダイアログ内に「New Account」ダイアログ・ページを表示(新規のユーザー)
例11-16に、「Login」ページ(login.jspx)からの3つのナビゲーション・ケースに対するナビゲーション・ルールを示します。
例11-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」ページにあります。例11-17に、「Login」commandButtonコンポーネントのコードを示します。
例11-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内で定義されているナビゲーション・ケースにおける結果と比較することにより、次に表示するページを決定します。このアクション・メソッドのコードを例11-18に示しています。
例11-18 「Login」ボタンのアクション・メソッド・コード
public String commandButton_action()
{
String retValue;
retValue = "orders";
_cust = getListCustomer();
if (_cust == null || !password.equals(_cust.getPassword()))
{
retValue = "dialog:error";
}
return retValue;
}
例11-19に、静的アクション結果を使用する「New user」のcommandLinkコンポーネントのコードを示します。
例11-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)を配信します。起動イベントは、ポップアップ・ダイアログを起動するコンポーネントと、ダイアログ・プロセスの開始時に表示するコンポーネント・ツリーのルートに関する情報を格納しています。起動イベントは、パラメータのマップをダイアログに渡すこともできます。詳細は、11.3.1.5項「ダイアログに値を渡す」を参照してください。
サンプル・アプリケーションのダイアログ・ページは、「Error」ページ、「New Account」ページおよび「Account Details」ページです。新規のユーザーのダイアログ・プロセスでは、実際に2つのページ(「New Account」ページと「Account Details」ページ)が表示されます。ユーザーがログインに失敗したときのダイアログ・プロセスでは、「Error」ページのみが表示されます。
ダイアログ・ページは他のJSFページとほとんど同じですが、1つだけ例外があります。ダイアログ・ページでは、ダイアログ・プロセスが終了したとき(つまり、ユーザーがダイアログを閉じたとき)にADF Facesに通知する方法を指定する必要があります。通常は、コマンド・コンポーネントを介して、プログラムまたは宣言により指定します。例11-20に、「Error」ページの「Cancel」ボタンを介して、プログラムによりこれを指定する方法を示します。
例11-20 「Error」ページの「Cancel」ボタン
<af:commandButton text="Cancel"
actionListener="#{backing_error.cancel}" />
commandButtonのactionListener属性は、ページのバッキングBean(Error.java)内のアクション・リスナー・メソッドへの参照を指定します。アクション・リスナー・メソッドは、「Cancel」ボタンをクリックしたときに生成されるアクション・イベントを処理します。このアクション・リスナー・メソッド内でAdfFacesContext.returnFromDialog()メソッドをコールします(例11-21を参照)。
例11-21 バッキングBean内の「Cancel」ボタンに対するアクション・リスナー・メソッド
public void cancel(ActionEvent actionEvent)
{
AdfFacesContext.getCurrentInstance().returnFromDialog(null, null);
}
|
注意: AdfFacesContext.returnFromDialog()メソッドはnullを返します。バッキングBean内で「Cancel」アクション・イベントを処理するのに必要な値はこれだけです。 |
「Account Details」ダイアログ・ページで宣言的に同じことを達成するには、af:returnActionListenerタグを「Cancel」ボタン・コンポーネントにアタッチします(例11-22を参照)。af:returnActionListenerタグは、AdfFacesContextのreturnFromDialogメソッドをコールします。バッキングBeanのコードは必要ありません。
例11-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」をクリックした場合、プロセスは終了し、ユーザーの入力値が返されます。例11-23に、「Done」ボタンのコードを示します。
例11-23 「Account Details」ページの「Done」ボタン
<af:commandButton text="Done"
actionListener="#{backing_new_account.done}" />
commandButtonのactionListener属性は、ページのバッキングBean(New_account.java)内のアクション・リスナー・メソッドへの参照を指定します。アクション・リスナー・メソッドは、「Done」ボタンをクリックしたときに生成されるアクション・イベントを処理します。例11-24に、このアクション・リスナー・メソッドのコードを示します。ここでは、戻り値が取得されてから、AdfFacesContext.returnFromDialog()メソッドを介して返されています。
例11-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)に配信されます。戻り値を処理する方法は、11.3.1.4項「戻り値の処理」を参照してください。
戻り値を処理するには、ダイアログを起動したコマンド・コンポーネント(サンプル・アプリケーションでは「Login」ページの「New user」リンク・コンポーネント)に戻りリスナーを登録します。例11-25に、「New user」リンク・コンポーネントのコードを示します。
例11-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)内の戻りリスナー・メソッドへの参照を指定します。戻りリスナー・メソッドは、ダイアログが閉じられたときに生成される戻りイベントを処理します。例11-26に、戻り値を処理する戻りリスナー・メソッドのコードを示します。
例11-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)に起動リスナーを登録します。例11-27に、commandLinkコンポーネントのコードを示します。
例11-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に追加します。例11-28に、起動リスナー・メソッドのコードを示します。
例11-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式を介してキーと値を取得します(例11-29を参照)。
例11-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」ページ(図11-13を参照)では、ユーザーが「Frequently Asked Questions」リンクをクリックすると、FAQリストが示されたポップアップ・ダイアログが表示されます。
「Edit Service Request」ページでは、ユーザーが「Assigned to」ラベルの横の懐中電灯アイコン(図11-12を参照)をクリックすると、「Search for Staff」ポップアップ・ダイアログが表示されます。このダイアログ(図11-9)では、ユーザーは最初にユーザー・ロールに基づいて検索を構成します。次に、結果のセクションで名前の横のラジオ・ボタンをクリックして「Select」をクリックします。
選択が完了すると、ポップアップ・ダイアログが閉じ、アプリケーションは「Edit Service Request」ページに戻ります。このページでは、表示専用の「Assigned to」フィールドが更新され、選択された技術者のファースト・ネームとラスト・ネームが表示されています(図11-10)。
繰り返して説明すると、ポップアップ・ダイアログをサポートするための操作は次のとおりです(順序は任意です)。
dialog:結果を持つJSFナビゲーション・ルールを作成します。
dialog:アクション結果を介してダイアログを起動するページを作成します。
ダイアログ・ページを作成し、値を返します。
戻り値を処理します。
最初に、ダイアログを起動するためのJSFナビゲーション・ルールを例11-30に示します。ダイアログ・ページSRStaffSearch.jspxを表示するためのナビゲーション・ケースは、dialog:StaffSearch結果によって定義されています。SRFaq.jspxダイアログ・ページを表示するためのナビゲーション・ケースは、dialog:FAQ結果によって定義されています。
例11-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に認識させるための前提条件です。
例11-31に、SRStaffSearch.jspxダイアログ・ページを起動する、ページのcommandLinkコンポーネントを示します。commandLinkコンポーネントは、静的アクション結果dialog:StaffSearchを持ちます。
例11-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>
例11-32に、SRFaq.jspxダイアログ・ページを起動する、ページのcommandLinkコンポーネントを示します。commandLinkコンポーネントは、静的アクション結果dialog:SRFaqを持ちます。
例11-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()メソッドがコールされます。commandButtonのaction属性は、ページのバッキングBean(SRStaffSearch.java)内のselectButton_actionアクション・メソッドにバインドされます。このアクション・メソッドは、選択された行データを表から取得してUserオブジェクトを抽出し、AdfFacesContext.returnFromDialog()メソッドを介してそのオブジェクトを返します。例11-33に、「Select」ボタン・コンポーネントとそのアクション・メソッドのコードSnippetを示します。
例11-33 「Select」コマンド・ボタンのアクション・メソッド
<af:tableSelectOne>
<af:commandButton text="#{res['srstaffsearch.button.select']}"
action="#{backing_SRStaffSearch.selectButton_action}"/>
</af:tableSelectOne>
...
...
public String selectButton_action() {
//get row data from table
JUCtrlValueBindingRef selectedRowData =
(JUCtrlValueBindingRef)this.getResultsTable().getSelectedRowData();
RowImpl row = (RowImpl)selectedRowData.getRow();
User staffMember = (User)row.getDataProvider();
// And return it
AdfFacesContext.getCurrentInstance().returnFromDialog(staffMember, null);
// no navigation to another page and thus null is returned
return null;
}
SRFaq.jspxの場合と同様に、ダイアログの終了とAdfFacesContext.returnFromDialog()メソッドのコールにはcommandLinkコンポーネントが使用されています。af:returnActionListenerタグは、AdfFacesContextのreturnFromDialogメソッドをコールします。バッキングBeanのコードは必要ありません。例11-34に、commandLinkのコードSnippetを示します。ユーザーがSRFaq.jspxポップアップ・ダイアログを閉じると、ADF Facesはダイアログを閉じるだけです。ダイアログの戻り値は送信されないため、戻り値の処理は必要ありません。
例11-34 SRFaqポップアップ・ダイアログを閉じるためのcommandLinkコンポーネント
<af:commandLink text="#{res['srdemo.close']}">
<af:returnActionListener/>
</af:commandLink>
SRStaffSearch.jspxポップアップ・ダイアログが閉じられると、ダイアログ戻り値(つまり、選択された行データ)が戻りイベント(ReturnEvent)のプロパティとして送信されます。戻りイベントは、元ページSREdit.jspxのcommandLinkコンポーネントに登録されている戻りリスナーに配信されます(例11-35を参照)。commandLinkのreturnListener属性は、ページのバッキングBean(SREdit.java)内のhandleStaffLOVReturnリスナー・メソッドにバインドされます。戻りリスナー・メソッドは、閉じられたダイアログからの戻り値を処理します。例11-35にも、handleStaffLOVReturnリスナー・メソッドのコードSnippetを示します。
例11-35 戻り値を処理するための戻りリスナー・メソッド
<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>
...
...
public void handleStaffLOVReturn(ReturnEvent event) {
//Get the return value from the pop up
User returnedStaffMember = (User)event.getReturnValue();
if (returnedStaffMember != null) {
DCBindingContainer bc = (DCBindingContainer)getBindings();
// Get the handle to the Service Request we are editing
DCControlBinding thisSRId =
(DCControlBinding)bc.getControlBinding("svrId");
RowImpl srRowImpl = (RowImpl)thisSRId.getCurrentRow();
ServiceRequest thisSR = (ServiceRequest)srRowImpl.getDataProvider();
//See if a different user has been selected?
User oldUser = thisSR.getAssignedTo();
if ((oldUser == null) || (!oldUser.equals(returnedStaffMember))) {
//Set the returned Staff member from the LOV
thisSR.setAssignedTo(returnedStaffMember);
//now re-execute the iterator to refresh the screen
DCControlBinding accessorData =
(DCControlBinding)bc.getControlBinding("assignedToFirstName");
accessorData.getDCIteratorBinding().executeQuery();
//Now reset the Assigned date
ADFUtils.setPageBoundAttributeValue(getBindings(), "assignedDate",
new Timestamp(System.currentTimeMillis()));
//And get the data field to update with the new bound value
this.getAssignedDate().resetValue();
}
}
}
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コンポーネントを使用しています。図11-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をサポートします。
図11-12は、未割当てのサービス・リクエストが表示されたSREdit.jspxページを示しています。ユーザーが懐中電灯アイコン(objectImageコンポーネントを持つcommandLinkコンポーネント)をクリックすると、ユーザーが名前を検索および選択できるポップアップ・ダイアログが表示されます。名前が選択されると、ポップアップ・ダイアログは閉じ、「Assigned to」表示専用フィールド(outputTextコンポーネント)と「Status」の下の日付フィールド(selectInputDateコンポーネント)が適切な値でリフレッシュされます(ただし、編集ページの他の部分はリフレッシュされません)。
コマンド・コンポーネントによって別のコンポーネントを部分的にリフレッシュする手順:
トリガー・コマンド・コンポーネントで、id属性を一意の値に設定し、partialSubmit属性をtrueに設定します。
トリガー・コマンド・コンポーネントが起動されたときに部分的にリフレッシュするターゲット・コンポーネントで、partialTriggers属性をコマンド・コンポーネントのIDに設定します。
|
ヒント: コンポーネントの一意IDは有効なXML名である必要があります。つまり、IDの最初の文字を数値または空白にすることはできません。また、JSFではID内にコロン(:)は使用できません。 |
例11-36に、PPRを示すために、SREdit.jspxページで使用されているコマンドと読取り専用出力コンポーネントのコードSnippetを示します。
例11-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.ServiceRequeststatus.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コンポーネントはそのプロセス内の合計ページ数と、ユーザーに現在表示されているページを表示します。ユーザーはこれらのページをナビゲーションできます。たとえば、図11-13は、サービス作成リクエスト・プロセスの最初のページを示しています。このページでは、ユーザーがリストボックスから機器を1つ選択し、テキストボックスに問題の説明を入力します。トレイン上のノードの数は、プロセス内の事前定義済ページの合計数を示しています。塗りつぶされているノードは、ユーザーが現在、プロセス内のそのページで作業していることを示します。プロセス内の次のページに移動するには、ノードの下のアクティブなテキスト・リンクをクリックします。
この章の図では、SRDemoスキンではなくOracleスキンを使用していることに注意してください。
processChoiceBarコンポーネントは、プロセス内のページを選択するためのドロップダウン・メニューをレンダリングし、適用可能であれば、プロセス内を前後にナビゲーションするための1つ以上のボタンもレンダリングします。
サービス・リクエスト作成プロセスの最初のページでは、ユーザーが「Confirm」テキスト・リンクまたは「Continue」ボタンをクリックする(またはドロップダウン・メニューから「Confirm」を選択する)と、このプロセスの2番目のページ(図11-14)が表示されます。
2番目のページからは、ユーザーはトレイン上の「Basic Problem Details」をクリックするか、「Back」ボタンをクリックすることにより、あるいはドロップダウン・メニューから「Basic Problem Details」をクリックすることにより、問題の説明のページに戻ることができます。
完了後、ユーザーが「Submit Request」をクリックすると、「Request Submitted」ページが表示されます(図11-15)。
各ページ上にプロセス・トレインを表示するには、processTrainコンポーネントをプロセス・トレイン・モデルにバインドします。実行時、列モデルによりプロセス内の各ページのトレインが動的に作成されます。
プロセス・トレインを作成して使用する手順:
プロセス・トレイン・モデルを作成します。(11.5.1.1項「プロセス・トレイン・モデルの作成」を参照。)
トレイン上の各ノードのJSFページを作成します。(11.5.1.2項「各トレイン・ノードのJSFページの作成」を参照。)
ノードごとに、ナビゲーション・ケースを持つナビゲーション・ルールを作成します。(11.5.1.3項「JSFナビゲーション・ルールの作成」を参照。)
oracle.adf.view.faces.model.MenuModelクラスとoracle.adf.view.faces.model.ProcessMenuModelクラスを使用して、動的にプロセス・トレインを生成するプロセス・トレイン・モデルを作成します。MenuModelクラスは、メニュー・タブおよびメニュー・バーを作成する場合に使用されるメニュー・モデル・メカニズムと同じです(11.2.1項「動的ナビゲーション・メニューを作成する方法」を参照)。
プロセス・トレイン上のノードごとに、プロパティを取得および設定できるクラスを作成します。
トレイン上の各ノードには、label、viewIdおよびoutcomeプロパティが必要です。例11-37に、SRDemoアプリケーションで使用されているMenuItemクラスの一部を示します。
例11-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で作成したクラスのインスタンスである必要があります。例11-38に、faces-config.xml内のプロセス・トレイン・ノードに対するマネージドBeanコードを示します。
例11-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です。順序は、トレインに表示するとおりにします。例11-39に、プロセス・トレイン・リストを作成するためのマネージドBeanコードを示します。
例11-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)を持つ必要があります。
例11-40に、SRDemoアプリケーションで使用されているTrainModelAdapterクラスを示します。
例11-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の動作を実装できます。これらのプロセス動作を使用してページ・アクセスを制御する方法の詳細は、11.5.1.1.1項「ページ・アクセスの制御について」を参照してください。
手順4で作成したクラスを参照するマネージドBeanを構成します。これは、processTrainコンポーネントがバインドされるBeanです。
このBeanはインスタンス化されたときに、instanceプロパティ値がトレイン・リストを作成する(手順3で構成した)マネージドBeanに設定されている必要があります。また、インスタンス化されたBeanでは、viewIdProperty値が、手順1で作成したBeanのviewIdプロパティに設定されている必要があります。例11-41に、プロセス・トレイン・モデルを作成するためのマネージドBeanコードを示します。
例11-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のようなコードを追加します(例11-41を参照)。
<managed-property> <property-name>maxPathKey</property-name> <value>TRAIN_DEMO_MAX_PATH_KEY</value> </managed-property>
maxPathKey値がProcessMenuModelに渡されるため、ADF FacesはMax Visitedプロセスを使用します(例11-40を参照)。
faces-config.xmlにはmaxPathKey管理対象プロパティの設定がないため、maxPathKeyに対してnullが渡されます。このため、「Create New Service Request」プロセスではPlus Oneプロセスが使用されます。nullが渡された場合、ADF FacesはPlusOneプロセスを使用します。
これらのプロセス・シナリオは、processTrainコンポーネント内で使用されているコマンド・コンポーネントのimmediateおよびreadOnly属性にも影響を与えます。詳細は、11.5.1.2.1項「immediateおよびreadOnly属性について」を参照してください。
各トレイン・ノードは、それぞれ独自のページを持ちます。プロセス・トレインを表示するには、各ページでprocessTrainコンポーネントをプロセス・トレイン・モデルにバインドします(例11-42を参照)。
processTrainコンポーネントは通常、panelPageまたはpageコンポーネントのlocationファセットに挿入されます。メニュー・コンポーネントと同様に、processTrainコンポーネントは、1つのcommandMenuItemコンポーネントを受け入れるnodeStampファセットを持ちます。これは、トレイン・ノードの下に表示される実際のラベルと、そのラベルが起動されたときのナビゲーション結果を提供するcommandMenuItemコンポーネントです。
例11-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コンポーネントも、同じプロセス・トレイン・モデルにバインドされるコンポーネントですが、複数ページ・プロセスを順に実行していくためのユーザーのナビゲーション選択項目を追加します。例11-43に、SRCreate.jspxページ内のprocessChoiceBarコンポーネントのコードを示します。processChoiceBarコンポーネントは通常、panelPageまたはpageコンポーネントのactionsファセットに挿入されます。
例11-43 SRCreate.jspxファイル内のprocessChoiceBarコンポーネント
<af:panelPage ..>
<f:facet name="actions">
<af:panelButtonBar>
<af:commandButton text="#{res['srdemo.cancel']}"
action="#{backing_SRCreate.cancelButton_action}"
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>
図11-13および図11-14に示されているように、processChoiceBarコンポーネントは、プロセス内を前後にナビゲートするための「Continue」ボタンおよび「Back」ボタンを自動的に提供します。これらのボタンに対するコードを記述する必要はありません。追加のボタン(図11-14の「Cancel」ボタンと「Submit Request」ボタンなど)を提供する場合は、panelButtonBarを使用して、ボタン・コンポーネントおよびprocessChoiceBarコンポーネントをレイアウトします。
|
注意: 2つのページのみで構成される複数ページ・プロセスの場合、ADF Facesは、進むボタンのラベルとして「Continue」を使用します。プロセス内に3ページ以上ある場合、進むボタンのラベルは「Next」です。 |
ADF Facesにより提供される2つのプロセス(11.5.1.1.1項「ページ・アクセスの制御について」を参照)は、processTrain内で使用されているcommandMenuItemコンポーネントのimmediate属性とreadOnly属性の両方に影響を与えます。processTrainをプロセス・トレイン・モデルにバインドする場合、ノードのimmediateまたはreadOnly属性をモデルのimmediateまたはreadOnly属性にバインドできます。この場合、ProcessMenuModelクラスはロジックを使用して、immediateまたはreadOnly属性の値を決定します。
現在のページのデータを検証する必要がない場合は、immediate属性をtrueに設定する必要があります。たとえば、Plus Oneシナリオ(11.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ページからしかアクセスできないため、グローバル・ナビゲーション・ルールには含められていません。例11-44に、プロセスのナビゲーション・ルールとナビゲーション・ケースを示します。
例11-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クラスを拡張します(11.2項「動的なナビゲーション・メニューの使用」を参照)。メニューやメニュー項目と同様に、トレイン上の各ノードはメニュー項目として定義されます。しかし、メニューの場合はメニュー項目が中間メニュー・ツリー・オブジェクト(MenuTreeModelAdapter)にまとめて挿入されますが、トレイン・ノードの全リストはArrayListにまとめて挿入されてから、TrainModelAdapterクラスに挿入されます。ただし、ViewIdPropertyMenuModelとProcessMenuModelはいずれも、リストを取得してそれを内部でツリーに変換することに注意してください。
SRDemoアプリケーションでは、どのユーザーでも新規のサービス・リクエストを作成できるため、トレイン上のノードはユーザー・ロールにより保護されていません。つまり、トレイン・モデルはapplicationスコープのマネージドBeanとして格納され、すべてのユーザーがこれを共有することができます。
ユーザー・ロールによっては使用できないタブもあるため、メニュー・タブ項目はユーザー・ロールにより保護されています。このため、メニュー・モデルはsessionスコープのマネージドBeanとして格納されます。
新しいページをプロセス・トレインに追加するには、そのページの新しいマネージドBeanを構成し(例11-38)、新しいマネージドBeanをトレイン・リストに追加し(例11-39)、新しいページのナビゲーション・ケースを追加(例11-44)します。
ファイル・アップロードは、多くのWebアプリケーションで必要とされる機能です。サーブレット、JSP、JSF 1.1.xのような標準的なJ2EEテクノロジでは、ファイル・アップロードは直接にはサポートされていません。ただし、ADF Facesフレームワークでは、inputFileコンポーネントを介してコンポーネント・レベルでファイル・アップロードのサポートが統合されています。
ファイル・アップロード中、ADF Facesにより着信ファイルが一時的にメモリーまたはディスクに格納されます。デフォルトのディレクトリ格納場所と、1つのファイル・アップロード・リクエストで使用可能なディスク領域およびメモリーのデフォルト値を設定できます。
図11-16は、SRDemoアプリケーションのSRMain.jspxページを示しています。このページから、ユーザーは特定のサービス・リクエストに対するファイルをアップロードできます。
ユーザーが「Upload a document」をクリックすると、ポップアップ・ダイアログ内にアップロード・フォームが表示されます(図11-17)。
ユーザーはアップロードするファイルのフルパス名を入力するか、「Browse」をクリックしてファイルを見つけてから選択します。「Begin Upload」をクリックすると、ADF Facesにより、選択したファイルが自動的にアップロードされます。アップロードが正常に完了すると、ADF Facesによりアップロード・ファイルの情報が表示されます(図11-18)。なんらかの理由でアップロードに失敗すると、同じポップアップ・ダイアログ内にエラー・スタック・トレースが表示されます。
JSFアプリケーション内でファイル・アップロードをサポートするには、次の操作を実行します。
ADF Facesフィルタがインストールされていることを確認します。
ADF Facesフィルタは、ADF Facesが適切に初期化されるようにAdfFacesContextオブジェクトを設定するサーブレット・フィルタです。初めてJSFページにADF Facesコンポーネントを挿入すると、JDeveloperにより自動的にこのフィルタがweb.xmlにインストールされます。例11-45に、web.xml内のADF Facesフィルタおよびマッピング構成の設定を示します。
例11-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内で、アップロード・ファイルの格納場所のコンテキスト初期化パラメータを設定します。アップロード・ファイルを保存する場所は、自由に指定できます。例11-46に、SRDemoアプリケーション内で使用されている、アップロード・ファイルのコンテキスト・パラメータを示します。
アップロード・ファイルを処理するためのバッキングBeanを作成します。例11-47に、faces-config.xml内のSRDemoのファイル・アップロード・ページに対するマネージドBeanコードを示します。
JSFページでは、ファイル・アップロードに対してaf:formまたはh:formを使用できます。ファイル・アップロードをサポートするには、次のコードSnippetに示すように、必ず外包フォームを設定してください。
<af:form usesUpload="true"/> .. <h:form enctype="multipart/form-data"/>
inputFileコンポーネントを使用して、ラベル付きの標準的な入力フィールドと「Browse」ボタンを提供します(図11-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ファイル・アップロード・フォームでは、値変更リスナー・メソッドを使用しています。例11-48に、inputFileコンポーネントのvalueChangeListener属性内のメソッド・バインディング式に対するコードを示します。
ページのバッキングBean内に、アップロード・コンテンツを処理するためのコードを記述します。たとえば、コンテンツをファイル・システム内のローカル・ディレクトリに書き込むことなどができます。例11-49に、SRDemoアプリケーションでのファイル・アップロードの値変更イベントを処理する値変更リスナー・メソッドを示します。
例11-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
Integer svrId = (Integer)JSFUtils.getManagedBeanValue("userState.currentSvrId");
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コンポーネントを使用して、フォームを送信します。例11-50に、SRDemoファイル・アップロード・フォーム内のcommandButtonコードと、ページのバッキングBean内のアクション・メソッド・コードを示します。
例11-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コンポーネントを追加します。ポップアップ・ダイアログを閉じる方法の詳細は、11.3.1.3項「ダイアログ・ページを作成し、ダイアログ値を返す」を参照してください。例11-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がスローされます。デフォルト値を変更するには、11.6.4項「ファイル・アップロードの初期化パラメータの構成」を参照してください。
ADF Facesのファイル・アップロードを使用する場合は、次の点を考慮してください。
ほとんどのアプリケーションでは、デフォルトのUploadedFileProcessorインスタンスを置き換える必要はありません。ただし、アプリケーションで非常に大きなファイルのアップロードをサポートする必要がある場合は、デフォルトのプロセッサをカスタムのUploadedFileProcessor実装で置き換えることもできます。詳細は、11.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です。
例11-52に、web.xml内で使用するファイル・アップロードのコンテキスト初期化パラメータを示します。
例11-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>要素を使用してカスタム実装を指定します。例11-53に、カスタムのUploadedFileProcessor実装を登録するためのコードを示します。
例11-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の選択リスト・コンポーネントには、selectOneChoiceとselectOneListboxが含まれます。これらのコンポーネントは、標準的なJSFリスト・コンポーネントと同様に機能します。ただし、ADF Facesのリスト・コンポーネントには追加の機能(ラベルとメッセージの表示、自動フォーム送信、部分ページ・レンダリングのサポートなど)が備わっています。
SRDemoアプリケーションのSRSearchページでは、ユーザーが検索を実行するサービス・リクエストのタイプを選択できるように、selectOneChoiceコンポーネントを使用しています。selectOneChoiceコンポーネントを使用すると、選択する項目の静的リストを提供できます。また、動的に値が移入されるリストを作成することもできます。どちらの場合も、f:selectItemsタグを使用して、表示および選択する項目を指定します。
SRSearchページでは、ユーザーが検索を実行するサービス・リクエストのタイプを選択できるように、selectOneChoiceコンポーネントを使用しています。たとえば、ユーザーは、すべてのサービス・リクエストを検索対象にせず、未処理、保留中または処理済のステータスを持つリクエストに対して限定検索を実行することができます。図11-19は、selectOneChoiceコンポーネントを使用したSRDemoアプリケーション内の検索フォームを示しています。
検索フォームは、パラメータを取るメソッドを使用して作成します。パラメータを使用して検索を作成する方法の詳細は、10.8項「検索ページの作成」を参照してください。次の手順では、パラメータを使用せずに、固定の値リストにバインドされたドロップダウン・リストを作成する方法について説明します。
データ・コントロール・パレットを使用して固定の値リストにバインドされたドロップダウン・リストを作成する手順:
データ・コントロール・パレットから、ビジネス・サービス・メソッドを拡張し、データ・コレクションを返すメソッド戻りを拡張します。目的のデータ・コレクション属性をページ上にドラッグ・アンド・ドロップし、ポップアップ・メニューから「作成」→「単一選択」→「ADF選択肢を1つ選択」を選択します。「リスト・バインディング・エディタ」が表示されます(図11-20を参照)。
サービス・リクエスト・ステータスの例を使用して、findAllServiceRequest()を拡張してからServiceRequestを拡張し、status属性をドラッグ・アンド・ドロップします。ユーザーが1つのサービス・リクエスト・タイプを対象に検索できるように、ServiceRequestデータ・コレクション(findAllServiceRequest()メソッドにより返されるすべてのリクエストのコレクション)に対してstatus属性を使用します。
「リスト・バインディング・エディタ」で「固定リスト」を選択します。次に「ベース・データソース属性」ドロップダウン・リストからstatus属性を選択します。
「固定リスト」オプションを使用すると、ユーザーは、事前定義されたリストから値を選択できます。別のデータ・コレクションから値を取得せずに、自分でコーディングした値でデータ・オブジェクト属性を更新する場合は、このオプションを使用すると便利です。
リストから値を選択すると、「ベース・データソース属性」に、選択した値に更新されるバインド済データ・コレクションの属性が表示されます。
「一連の値」ボックスに次の値を入力します。[Enter]を押して値を設定してから、次の値を入力します。
Open
Pending
Closed
値を入力する順序は、実行時にselectOneChoiceコントロールに項目が表示される順序です。
「リスト・アイテム」セクションで、「「選択なし」アイテム」ドロップダウン・リストから「ラベル付きアイテムを含める」を選択します。次に、その横のボックスにAny Statusと入力します。
selectOneChoiceコンポーネントではnull値がサポートされています。このため、ユーザーが項目を選択しない場合、その項目のラベルがブランクとして表示され、コンポーネントの値がデフォルトで空の文字列に設定されます。ブランクまたは空の文字列を使用せずに、null値を表す文字列を指定できます。デフォルトでは、手順3で定義した値リストの先頭に新しい文字列が表示されます。
|
ヒント: SRDemoアプリケーションでは、findServiceRequestSearch(Integer, String, String)メソッドに、3つのパラメータ(1つはstatusParam)に基づいてサービス・レコードを検索して返すためのロジックが含まれています。各メソッド・パラメータには1つの変数が関連付けられています。変数イテレータおよび変数の詳細は、10.8.2項「パラメータ・メソッド使用時に行われる処理」を参照してください。
(10.8.1項「検索フォームの作成方法」の説明に従って)パラメータを持つメソッドを使用して検索フォームを作成した場合、ステータス・フィールドに対して作成した |
データ・コントロール・パレットからドラッグ・アンド・ドロップを行うと、JDeveloperによって様々な処理が自動的に行われます。データ・コントロール・パレットの使用時に実行される処理と作成される内容の詳細は、5.2.3項「データ・コントロール・パレットの使用時に行われる処理」を参照してください。
例11-54に、「リスト・バインディング・エディタ」を完了した後のselectOneChoiceコンポーネントのコードを示します。
例11-54 バインディングを完了した後のselectOneChoiceコンポーネント
<af:selectOneChoice value="#{bindings.ServiceRequeststatus.inputValue}"
label="#{bindings.ServiceRequeststatus.label}: ">
<f:selectItems value="#{bindings.ServiceRequeststatus.items}"/>
</af:selectOneChoice>
選択項目のリストを提供するためのf:selectItemsタグは、バインディング・コンテナ内のServiceRequeststatusリスト・バインディング・オブジェクトのitemsプロパティにバインドされています。
JDeveloperにより、ページ定義ファイル(たとえば、SRSearchPageDef.xml)のbindings要素内にリスト・バインディング・オブジェクト定義が追加されます(例11-55を参照)。
例11-55 ページ定義ファイル内の固定ドロップダウン・リストに対するリスト・バインディング・オブジェクト
<bindings>
...
<list id="ServiceRequeststatus" IterBinding="findAllServiceRequestIter"
ListOperMode="0" StaticList="true" NullValueFlag="1">
<AttrNames>
<Item Value="status"/>
</AttrNames>
<ValueList>
<Item Value="Any Status"/>
<Item Value="Open"/>
<Item Value="Pending"/>
<Item Value="Closed"/>
</ValueList>
</list>
...
</bindings>
id属性は、リスト・バインディング・オブジェクトの名前を指定します。IterBinding属性は、イテレータ・バインディング・オブジェクトを指定します。これは、findAllServiceRequest()メソッドにより返されたコレクションを公開し、反復処理を実行します。AttrNames要素は、イテレータにより返される属性を定義します。ValueList要素は、選択可能な固定の値リストが実行時に表示されることを指定します。
ページ定義ファイルおよびADFデータ・バインディング式の詳細は、5.5項「ページ定義ファイルでの作業」および5.6項「ADFデータ・バインディングEL式の作成」を参照してください。
静的リストから値を取得せずに、実行時にselectOneChoiceコンポーネントに動的に値を移入することもできます。動的リストにバインドされたドロップダウン・リストの作成手順は、固定リストにバインドされたドロップダウン・リストの作成手順とほとんど同じです。違う点は、2つのデータソース(動的な値リストを提供するリスト・データ・コレクション用のデータソースと、ユーザーの選択に基づいて更新されるベース・データ・コレクション用のデータソース)を定義することです。
データ・コントロール・パレットを使用して動的な値リストにバインドされたドロップダウン・リストを作成する手順:
データ・コントロール・パレットから、ビジネス・サービス・メソッドを拡張し、データ・コレクションを返すメソッド戻りを拡張します。次に、詳細コレクションを返すアクセッサ戻りを拡張します。目的の属性をページ上にドラッグ・アンド・ドロップし、ポップアップ・メニューから「作成」→「単一選択」→「ADF選択肢を1つ選択」を選択します。「リスト・バインディング・エディタ」が表示されます(図11-21を参照)。
たとえば、サービス・リクエストの検索前にユーザーが製品を選択できるようにする場合、「findAllServiceRequest()」を拡張し、続いて「ServiceRequest」および「product」を拡張します。次にname属性をドラッグ・アンド・ドロップします。ユーザーが製品名に基づいてサービス・リクエストを検索できるように、「製品」詳細コレクションにはname属性を使用します。
|
注意: リスト・データ・コレクションとベース・データ・コレクションはマスター/ディテール関係になっている必要はありませんが、リスト・データ・コレクション内の項目のタイプはベース・データ・コレクション属性と同じである必要があります。 |
「リスト・バインディング・エディタ」で「動的リスト」を選択します。
「ベース・データソース」ドロップダウン・リストから、ユーザーが選択したリスト値で更新されるデータ・コレクションを選択します。たとえば、「ServiceRequest: SRPublicFacade.findAllServiceRequest.product」を選択します。
「リスト・データソース」ドロップダウン・リストから、動的に値リストを提供するデータ・コレクションを選択します。たとえば、「Product: SRPublicFacade findAllProduct」を選択します。
マッピング領域で、「ベース・データソース属性」から「name」を選択し、「リスト・データソース属性」から「name」を選択します。これにより、更新するベース・ソース属性にリスト・ソース属性がマッピングされます。
「リスト・アイテム」セクションで、「属性の表示」ドロップダウン・リストから「name」を選択します。これにより、ユーザーに表示される値がリストに移入されます。
データ・コントロール・パレットからドラッグ・アンド・ドロップを行うと、JDeveloperによって様々な処理が自動的に行われます。データ・コントロール・パレットの使用時に実行される処理と作成される内容の詳細は、5.2.3項「データ・コントロール・パレットの使用時に行われる処理」を参照してください。
例11-56に、「リスト・バインディング・エディタ」を完了した後のselectOneChoiceコンポーネントのコードを示します。
例11-56 バインディングを完了した後のselectOneChoiceコンポーネント
<af:selectOneChoice value="#{bindings.Productname.inputValue}"
label="#{bindings.Productname.label}">
<f:selectItems value="#{bindings.Productname.items}"/>
</af:selectOneChoice>
選択項目のリストを提供するためのf:selectItemsタグは、バインディング・コンテナ内のProductnameリスト・バインディング・オブジェクトのitemsプロパティにバインドされています。ADFデータ・バインディング式の詳細は、5.6項「ADFデータ・バインディングEL式の作成」を参照してください。
JDeveloperにより、ページ定義ファイル(たとえば、SRDemopage.xml)のbindings要素内にリスト・バインディング・オブジェクト定義が追加されます(例11-57を参照)。
例11-57 ページ定義ファイル内の動的ドロップダウン・リストに対するリスト・バインディング・オブジェクト
<bindings>
...
<list id="Productname" IterBinding="productIterator" StaticList="false"
ListOperMode="0" ListIter="findAllProductIter"..>
<AttrNames>
<Item Value="name"/>
</AttrNames>
<ListAttrNames>
<Item Value="name"/>
</ListAttrNames>
<ListDisplayAttrNames>
<Item Value="name"/>
</ListDisplayAttrNames>
</list>
...
</bindings>
id属性は、リスト・バインディング・オブジェクトの名前を指定します。IterBinding属性は、イテレータ・バインディング・オブジェクトを指定します。これは、findAllProduct()メソッドにより返されたコレクションを公開し、反復処理を実行します。AttrNames要素は、イテレータにより返されるベース・データソース属性を定義します。ListAttrNames要素は、イテレータにより返されたベース・データソース属性にマッピングされるリスト・データソース属性を定義します。ListDisplayAttrNames要素は、ユーザーに表示される値をリストに移入するリスト・データソース属性を定義します。
ページ定義ファイルの詳細は、5.5項「ページ定義ファイルでの作業」を参照してください。
selectOneChoiceコンポーネントで、ユーザーが選択した項目の値を収容する変数を使用することもできます。SRSkillsページ(後出の図11-25)で、管理者がドロップダウン・リストからスタッフ・メンバーを選択すると、シャトル・コンポーネント内に、そのメンバーに割り当てられた製品スキルが表示されます。ユーザーが項目を選択すると、SRSkillsページのselectOneChoiceコンポーネントによりページ定義ファイル内の変数に値が移入されます。
次の手順では、ページ定義ファイルに手動で変数を追加する方法を示します。
ページ定義ファイル内に変数イテレータと変数を作成する手順:
selectOneChoiceコンポーネントを使用するJSFページのページ定義ファイル(たとえば、<pageName>PageDef.xml)を開きます。
構造ウィンドウで最上位ノードを右クリックし、「<pageName>PageDefの中に挿入」→「実行可能ファイル」を選択して、「実行可能ファイル」ノードを追加します(まだ追加されていない場合)。
構造ウィンドウで「実行可能ファイル」を右クリックし、「実行可能ファイルの中に挿入」→「variableIterator」を選択して、「変数」ノードを追加します(まだ追加されていない場合)。
構造ウィンドウで「変数」を右クリックし、「実行可能ファイルの中に挿入」→「変数」を選択します。
「変数の挿入」ダイアログで、変数の名前とタイプを入力します。たとえば、選択されたスタッフ・メンバーに関するデータを収容する変数を作成する場合は、名前にsomeStaffIdVarを入力し、タイプにjava.lang.Integerを入力します。
例11-58に、変数を作成した後のページ定義ファイルを示します。
例11-58 ページ定義ファイル内の変数イテレータと変数
<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
version="10.1.3.36.61" id="app_management_untitled4PageDef"
Package="oracle.srdemo.view.pageDefs">
<executables>
<variableIterator id="variables">
<variable Name="someStaffIdVar" Type="java.lang.Integer"/>
</variableIterator>
</executables>
</pageDefinition>
変数イテレータと変数の詳細は、5.5.2.2項「executables要素で定義されたバインディング・オブジェクト」を参照してください。
次の手順では、変数を使用して、ユーザーが動的リストからスタッフ・メンバーの名前を選択できるドロップダウン・リストを作成する方法を示します。
動的ドロップダウン・リストを作成する手順:
selectOneChoiceコンポーネントを追加するJSFページを開きます。
データ・コントロール・パレットから「findAllStaff()」→「ユーザー」を拡張します。userId属性をページ上にドラッグ・アンド・ドロップし、ポップアップ・メニューから「作成」→「単一選択」→「ADF選択肢を1つ選択」を選択します。
「リスト・バインディング・エディタ」で、「ベース・データソース」ドロップダウン・リストから「変数」を選択します。
「動的リスト」を選択します。
「リスト・データソース」ドロップダウン・リストから、「ユーザー: SRPublicFacade:findAllStaff」を選択します。
マッピング領域で、「ベース・データソース属性」ドロップダウン・リストから「someStaffIdVar」を選択し、「リスト・データソース属性」から「userId」を選択します。
「リスト・アイテム」セクションで、「属性の表示」ドロップダウン・リストから「複数の選択」を選択し、「複数の表示属性の選択」ダイアログ内の「表示する属性」リストに「firstName」および「lastName」を追加します。
「「選択なし」アイテム」ドロップダウン・リストから、「ラベル付きアイテムを含める」を選択します。
selectManyShuttleおよびselectOrderShuttleコンポーネントは、2つのリスト・ボックスと、ユーザーが先行(選択可能)リスト・ボックスから複数の項目を選択して項目を後続(選択済)リスト・ボックスに移動または往復させる(またはその逆を行う)ことのできるボタンをレンダリングします。図11-22は、レンダリングされたselectManyShuttleコンポーネントの例を示しています。リスト・ボックスの上に表示されるヘッダーには任意のテキストを指定できます。
|
注意: 項目をリスト間で往復させるには、提供された「Move」および「Remove」ボタンを使用する以外に、いずれかのリストで項目をダブルクリックする方法もあります。一方のリストで項目をダブルクリックすると、その項目はもう1つのリストに移動します。たとえば、先行リスト内の項目をダブルクリックすると、その項目は自動的に後続リストに移動します。その逆も同様です。 |
selectManyShuttleとselectOrderShuttleで違う点は、selectOrderShuttleコンポーネントでは、ユーザーは横の上下矢印を使用して後続リスト・ボックス内の項目を並替えできることのみです(図11-23を参照)。
SRDemoアプリケーションでは、SRSkillsページで、管理者が技術者に製品スキルを割り当てることができるようにselectManyShuttleコンポーネントを使用しています。図11-24は、サンプル・アプリケーションで作成されるSRSkillsページを示しています。左側の先行リスト・ボックスには「washing machine」や「dryer」などの製品が表示され、右側の後続リスト・ボックスには技術者に保守スキルがある製品が表示されます。
製品スキルの割当てを確認して変更するには、管理者がまず、シャトル・コンポーネントの上のドロップダウン・リストから技術者の名前を選択します。すると、アプリケーションにより、後続リスト内にその技術者の既存のスキル割当てが表示されます(図11-25)。
先行リストと後続リストの下に、製品の説明を表示するためのオプションのボックスがあります。製品の説明を表示するには、管理者がいずれかのリスト・ボックスから項目を選択します。するとアプリケーションにより、そのリストの下のボックスに製品の説明が表示されます。
新しいスキル割当てを追加するには、管理者が先行リスト(「Available Products」)から製品を選択し、「Move」ボタンをクリックします。
「Assigned Skills」リストからスキルを削除するには、管理者が後続リストから製品を選択し、「Remove」ボタンをクリックします。
他のADF Faces選択リスト・コンポーネントと同様に、selectManyShuttleコンポーネントはf:selectItemsタグを使用して、先行リスト内で表示および選択するための使用可能な項目のリストを提供できます。
f:selectItemsタグをバインドする前に、シャトルの有効な製品(スキル)のリストを保持するクラスと、技術者に割り当てられた(選択された)製品の索引を作成する必要があります。このクラスでは、ページのバインディング・コンテナを使用して、シャトルの先行リストに移入するための製品マスター・リストを取得する必要があります。例11-59に、SRSkillsページでシャトル・コンポーネントの移入および選択の状態を管理するために作成されたSkillsHelperクラスを示します。
例11-59 SkillsHelperクラス
package oracle.srdemo.view;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
...
public class SkillsHelper {
private BindingContainer bindings;
private List<Product> productMasterList;
private List <SelectItem> allProducts;
private HashMap productLookUp;
private int[] selectedProducts;
private int skillsFor;
private boolean skillsChanged = false;
public List<SelectItem> getAllProducts() {
if (allProducts == null) {
OperationBinding oper = getBindings().getOperationBinding("findAllProduct");
productMasterList = (List<Product>)oper.execute();
int cap = productMasterList.size();
allProducts = new ArrayList(cap);
productLookUp = new HashMap(cap);
//for(Product prod: products) {
for (int i=0;i<cap;i++){
Product prod = productMasterList.get(i);
SelectItem item = new SelectItem(i, prod.getName(),prod.getDescription());
allProducts.add(item);
productLookUp.put(prod.getProdId(), i);
}
}
return allProducts;
}
public void setAllProducts(List<SelectItem> allProducts) {
this.allProducts = allProducts;
}
public void setSelectedProducts(int[] selectedProducts) {
skillsChanged = true;
this.selectedProducts = selectedProducts;
}
public int[] getSelectedProducts() {
Integer currentTechnician = (Integer)ADFUtils.getBoundAttributeValue(getBindings(),"currentTechnician");
if (currentTechnician != null){
if (skillsFor != currentTechnician.intValue()){
skillsFor = currentTechnician.intValue();
skillsChanged = false;
OperationBinding getAssignedSkillsOp = getBindings().getOperationBinding("findExpertiseByUserId");
List<ExpertiseArea> skills = (List<ExpertiseArea>)getAssignedSkillsOp.execute();
selectedProducts = new int[skills.size()];
for (int i=0;i<skills.size();i++){
Integer lookup = (Integer)productLookUp.get(skills.get(i).getProdId());
selectedProducts[i] = lookup.intValue();
}
}
}
return selectedProducts;
}
public List<Integer> getSelectedProductIds(){
ArrayList prodIdList = new ArrayList(selectedProducts.length);
for (int i:selectedProducts){
prodIdList.add(productMasterList.get(i).getProdId());
}
return prodIdList;
}
public void setBindings(BindingContainer bindings) {
this.bindings = bindings;
}
public BindingContainer getBindings() {
return bindings;
}
public void setSkillsChanged(boolean skillsChanged) {
this.skillsChanged = skillsChanged;
}
public boolean isSkillsChanged() {
return skillsChanged;
}
}
SkillsHelperクラス内で対象となるメソッドは、getAllProducts()およびgetSelectedProducts()です。
getAllProducts()メソッドは、シャトルの先行リストにデータを移入するメソッドです。このメソッドが初めてコールされると、SRPublicFacadeセッションBeanのfindAllProduct()メソッドが起動され、製品のリストがSelectItemオブジェクトの配列リスト内にキャッシュされます。また、getAllProducts()メソッドは、製品IDに基づいてリスト・アイテム索引番号の逆検索を実行できるようにするハッシュマップを保持します。
getSelectedProducts()メソッドは、シャトルの後続リストに表示される項目リストを定義するint値の配列を返します。また、このメソッドは、(シャトルの上のドロップダウン・リストから)現在選択されている技術者が変更されたかどうかを確認します。現在選択されている技術者が変更された場合、SRAdminFacadeセッションBeanのfindExpertiseByUserId()メソッドが起動され、新しい現行技術者のスキル・リストが取得されてシャトルの後続リスト内に表示されます。
SkillsHelperクラスは、sessionスコープのマネージドBean(skillsHelper)として保持されます。例11-60に、SRDemoアプリケーションでシャトル・コンポーネントを使用するために構成されたマネージドBeanを示します。
例11-60 faces-config.xmlファイル内のシャトル・コンポーネントに対するマネージドBean
<managed-bean> <managed-bean-name>skillsHelper</managed-bean-name> <managed-bean-class>oracle.srdemo.view.SkillsHelper</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> <managed-property> <property-name>bindings</property-name> <value>#{data.SRSkillsPageDef}</value> </managed-property> </managed-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-bean>
SRSkillsページのすべてのバインディングは、ファイルapp_management_SRSkillsPageDef.xml内に定義されています。その参照は、SkillsHelperクラスに挿入されます。例11-61に、SRSkillsページのページ定義ファイルを示します。
例11-61 SRSkillsページのページ定義ファイル
<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
version="10.1.3.36.2" id="SRSkillsPageDef"
Package="oracle.srdemo.view.pageDefs"..>
<executables>
<methodIterator id="findAllStaffIter" Binds="findAllStaff.result"
DataControl="SRPublicFacade" RangeSize="-1"
BeanClass="oracle.srdemo.model.entities.User"/>
<methodIterator id="findAllProductIter" Binds="findAllProduct.result"
DataControl="SRPublicFacade" RangeSize="-1"
BeanClass="oracle.srdemo.model.entities.Product"/>
<variableIterator id="variables">
<variable Name="selectedStaffIdVar" Type="java.lang.Integer"/>
</variableIterator>
</executables>
<bindings>
<methodAction id="findAllStaff" InstanceName="SRPublicFacade.dataProvider"
DataControl="SRPublicFacade" MethodName="findAllStaff"
RequiresUpdateModel="true" Action="999"
ReturnName="SRPublicFacade.methodResults.SRPublicFacade_dataProvider_findAllStaff_result"/>
<methodAction id="findAllProduct" InstanceName="SRPublicFacade.dataProvider"
DataControl="SRPublicFacade" MethodName="findAllProduct"
RequiresUpdateModel="true" Action="999"
ReturnName="SRPublicFacade.methodResults.SRPublicFacade_dataProvider_findAllProduct_result"/>
<attributeValues IterBinding="variables" id="currentTechnician">
<AttrNames>
<Item Value="selectedStaffIdVar"/>
</AttrNames>
</attributeValues>
<methodAction id="findExpertiseByUserId"
InstanceName="SRAdminFacade.dataProvider"
DataControl="SRAdminFacade" MethodName="findExpertiseByUserId"
RequiresUpdateModel="true" Action="999"
ReturnName="SRAdminFacade.methodResults.SRAdminFacade_dataProvider_findExpertiseByUserId_result">
<NamedData NDName="userIdParam"
NDType="java.lang.Integer"
NDValue="#{bindings.currentTechnician.inputValue}"/>
</methodAction>
<list id="findAllStaffList" StaticList="false" ListOperMode="0"
IterBinding="variables" ListIter="findAllStaffIter"
NullValueFlag="1" NullValueId="findAllStaffList_null">
<AttrNames>
<Item Value="selectedStaffIdVar"/>
</AttrNames>
<ListAttrNames>
<Item Value="userId"/>
</ListAttrNames>
<ListDisplayAttrNames>
<Item Value="firstName"/>
<Item Value="lastName"/>
</ListDisplayAttrNames>
</list>
<methodAction id="updateStaffSkills"
InstanceName="SRAdminFacade.dataProvider"
DataControl="SRAdminFacade" MethodName="updateStaffSkills"
RequiresUpdateModel="true" Action="999">
<NamedData NDName="userId"
NDValue="${bindings.currentTechnician.inputValue}"
NDType="java.lang.Integer"/>
<NamedData NDName="prodIds" NDValue="${skillsHelper.selectedProductIds}"
NDType="java.util.List"/>
</methodAction>
</bindings>
</pageDefinition>
次の手順では、関連するバインディング(例11-59のSkillsHelperクラスと同様のクラス)をすでに作成し、faces-config.xml内で必要なマネージドBeanをすでに構成してある(例11-60を参照)ことを前提としています。
シャトル・コンポーネントを作成する手順:
コンポーネント・パレットの「ADF Faces Core」ページから、「SelectManyShuttle」をページ上にドラッグ・アンド・ドロップします。JDeveloperにより、「SelectManyShuttleの挿入」ダイアログが表示されます(図11-26)。
「リストにバインド(項目を選択)」を選択し、「バインド」をクリックして「式ビルダー」を開きます。
「式ビルダー」で、「JSFマネージドBean」→「スキル」を拡張します。「allProducts」をダブルクリックして、式#{skillsHelper.allProducts}を構築します。「OK」をクリックします。
これにより、f:selectItemsタグが、シャトルの先行リストにデータを移入するgetAllProducts()メソッドにバインドされます。
「SelectManyShuttleの挿入」ダイアログで、「共通プロパティ」をクリックします。「値」フィールドの横の「バインド」をクリックして、再び「式ビルダー」を開きます。
「式ビルダー」で、「JSFマネージドBean」→「スキル」を拡張します。「selectedProducts」をダブルクリックして、式#{skillsHelper.selectedProducts}を構築します。「OK」をクリックします。
これにより、selectManyShuttleのvalue属性が、シャトルの後続リストのリスト・アイテムを定義するint値の配列を返すgetSelectedProducts()メソッドにバインドされます。
例11-62に、「SelectManyShuttleの挿入」ダイアログを完了した後のselectManyShuttleコンポーネントのコードを示します。
例11-62 SRSkills.jspxファイル内のselectManyShuttleコンポーネント
<af:selectManyShuttle value="#{skillsHelper.selectedProducts}" ... <f:selectItems value="#{skillsHelper.allProducts}"/> </af:selectManyShuttle>
シャトル・コンポーネントの使用方法の詳細は、次のサイトでADF Faces Coreのタグを参照してください。
SRSkillsページが最初にアクセスされると、変数イテレータが実行され、その変数selectedStaffIdVarがインスタンス化されます。この時点では、変数には値は含まれていません。管理者がドロップダウン・リストから名前を選択すると、変数に値が移入されます。すると、属性バインディングは、NamedData要素の値のEL式を使用してfindExpertiseByUserId()メソッドのパラメータuserIdParamの値を提供できます。
「Save skill changes」コマンド・ボタン(図11-24を参照)をクリックすると、現在の技術者のユーザーIDと、関連付けられている製品IDの配列(割り当てられているスキル)が取得され、SRAdminFacade BeanのupdateStaffSkills()メソッドに送信されます。
例11-63に、SRSkills.jspxページ内のcommandButtonコンポーネントのコードを示します。