UIX開発者ガイド | ![]() 目次 |
![]() 前へ |
![]() 次へ |
堅牢なアプリケーションでも常にエラーは発生します。Webアプリケーションのエラー処理では、従来のクライアント/サーバー・アプリケーションには見られない方法が使用されることがあります。このトピックでは、データ・バインドおよびイベント処理中のエラーの処理方法、クライアントへのエラーの表示方法、ユーザーの作業状況を改善するためのクライアント側の検証の使用方法を学びます。
ここでは、次の項目について説明します。
UIXのデータ・バインドに関して、注意すべき問題は次のようなことです。データ・バインドは、ページのレンダリング時に行われます。値は、必要な場合のみ取得されます。また、DataProvider
がDataObject
をリクエストされるのは、最初にそのDataObject
が必要となった場合のみです。ただし、HTMLの行が一度インターネット上に送信されると、取り戻すことはできません。そうすると、エラー・ページにリダイレクトすることはできず、失敗した部分に適切なエラー・セクションをレンダリングすることもできません。処理を続行し、最善を尽くしてページを完成させるしかありませんが、ページの途中にスタック・トレースを書き出すことになり、ユーザーを混乱させる原因にもなるので問題があります。
DataProvider
、DataObject
、BoundValue
、およびアプリケーションのその他すべてのデータ・バインド・コードが雑に作られているように見えます。これらのいずれでも障害が認識されず、常に有効な結果を返すことができるかのようです。しかし、これは事実ではありません。代替手段が豊富にあります。
最も単純なオプションは何もしないことです。例外を捕捉し、ErrorLog
APIに記録してから削除します。次に例を示します。
public class SimpleDataObject implements DataObject
{
public Object selectValue(RenderingContext context,
Object key)
{
try
{
// Try to get the value
return _aMethodThatMayFail(context, key);
}
catch (SQLException sqle)
{
// Oh no - an error! But just log it...
context.getErrorLog().logError(sqle);
// and return null.
return null;
}
}
}
このようなコードを見ると、問題点が理解しやすいはずです。障害が発生しても何も知らせないよりも、適切なエラー・メッセージをユーザーに表示する方が賢明です。空白のセルやフィールドを表示するとユーザーが混乱するだけでなく、さらに悪いことに、ユーザーが誤ったデータを送信する場合もあります。
ただし、高水準に合せてすべてのコードを作成するのは、逆効果の場合もあります。多くのエラーを適切に処理しようとすればするほど、大量のエラー処理コードを記述することになります。また、エラー処理コードは通常アプリケーション内で最もテストされないコードです。このため、最も問題の多いコードだとも言えます。
処理していない例外がすでにある可能性もあります。たとえば、NullPointerException
を常に捕捉していない可能性もあります。この例外は、予期しないイベントが発生したことを意味します。予期されるエラー(データベース接続の障害など)のみではなく、予期しないエラー(NullPointerException
またはMissingResourceException
など)をユーザーに示す方法をアプリケーションに設計していると、いつまでたってもコーディングは終了しません。どこで線を引くかを決めるのは困難ですが、RuntimeException
の処理をしないのは妥当な選択でしょう。
NullPointerException
およびその他のRuntimeException
が、UIXではどのように処理されるか説明します。DataProvider
およびDataObject
でスローされるすべての例外は、前述の例と同様に、自動的に捕捉され、エラーが記録されて、nullが返されます。例外が届くと、これが唯一の最適な代替手段になります。
このエラーはまったく処理されずに削除されるわけではなく、サーブレット・エンジンのエラー・ログに記録されます(デフォルトでは、UIXのエラー・ログはメッセージとエラーをサーブレット・エンジンのエラー・ログに送信しますが、別の方針で置き換えることもできます)。本番アプリケーションでも、エラー・ログを監視する必要があります。ただし、エラーはユーザーには表示されないため、問題があることをユーザーが画面から知ることはできません。
第1の方法の大きな問題は、エラーが発生したときにユーザーへのフィードバックがないことです。エラーを示すヒントもなく空の表やフィールドが表示されるので、ユーザーが疑問を抱くことになります。最初の方法を少し変更することで、この問題を解決できます。エラーが発生するとすぐに、エラーを記録するだけではなく、エラーを格納し、データからエラーを返すことをサポートします。たとえば、次のuiXMLを使用します。
<dataScope>
<!-- Get some data -->
<provider>
<data name="dataForTable">...</data>
</provider>
<contents>
<!-- Put the data in a table -->
<table>...</table>
</contents>
</dataScope>
ここで、表の後にコードを追加します。
...
<contents>
<!-- Put the data in a table -->
<table>...</table>
<!-- And warn the user of an error if one happened, -->
<header messageType="error" data:rendered="error@dataForTable">
<contents>
An error occurred:
<styledText data:text="exception@dataForTable"/>
</contents>
</header>
</contents>
...
このuiXMLコードによってエラー・メッセージが追加されます。これがレンダリングされるのは、errorフラグが設定された場合のみです(rendered属性の詳細は、「uiXMLページの動的構造」を参照してください)。次に例外が表示されます。必要に応じて、出力内容をわかりやすくできます。この処理を行うためのデータソースの更新方法を次に示します。
public class SimpleDataObject2 implements DataObject
{
public Object selectValue(RenderingContext context,
Object key)
{
// Have we encountered an error yet?
if ("error".equals(key))
{
return (_exception == null) ? Boolean.FALSE : Boolean.TRUE;
}
// What exception have we seen?
else if ("exception".equals(key))
{
if (_exception == null) return null;
return _exception.toString();
}
try
{
// Try to get the value
return _aMethodThatMayFail(context, key);
}
catch (SQLException sqle)
{
// Oh no - an error! But log it...
context.getErrorLog().logError(sqle);
// ...store it...
_exception = sqle;
// and return null.
return null;
}
}
// Cached exception
private Exception _exception;
}
これは前述の例によく似ていますが、ログを記録した後に例外をユーザーに対して解放しないで一度格納します。また、このエラーの状態を公開する2つの新しいキー("error"、"exception")をDataObject
に追加しています。
この方法はかなり優れています。エラーがない場合のオーバーヘッドが大変少なく、エラーが発生するとユーザーに知らされます。短所としては、誤っている可能性があるデータが表示されること、ユーザーがページをかなり下までスクロールしないとエラーに気付かない可能性があることと、各DataObject
にエラー処理コードを設定する必要があることです。最後の問題は、エラー格納コードを汎用にすれば解消できます。ただし、ユーザー側の問題はこの方法特有の現象です。
ページまたはセクションの一番上に、つまりデータが実際に使用される前にデータ・バインド・エラーを表示するには、2つの方法があります。1つは、すべてのデータを一度に取得して、レンダリングの途中に障害が発生しないようにすることです。この1番目の方法について説明し、2番目の方法については次のセクションで説明します。
uiXMLの<method>
要素(またはJavaのMethodDataProvider
クラス)でこの方法をどのように実行するのか説明します。データを取得するメソッドを定義します。このメソッドは取得に失敗すると、エラー・コードのみを含む単純なDataObject
をかわりに返します。
import oracle.cabo.ui.data.DictionaryData;
...
static public DataObject getResultSet(
RenderingContext context, String namespace, String name)
{
DictionaryData data = new DictionaryData();
try
{
ResultSet result = _getJDBCResultSet(...);
data.put("ename", result.getObject("ENAME"));
data.put("id", result.getObject("ID");
data.put("manager", result.getObject("MANAGER");
// .. etc...
// And mark that we didn't have any errors
data.put("errors", "no");
}
catch (SQLException sqle)
{
// No - an error. Mark the error case, store
// the exception, and return.
data.put("errors", "yes");
data.put("exception", sqle.toString());
}
return data;
}
このコードでは、次の処理が実行されます。
DictionaryData
を作成します。このクラスは、少量の固定データ・セットを格納するための単純な方法です。例外はスローされません。
ResultSet
を取得します。正常に取得した場合は、次の処理を続けます。
no
をerrors
キーに格納します。これをUIで使用して、エラーのないUIをレンダリングします。
yes
をerrors
キーに格納します。これをUIで使用して、エラー発生時のUIをレンダリングします。
このデータを使用するUIを次に示します。
<dataScope>
<provider>
<!-- Get the result set. Note that this _is not_ enough
to trigger actually calling getResultSet(); that won't
happen until we try to access a piece of the result -->
<data name="resultSet">
<method class="...." method="getResultSet"/>
</data>
</provider>
<contents>
<!-- Now: did we have an error? Ask for the "errors" key
first - this will at last trigger the call to
getResultSet(), which will get all of the data and
set the "errors" key. -->
<switcher data:childName="errors@resultSet">
<case name="no">
<!-- No errors: show the result set -->
<labeledFieldLayout>
<contents>
<messageStyledText name="id" prompt="Employee ID:"
data:text="id@resultSet"/>
<messageTextInput name="ename" prompt="Employee name:"
data:text="ename@resultSet"/>
</contents>
</labeledFieldLayout>
</case>
<case name="yes">
<!-- An error. Show the exception -->
<header messageType="error">
<contents>
<styledText data:text="exception@resultSet"/>
</contents>
</header>
</case>
</switcher>
</contents>
</dataScope>
ここでは適切なuiXMLのコードが使用されています。内容を説明します。
<data name="resultSet">
<method class="...." method="getResultSet"/>
</data>
ここで重要なのは、データ・プロバイダを参照するだけではデータがロードされないということです。そのプロバイダからデータがリクエストされるまで、ロードされません。
<switcher>
を使用してコンテンツのレンダリングを開始します(このBeanはJavaのSwitcherBean
に相当します)。この詳細は、「uiXMLページの動的構造」のトピックで説明しますが、ここでは、childName
文字列属性に基づいて、いくつかのケースに分岐するということを覚えておいてください。
<switcher data:childName="errors@resultSet">
<case name="no">
...
</case>
<case name="yes">
...
</case>
</switcher>
ここでは、errors
キーを使用してchildName
属性を実行します。前述したように、このキーは、すべてのデータを正常に取得したときにno
に設定され、エラーが発生するとyes
に設定されます。
<case>
内のuiXMLは、理解できるかと思います。エラーが発生した場合には、エラー・メッセージを表示します。正常に処理された場合は、予期されている結果を表示します。
(この方針を説明するためにDataProvider
を使用しましたが、独自にDataObject
を記述する場合でも、同様にDataProvider
を使用すると簡単です。この場合、それぞれのデータをDictionaryData
にコピーする必要はありません。この方針によって、最初から最後までのすべてのリクエストが正常終了するという規則を通せるようになります。)
この選択肢では、最も優れた出力結果が得られます。エラーがあると、出力が即時に中断され、ユーザーはすぐにエラー・メッセージを確認できます。ただし、開発に時間がかかり、アプリケーションのパフォーマンスのコストも高くなります。1つか2つの少量のデータのみが必要な場合でも、Javaコードに事前にすべてのデータを取得する必要があります。さらに、コードで作成可能なすべての情報のリストを記述し、そのリストを更新し続けることが必要になります。このようなコードの記述は煩雑な上にメンテナンスも手間がかかります。
これまでのすべての選択肢では、すべてのDataProvider
およびDataObject
にインテリジェント機能を追加する必要がありました。インテリジェント機能を追加せずに、UIXのフレームワークでエラー処理が可能になれば、処理は非常に簡単になります。これを簡単に実行できる<try>
要素があります。これはJavaのTryBean
に相当します。
データ・プロバイダにエラー条件を処理させるかわりに、セクションを<try>
要素で囲みます。次のDataProvider
は必ず失敗します。
static public DataObject throwException(
RenderingContext context,
String namespaceURI,
String name) throws Exception
{
throw new java.sql.SQLException("Big problem");
}
次に、このコードを使用するUIXを示します。
<try>
<contents>
<dataScope>
<provider>
<data name="noGood">
<method class="..." method="throwException"/>
</data>
</provider>
<contents>
<text text="This won't work: "/>
<text data:text="notAChance@noGood"/>
</contents>
</dataScope>
</contents>
</try>
この例を実行しても何も表示されません。<try>
を入力すると、出力のバッファリングがすぐに開始されます。ネットワーク上にデータを直接送信するかわりにバッファに格納します。セクションが終了してエラーがなければ、すぐにバッファがフラッシュされ、ネットワークに送信されます。(ただし、<try>
自体が別の<try>
の内側にない場合です。このように、<try>
はネストすることができます。)ただしエラーが発生すると、セクション全体が削除されます。
ユーザーにエラーを伝える場合(通常は適切な方法)は、<try>
で<catch>
セクションを使用します。また、エラー・オブジェクトがプロパティとしてRenderingContext
に格納されるので、そのデータをエラー・メッセージで使用できます。次に、<catch>
セクションも含めて完成させたUIXの例を示します。
<try>
<contents>
<dataScope>
<provider>
<data name="noGood">
<method class="..." method="throwException"/>
</data>
</provider>
<contents>
<text text="This won't work: "/>
<text data:text="notAChance@noGood"/>
</contents>
</dataScope>
</contents>
<!-- Catch the error if it happens -->
<catch>
<header messageType="error">
<!-- And for the text of the error message, just show
the exception that caused it -->
<boundAttribute name="text">
<contextProperty select="ui:currentThrowable"/>
</boundAttribute>
</header>
</catch>
</try>
レンダリング・コンテキストのui:currentThrowable
キーでThrowable
を取得したことに注意してください。<try>
によってエラーがここに格納されます。Javaでは、次のコードを使用してエラーにアクセスできます。
import oracle.cabo.ui.UIConstants;
...
RenderingContext ctxt = ...;
Throwable error = (Throwable)
ctxt.getProperty(UIConstants.MARLIN_NAMESPACE,
UIConstants.CURRENT_THROWABLE_PROPERTY);
エラーの表示を簡単にするために、uiXMLでは<catch>
の内部で使用できる<displayException>
要素が用意されています。例外のタイプおよびメッセージ(前述の例で表示できるものはこれだけです)を表示するだけでなく、スタック・トレースも参照できます。
<try>
<contents>
<!-- etc. -->
</contents>
<!-- Catch the error if it happens -->
<catch>
<displayException/>
</catch>
</try>
このようなバッファリングは、JSPで現在すでに行っている処理と類似しています。このため、JSPに精通している開発者は、Webアプリケーションのこの種の解決方法は当然だと思うでしょう。ビルトインJSPソリューションでは、ページ全体をバッファリングするか、まったくバッファリングしないかという2つの選択肢しかないという問題があります。ページの一部を選択してバッファリングするための簡単な方法がありません。(JSP 1.2仕様には、TryCatchFinally
タグ・インタフェースが含まれています。これを使用すると、カスタム・タグで問題に対処できます。UIXのソリューションはすべてのJSPバージョンで機能します。)ページ全体をバッファリングする場合は、ページ全体が生成されるまで、データがWebに送信されてブラウザに表示されるのをユーザーは待機する必要があります。選択式バッファリングでは、結果を得てからすぐに送信できます。
それでは、なぜ常に<try>
を使用しないのでしょうか。エラーをまったく無視することを除けば、すでに存在していたページにこの要素を追加するのは、確かに他のどの方法よりも簡単です。<try>
の最大の短所は効率が悪いことです。不必要に出力をバッファリングしなければ処理速度は速くなります。つまり、全ページのすべてのコンテンツを常にバッファリングするのは時間がかかります。そこで、選択した範囲に<try>
を指定します。つまり、<table>
または一連のフォーム要素を囲むように指定します。ただし、選択範囲を細かくしすぎないでください。また、個々のフォーム要素の前後に指定しないでください。バッファリングのオンとオフの切替えに時間がかかるためです。
2番目の短所は、1つのエラーによって、<try>
の内側のすべての出力が廃棄されることです。通常は、これは短所ではありません。誤った出力よりは出力がない方がよいからです。ただし、出力がないために問題の識別が困難になることがあります。特に、<catch>
セクションがない場合や、エラー・ログにアクセスできない場合です。これは、ユーザー使用時よりも開発時に問題になります。
BoundValue
、DataObject
を独自に作成して、DataProvider
とともにTryBean
を利用しようとすると問題に出会う場合があります。BoundValue
やDataObject
のインタフェースでは実際に例外をスローできないからです。これらのJavaのメソッドでは、常にRuntimeException
のサブクラスをスローできますが、検出する可能性があるIOException
、SQLException
、SAXException
またはその他の実際の例外をスローすることはできません。これらのメソッドで例外をスローできれば、処理は非常に簡単になります。よって、これに対処する方法が必要になります。
これを解決するのは、oracle.bali.share.util
パッケージのUnhandledException
クラスです。これはRuntimeException
のサブクラスであり、どのコードからもスローできます。また、任意のThrowable
またはException
をラップしてコンストラクタに渡すことができます。説明のためにDataObject
の簡単な例を示します。
public class SomeDataObject implements DataObject
{
public Object selectValue(RenderingContext context, Object key)
{
try
{
...
Try to get the data...
...
}
catch (SQLException sqle)
{
// We can't throw a SQLException from a DataObject. But
// we can throw an UnhandledException!
throw new UnhandledException(sqle);
}
}
}
前述のDataProvider
の例ではどのようにSQLException
をスローできているのか、疑問に思うかもしれません。DataProvider
は<method>
要素を介してアクセスされるために少し違った形で設計されています。DataProvider
は実際にインタフェースを実装していないため、メソッドのthrows
宣言内にあるかぎり自由に例外をスローできるのです。
データ・バインドの問題を処理するための様々な選択肢について説明しました。多数の選択肢があるため、すべてに対応できる1つの方針を示すのは困難です。場合によっては開発者の個人的な好みで決定する場合もあります。ただし、賢明かつ単純な方針は、次のように<try>
と何もしないことを組み合せることです。
<try>
で囲みます。
もちろん、<try>
を利用するばかりでは、堅牢なコードを記述することはできません。どのエラー処理コードを使用する場合でも、エラーを少なくすることが最も重要です。
イベント処理でのエラー処理はより単純です。出力はまだ生成されていないため、適当と判断する任意の方法で対応できます。一般的には2つの選択肢があります。汎用エラー・ページを使用するか、インライン・エラーを表示することです。インライン・エラーは、コンテンツと同じページ内でユーザーにエラーを表示するために使用されます。次のセクションで処理方法を説明します。
イベント処理でのエラーの多くは予測不可能です(データベースの接続拒否、問合せの失敗、サーバーのメモリー不足など)。これらは予測できないため、ユーザーが問題を解決する方法はありません。ユーザーに単純なエラー・ページを表示して、何が起きたかを知らせるのが最適です。UIX Controllerを使用すると簡単に実行できます。
まず、イベント・ハンドラは例外を自由にスローできることに注意してください。ただし、これによってエラー処理が不要になるわけではありません。ユーザーが正しい数値を入力しなかった場合にスローされるNumberFormatException
など、エラーによっては、必ず捕捉して適切に処理する必要があります。ただし、予測できない例外が発生したときは、EventHandler
からエスケープするようにします。
例外がスローされると、UIX ControllerはPageBroker.renderError(BajaContext, Throwable)
をコールします。このメソッドはデフォルトで次の手順を実行します。
Throwable
をoracle.cabo.servlet.eventError
キーでServletRequest
属性として保存します。
エラー・ページは、setErrorPage()
をコールして設定するか、oracle.cabo.servlet.errorPage
サーブレット構成パラメータによって設定することができ、いつでも取得できます。
エラーを表示するためには、エラー内容を書き出す必要があります。UIXには、この指定のための標準的な形式があります。MessageData
クラスです。MessageData
APIを使用すると、イベント・ハンドラで、1つのページに対して複数のエラーを指定でき、それらのエラーとページ内のコンポーネントとの対応を指定できます。ユーザー・インタフェースでは、<messageBox>
によってエラーがまとめられます。また、UIXのインライン・メッセージ・コンポーネントを使用すると、コンポーネントとともにインライン・エラーを表示できます。
MessageData
クラスは特殊なDataObject
であり、これを使用すると、サーバー側のエラー・メッセージを簡単にページに表示できるようになります。
1つのMessageData
オブジェクトで、任意の数のメッセージのリストが収集されます。MessageData
オブジェクトを作成し、各メッセージを続けて追加して、レンダリング時にそのMessageData
を使用します。
個々のメッセージは、ページのコンテンツ全体に適用する場合(たとえば「ユーザー名とパスワードの組合せが正しくありません」など)はページ・レベル、または単一のコンポーネントのメッセージのみに適用する場合(たとえば「本日以降の日付を入力してください」など)はコンポーネント・レベルになります。各メッセージに含まれる情報は次のとおりです。
<boundMessage>
(後述)を識別するために使用されます。ページ・レベル・メッセージでは、これをnull
にできます。
UIConstants
インタフェースの定数を指定します。通常は次のいずれかになります。
UIConstants.MESSAGE_TYPE_INFO
: 情報メッセージ。
UIConstants.MESSAGE_TYPE_WARNING
: 警告。
UIConstants.MESSAGE_TYPE_ERROR
: エラー。
<messageBox>
を使用し、コンポーネント・レベル・メッセージを表示している場合、ユーザーをコンポーネントに導くためサマリーにリンクを追加します。これによって、そのリンク・テキストを制御できます。ページ・レベル・メッセージの場合は、これを使用して、メッセージの省略タイトルを指定できます。
次の例では、SQLException
に応答する一連のページ・レベル・エラー・メッセージが構築されます。
static public MessageData getMessages(SQLException exception)
{
MessageData msgs = new MessageData();
while (exception != null)
{
// Create a title and get the message
String title = "ORA-" + exception.getErrorCode();
String message = exception.getLocalizedMessage();
// Add the message
msgs.addError(null, // null select - a page-level error
message, // the error message
null, // description URL - only for component-level
title, // the error title
null); // message description - let it default to the
// message itself
// And now go to the next exception in the chain.
exception = exception.getNextException();
}
return msgs;
}
MessageData
クラスには、エラー、警告および情報メッセージを追加するための便利なメソッドが他にも多く含まれています。この例で使用されているメソッドは1つのみです。詳細は、このAPIのドキュメントを参照してください。
ここで重要なのは、このクラスおよびその他の関連するクラスや要素は、非常に便利なオプションであるということです。すべて標準のデータ・バインドであり、特に複雑なものではありません。MessageData
およびuiXMLの要素(後述)を使用することによって、誤って発生するエラーを防ぐことができるでしょう。
一度MessageData
オブジェクトを作成すると、それをページに取得する必要があります。これは、UIXの<method>
要素など、通常のデータ・バインド技術を使用して実行できます。ただし、UIX Controllerのイベント・ハンドラを使用している場合は、さらに簡単な方法があります。イベント・ハンドラには、DataProvider
をイベント・ハンドラからページに渡すための簡単な方法があり、MessageData
には、自身をDataProvider
に変換するための便利なメソッドがあります。使用例を次に示します。
// An EventHandler that may produce an error
static public EventResult doSomething(
BajaContext context,
Page page,
PageEvent event)
{
try
{
// .... Do the usual stuff ....
return ...;
}
// An error!
catch (SQLException sqle)
{
// Turn the exception into a MessageData object
MessageData msgs = getMessages(sqle);
DataProvider provider = msgs.toDataProvider();
UIEventResult result = new UIEventResult();
result.setDataProvider(provider);
return result;
}
}
この例について簡単に説明します。すべてのイベント・ハンドラの標準コードについては省き、catch
セクションについて説明します。
SQLException
をMessageData
に変換します。
toDataProvider()
という便利なメソッドを使用して、MessageData
をDataProvider
に変換します。DataProvider
は、任意の数のDataObject
を動的に取得するためのAPIであることに注意してください。これはシンプルです。この変換後のDataProvider
はメッセージのために予約されている特別なデフォルトのネームスペースおよび名前でMessageData
を返します。(任意のネームスペースおよび名前を使用できます。ただし、デフォルトを使用すると記述するコードが少なくなり、短所もありません。)
UIEventResult
を作成します。このクラスは、特別な機能を1つ備えた標準のEventResult
のサブクラスです。これは、イベント処理コードからレンダリング・コードにDataProvider
を渡すことができます。
setDataProvider()
をコールして、エラー・メッセージをレンダリング・コードに渡します。UIEventResult
のDataProvider
は、レンダリング時に使用できることが保証されています。
UIEventResult
を返します。
コードをよく見ると、重要なことに気付きます。resultオブジェクトには実際には(DataProvider
以外)何も設定していません。デフォルトのコンストラクタを使用し、EventResult.setResult()
もコールしていません。このように作成されたEventResult
はUIX Controllerに対して特別な意味を持ちます。それは、ページ・フロー・エンジンの種類に関係なく、移動しない、つまり同一ページに戻るという意味です。これはエラーを表示するために重要です。また、この機能のために、ページ・フロー・エンジンの知識がなくても、このようなイベント・ハンドラを作成できます。
<messageBox>
XML要素およびMessageBoxBean
Javaクラスは、ページのメッセージすべてのサマリーの表示に使用します。
ほとんどの場合、このBeanにはautomatic
属性を設定するのみです。automatic
をtrue(JavaではMessageBoxBean.setAutomatic(true)
)に設定すると、<messageBox>
が自動モードになります。デフォルトの場所でMessageData
インスタンスが自動的に検索されます。1つも検出されない場合、(現在のUI仕様に基づき)何もレンダリングされません。検出された場合は、正しいコンテンツ内に各メッセージが書き出されます。
よく使用されるもう1つの機能は、message
属性(setMessage()
メソッド)です。このメソッドを使用すると、ボックス全体のメイン・メッセージを設定できます。setMessage()
メソッドはMessageData
でもサポートされており、自動モードでは、<messageBox>
によりMessageData
からメッセージが引き出されます。
<messageBox>
はuiXMLから簡単に使用できます。
<pageLayout>
<messages>
<!-- A message box belongs at the top of the page's content -->
<messageBox automatic="true"/>
</messages>
<contents>
<!-- and then follows the rest of the page -->
</contents>
</pageLayout>
<messageBox>
が特別な<messages>
要素内に追加されていることに注意してください。これにより、メッセージがルック&フィールに対して正しい場所に配置されます。<messageBox>
はページの任意の場所に配置できますが、<pageLayout>
を使用している場合は、ここが正しい場所になります。
<messageBox>
には属性が他にもいくつかありますが、それほど使用されません。たとえば、MessageData
の検索を行うネームスペースと名前をオーバーライドできますが、すでに説明したようにデフォルトを使用する方がより簡単です。
エラーや警告メッセージ以外を表示するため、自動モードをオフにして<messageBox>
を使用することができます。この場合は、明示的にメッセージを追加し、メッセージの種類およびタイトルを設定する必要があります。たとえば、<messageBox>
を使用して、「このファイルを本当に削除しますか?」というような確認ページを作成する場合があります。確認専用のmessageType
の値(JavaのUIConstants.MESSAGE_TYPE_CONFIRMATION
)を使用すると、適切な外観が得られます。
UIXのレンダラと、それが実装するユーザー・インタフェースの仕様のエラー表示に係わる主な機能は、インライン・メッセージです。すべてのエラーがまとめて最上部に表示されるWebページでは、ユーザーはエラーを修正するため、どのフィールドを変更する必要があるのか探し出す必要があります。UIXでは、エラーを問題の原因となった入力コントロールとともにインラインで表示できるようにして、この使用性の問題を解決しています。
このインタフェースの中心となるコンポーネントは、<inlineMessage>
XML要素(JavaのInlineMessageBean
)です。通常は、いくつかの他のコントロールの前にプロンプトが表示されます。
エラーが発生するとこのコンポーネントが自動的に拡張し、エラー・メッセージおよびアイコンが表示されます。
この2つの図で示した<inlineMessage>
のUIXは次のとおりです。
<inlineMessage prompt="古いパスワード:">
<contents>
<textInput name="pwd" secret="true"/>
</contents>
<boundMessage select="pwdError"/>
</inlineMessage>
特に難しくはありません。prompt
属性で左側の「古いパスワード:」を設定し、内部の<textInput>
で入力フィールドを設定します。これらは新規の要素ですが、特に新しい概念ではありません。本当に新しい要素は、<boundMessage>
要素のみです。
<boundMessage>
要素は、MessageData
のユーザー・インタフェース側をサポートするデータ・バインドをすべて設定します。この要素は、1つのMessageData
から値を取得して、InlineMessageBean
の属性のうち5つとデータ・バインドします。<boundMessage>
のselect
属性がMessageData
オブジェクトに追加されたメッセージの選択キーと一致する場合、そのメッセージがコンポーネントの横に表示され、アイコンなどが表示されます。MessageData
に一致するものがない場合は、inlineMessage
が直接レンダリングされます。
Javaでは、この機能はMessageData.bindNode()
メソッドで実現されます。
InlineMessageBean imb = ....;
// Attach this node to the "pwdError" message
MessageData.bindNode(imb, "pwdError");
<inlineMessage>
要素を2つ並べて配置すると、ちょっとしたことに気付きます。
<inlineMessage prompt="Your username:">
<contents>
<textInput name="user"/>
</contents>
</inlineMessage>
<inlineMessage prompt="Password:">
<contents>
<textInput name="pwd" secret="true"/>
</contents>
</inlineMessage>
これによって出力されたレイアウトでは2つの<textInput>
要素が並びません。並べるには、レイアウト・コンポーネント、<tableLayout>
または<labeledFieldLayout>
のいずれかの内部に2つの要素を入れる必要があります。使用方法は、これらのコンポーネントのドキュメントを参照してください。
インライン・メッセージは、フォーム要素とともによく使用されます。このため、<inlineMessage>
の機能と別の要素の機能を結合する便利な要素がいくつか追加されています。たとえば、<messageTextInput>
要素は<inlineMessage>
と<textInput>
の組合せとして機能します。次に示す2つのuiXMLは、機能面では同じです。
<inlineMessage prompt="Username:">
<contents>
<textInput name="user"/>
</contents>
<boundMessage select="userMsg"/>
</inlineMessage>
および
<messageTextInput name="user" prompt="Username:">
<boundMessage select="userMsg"/>
</messageTextInput>
両方で<boundMessage>
構文がサポートされていることに特に注意してください。その他の便利な要素として、次のものがあります。
<messageCheckBox>
<messageChoice>
<messageDateField>
<messageFileUpload>
<messageList>
<messageLovField>
<messageRadioButton>
<messageRadioGroup>
<messageStyledText>
これらはすべて簡単に使用でき、非常に便利であることに注意してください。ここに示していないコンポーネントでインライン・メッセージが必要な場合は、<inlineMessage>
でそのコンポーネントをラップしてください。これらの便利なBeanは、MessageTextInputBean
、MessageCheckBox
などのJava APIでも使用できます。
これまでに説明した内容を元に、完全な例を説明します。次の3つのフィールドを使用して、パスワード変更のページを定義します。
イベント・ハンドラでいくつかのエラー条件を調べ、エラーがあった場合はインライン・メッセージでページを再表示します。
まず、uiXMLを示します。
<page xmlns="http://xmlns.oracle.com/uix/controller"
xmlns:ctrl="http://xmlns.oracle.com/uix/controller">
<content>
<form xmlns="http://xmlns.oracle.com/uix/ui"
name="default" method="post">
<contents>
<pageLayout>
<contents>
<messageBox automatic="true"/>
<labeledFieldLayout>
<contents>
<messageTextInput prompt="Old password:"
name="old" secret="true">
<boundMessage select="oldError"/>
</messageTextInput>
<messageTextInput prompt="New password:"
name="new" secret="true"
tip="Passwords must be between 4 and 12 characters">
<boundMessage select="newError"/>
</messageTextInput>
<messageTextInput prompt="Confirm:"
name="confirm" secret="true"/>
</contents>
</labeledFieldLayout>
</contents>
<contentFooter>
<submitButton text="Submit" ctrl:event="newPassword"/>
</contentFooter>
</pageLayout>
</contents>
</form>
</content>
<handlers>
<event name="newPassword">
<method class="oracle.cabo.doc.demo.PasswordDemo"
method="handleNewPassword"/>
</event>
</handlers>
</page>
処理内容を簡単に推測できるコンテンツです。入力結果を送信するための<form>
と<submitButton>
を追加し、3つの<messageTextInput>
要素のうち2つをメッセージにバインドしています。また、コンテンツの最上部には<messageBox>
を配置しています。このページ内には、発生する可能性のあるエラーの種類について記述したり、このページによって処理されるビジネス・ロジックへの参照に関する記述を行う必要がないことに注意してください。これで正しい状態です。
次はイベント処理のソース・コードです。実際のパスワード・チェックのコードは省略しています。古いパスワードはハードコードし、新規パスワードはどこにも保存していません。しかし、メッセージ出力に関するコードはここに示すのがすべてです。
static public EventResult handleNewPassword(
BajaContext context,
Page page,
PageEvent event) throws Throwable
{
MessageData messages = new MessageData();
// Get all three parameters
String oldPassword = event.getParameter("old");
String newPassword = event.getParameter("new");
// Safeguard against null values. Always check parameters
if (newPassword == null)
newPassword = "";
String confirm = event.getParameter("confirm");
if (!"abcd".equals(oldPassword))
messages.addError("oldError",
"The old password was not correct (try abcd)",
null,
"Old password",
null
);
if (!newPassword.equals(confirm))
messages.addError(null,
"The two values for the new password didn't match.",
null,
"Error",
null);
if ((newPassword.length() < 4) ||
(newPassword.length() > 12))
messages.addError("newError",
"Passwords must be between 4 and 12 characters long",
null,
"Password length",
null);
// Are there any error messages yet? If not, then all is good,
// and move on to the success page.
if (messages.getLength() == 0)
{
// A real application would store the new password here!
return new EventResult(new Page(page, "success"));
}
else
{
UIEventResult result = new UIEventResult();
result.setDataProvider(messages.toDataProvider());
return result;
}
}
このコードも処理内容を簡単に推測することが可能で、すでに説明した内容の繰返しであることがわかります。古いパスワードが正しいこと、新規パスワードの文字数が4文字以上12文字以下であること、ユーザーがパスワードをミスタイプしていないかの確認として、新規パスワードのフィールドと確認用のフィールドが一致することを確認しています。問題が存在すると、メッセージがリストに追加されます。メッセージがある場合は、現在のページに戻ってエラーを表示します。ない場合はそのまま処理を進めます。非常に単純な内容です。
先に進む前に、メッセージBeanのその他の機能をいくつか説明します。
メッセージBeanはすべて、tip
属性をサポートします。これによって、現在のフィールドの使用方法をユーザーに示すテキストが追加されます。このテキストはエラーが表示されても見えるため、常に有用な情報の表示に使用してください。一般的には、正しい形式または値を示すテキストに使用します。
<messageDateField prompt="Date:" name="date"
tip="DD/MM/YYYY"/>
メッセージBeanではrequired
属性もサポートします。required
で使用できる値および値の意味については、このトピックで後述します。メッセージBeanという観点では、これは単に、値の入力が必須であることをユーザーに示すアイコンを表示するかどうかを示します。
<inlineMessage prompt="Username:" required="yes">
<contents>
<textInput name="user"/>
</contents>
</inlineMessage>
メッセージBeanはまた、<end>
子要素(Java APIではsetEnd()
メソッド)もサポートします。この要素で囲まれた部分は、コンテンツの最後(通常は右側)に追加されます。
<messageTextInput prompt="Username:" name="user">
<end>
<button text="Press Me" onClick="alert('Boo!')"/>
</end>
</messageTextInput>
最後にもう1点説明します。メッセージBeanのレイアウトでは全体的な形状が制限され、項目を期待どおりに並べることができない場合があります。このような場合は、特別な2つのコンポーネント、<messagePrompt>
および<messageText>
、またはJavaのMessagePromptBean
およびMessageTextBean
を使用して、必要なメッセージBeanを分割配置します。
<messagePrompt>
は、プロンプト、必要なアイコン、および任意のエラー・アイコンを表示します。<messageText>
は、エラー・メッセージおよび任意のヒントを表示します。両方とも<boundMessage>
要素(およびMessageData.bindNode()
)をサポートします。一般的には、この2つは同じエラー・メッセージにバインドします。次の例は、垂直方向に並べてレイアウトするために通常のインラインのメッセージBeanを分割したものです。特に<boundMessage>
要素に注意してください。
<stackLayout>
<contents>
<messagePrompt prompt="Username:" required="yes">
<boundMessage select="userError"/>
</messagePrompt>
<textInput name="user"/>
<messageText tip="Your username">
<boundMessage select="userError"/>
</messageText>
</contents>
</stackLayout>
ユーザー・エラーは、結果がサーバーに送信される前に捕捉するのが理想的です。ユーザーにとっては、エラー・ページがロードされるまで待機する必要がなくなるため、処理が速くなります。間違った結果を解析する必要もないため、サーバーの負荷も減ります。UIXは、クライアント側の検証と呼ばれるこの機能をサポートしています。
クライアント側の検証を使用するにあたり、検証をクライアント側のみにしてしまわないよう注意することが必要です。すべての層で検証するというのが複数層アプリケーションの鉄則です。中間層のコード、たとえば、サーブレット・エンジンで実行するコードでは、クライアントで検証済であっても、常にすべての値を調べる必要があります。データベースでは、中間層から送信されてきた値を検証します。アプリケーションのパフォーマンスの低下につながる不要な作業に見えますが、必ずこのルールに従ってください。悪意を持つユーザーにより、URLが変更されたり、ダミーのHTTPポストが作成される可能性があります。データベースが現在はJavaScript対応のブラウザで使用されていても、次の日には携帯情報端末の単純なブラウザで使用される可能性もあります。このルールを守らないと、ぜい弱なアプリケーションになります。最悪の場合にはセキュリティ・ホールが発生します。
最初に説明するクライアント側の検証の種類は、required
属性です。この属性は、ユーザーのデータ入力が必要か、もしくは値を空のままにできるかを定義します。まずは、テキストを入力せずに次のページの「Submit」をクリックしてみてください。ページ送信の中止を求めるエラーが表示されます。
<form name="defaultForm" xmlns="http://xmlns.oracle.com/uix/ui">
<contents>
<messageTextInput prompt="Username:" name="user" required="yes"/>
<submitButton text="Submit"/>
</contents>
</form>
required属性は<choice>
および<messageChoice>
要素でもサポートされます。ここで使用する場合は空のオプションを追加し、selectedとマークします。
<form name="defaultForm" xmlns="http://xmlns.oracle.com/uix/ui">
<contents>
<messageChoice prompt="Withholding:" name="withhold" required="yes">
<contents>
<option selected="true"/>
<option text="10%" value="10"/>
<option text="20%" value="20"/>
<option text="30%" value="30"/>
</contents>
</messageChoice>
<submitButton text="Submit"/>
</contents>
</form>
required
属性には、次の4つの有効な値があります。
yes
(JavaではUIConstants.REQUIRED_YES
): 空以外の値のみが使用できます。
no
(JavaではUIConstants.REQUIRED_NO
): 空の値を常に使用できます。
validaterOnly
(JavaではUIConstants.REQUIRED_VALIDATER_ONLY
): 連結されているクライアント側のValidaterで空の値が許可されている場合にのみ、空の値を使用できます(Validaterの連結については、次項を参照してください)。
uiOnly
(JavaではUIConstants.REQUIRED_UI_ONLY
): ユーザーについて値は必須なように見えますが、実際にはvalidaterOnly
と同じように動作します。この動作はあまり必要とされませんが、有用な場合もまれに存在します。
最も一般的な入力フィールドは数値フィールドです。この動作は、UIXのDecimalValidater
クラスおよび<decimal>
要素でサポートされています。これは単純な要素です。次の例で、テキストを無作為に入力してフォームをサブミットしてください。
<messageTextInput prompt="Any number:" name="number">
<onSubmitValidater>
<decimal/>
</onSubmitValidater>
</messageTextInput>
<decimal>
要素は非常に単純であり、属性はサポートしません。ここで興味深いのは、この要素をラップする<onSubmitValidater>
要素です。この要素は、使用するValidaterの種類を定義します。<onSubmitValidate>
および<onBlurValidater>
の2種類があります。Javaでは、これらはsetOnSubmitValidater()
およびsetOnBlurValidater()
メソッドによって設定されます。onSubmit
のValidaterは、ユーザーがフォームにすべてを入力して「Submit」ボタンをクリックすると実行され、onBlur
のValidaterは、ユーザーが別のフィールドに移ると同時に実行されます。違いを確認するために、次の例を試してください。フィールドにテキストを不作為に入力して[Tab]キーを押します。
<messageTextInput prompt="Any number:" name="number">
<onBlurValidater>
<decimal/>
</onBlurValidater>
</messageTextInput>
特別な理由がないかぎり、onSubmit
の検証を使用してください。正しい値を入力するまで他に何も入力できないような設定は、ユーザーにとって非効率的です。
次に一般的な入力フィールドは日付フィールドです。UIXでは、<dateField>
要素およびDateFieldBean
と、これらに対応するメッセージ系の<messageDateField>
要素およびMessageDateFieldBean
とともにこの機能を用意しています。(<messageDateField>
は、<inlineMessage>
内の<dateField>
に相当します。)<dateField>
の例を次に示します。フィールドの右側にあるカレンダのアイコンをクリックしてください。
<messageDateField prompt="Date:" name="date"/>
非常に簡単です。しかし、デフォルト書式が望ましくない場合、または日付のみでなく時刻も編集する場合は、結果の書式を変更する必要があります。これを実行する際は注意が必要です。<dateField>
で属性を直接設定するのではなく、<onSubmitValidater>
を特別な<date>
要素(またはDateValidater
オブジェクト)とともに追加してください。詳細は、<date>
要素に関するドキュメントを参照してください。次の例を試すと概要がつかめるはずです。
<tableLayout>
<contents>
<messageDateField prompt="Date:" name="date" columns="20">
<onSubmitValidater>
<date dateStyle="long"/>
</onSubmitValidater>
</messageDateField>
<messageDateField prompt="Date and Time:" name="dateandtime"
columns="20">
<onSubmitValidater>
<date dateStyle="short" timeStyle="short"/>
</onSubmitValidater>
</messageDateField>
<messageDateField prompt="Patterned:" name="pattern">
<onSubmitValidater>
<date pattern="yyyy/MM/dd"/>
</onSubmitValidater>
</messageDateField>
</contents>
</tableLayout>
UIXでは、<regExp>
要素またはRegExpValidater
クラスによる検証において、正規表現の使用もサポートしています。JavaScriptで使用する構文をサポートしています。このヘルプでは完全な構文を説明しませんが、必要な場合はJavaScriptのリファレンスを参照してください。社会保障番号を入力する例を次に示します(実際の番号は入力しないでください)。
<messageTextInput prompt="SSN:" name="ssn" text="123-45-6789">
<onSubmitValidater>
<regExp pattern="[0-9]{3}-[0-9]{2}-[0-9]{4}"/>
</onSubmitValidater>
</messageTextInput>
ページの一部のボタンでのみユーザーのデータを検証し、その他のボタンでは検証しないという場合がしばしばあります。たとえば、「Back」ボタンでは結果は使用されないため、ユーザーに正しい結果の入力を求めることはありません。コンポーネントごとに検証をオフにするには、unvalidated属性(setUnvalidated()
メソッド)を使用します。ここで正規表現の例を再び示します。ここでは2つのボタンが存在し、そのうちの1つでは検証は実行されないようになっています。無効な社会保障番号を入力してください。「Submit」ボタンでは検証が実行されますが、「Back」ボタンでは実行されません。
<messageTextInput prompt="SSN:" name="ssn" text="123-45-6789">
<onSubmitValidater>
<regExp pattern="[0-9]{3}-[0-9]{2}-[0-9]{4}"/>
</onSubmitValidater>
</messageTextInput>
<submitButton text="Submit"/>
<submitButton text="Back" unvalidated="true"/>
エラー処理のための強力なシステムがUIXフレームワークで提供されていますが、追加処理の実行のため開発者がこのシステムをオーバーライドすることが必要な場合があります。たとえば、リンクがクリックされた際に実行される特別なJavaScriptの処理をUIXの検証システムが通常実行する処理に追加する、またはそのかわりとして追加する場合などがそうです。
このため、UIXでは開発者がコールできるクライアント側のJavaScript関数をいくつか用意しています。関数は、UIXで提供されるライブラリに実装されています。これらのファイルにある数多くの関数はUIXで内部的に使用するもので、開発者が直接コールすることはできません。このような関数名には接頭辞としてアンダースコア(_)が付いています。これらの関数の動作および引数は、リリースによって変更される可能性があるため、編集は避けてください。
ただし、特別なフォーム送信の動作を実装する場合に使用できる関数が1つあります。submitForm()
関数です。この関数は、UIX Componentsのレンダラで使用するものと同一です。この関数を使用すると、開発者はUIXが実装しているクライアント側の検証のフレームワークにアクセスできます。
UIXページでフォーム送信をカスタムで行う場合は、フォームのDOM要素を直接送信するかわりにこのメソッドをコールしてください。次のパラメータを指定します。
この関数は、フォームが実際に送信された場合にtrue
を返し、エラーや検証の失敗など、なんらかの理由でフォームが送信されなかった場合にfalse
を返します。
UIXのデプロイ用ライブラリでは、これらの関数を含むJavaScriptファイルはパフォーマンスの向上のために縮小されています。したがって、コメントを含むコピーおよびドキュメントを参照するには、UIXバージョンのソース・リリースを調べ、oracle.cabo.ui.jsLibs
ディレクトリを参照してください。