UIX開発者ガイド |
![]() 目次 |
![]() 前へ |
![]() 次へ |
このトピックでは、ノード・ツリー自体は変更せずに、uiXMLのデータ・バインド属性を使用して、ユーザーごとに動的コンテンツを生成するページの作成方法を説明します。UIXによるJavaBeansおよびデータをグループ化するためのMap
のサポート方法や、配列およびList
を持つデータの反復方法を説明します。また、インライン・データをプロトタイプ用のuiXMLに直接作成する方法も説明します。次に、Java開発者はこの機能をサポートするDataObject
、BoundValue
、DataProvider
およびDataObjectList
インタフェースや、これらのJava APIを使用してさらにカスタマイズされたデータ・バインドを実装する方法を学習します。
最後に、JavaBeansに対するUIXサポートの詳細を説明します。
ここでは、次の項目について説明します。
以降いくつかのセクションでは、uiXML開発者の観点からUIXのデータ・バインドについて説明します。Java APIを使用してUIXの開発のみを行っている場合は、「Java APIからのデータ・バインド」へスキップしてもかまいません。
前のトピックの一番初めの例を使用して、最も単純な例から説明します。
<text xmlns="http://xmlns.oracle.com/uix/ui"
text="hello, world"/>
「hello, world」のデータ・バインドのサポートを、ある興味深い内容に追加します。具体的には、非常に単純なJavaBeansを使用して、現在の日付と時刻を表示します。
package yourpackage;
import java.util.Date;
public class CurrentDateBean
{
public CurrentDateBean() { }
public String getTime()
{
return (new Date()).toString();
}
}
ここで、getTime()
が使用できるようにページを変更します。次の3つの作業が必要です。
まず、textをデータ・バインドします。
<text xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:data="http://xmlns.oracle.com/uix/ui"
data:text="time@currentDate"/>
最初の例から次の3箇所が変更されました。
このサンプル・コードを実行しても、まったく何も起こりません。currentDateをページに設定していないため、データ・バインドに失敗し、textがNULLのままだからです。この設定をするには、<dataScope>をページに追加します。
<dataScope xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:data="http://xmlns.oracle.com/uix/ui">
<provider>
<data name="currentDate">
<method class="yourpackage.DataDemo" method="getCurrentDate"/>
</data>
</provider>
<contents>
<text data:text="time@currentDate"/>
</contents>
</dataScope>
これで、大幅な変更が加えられました。結果は次のとおりです。
getCurrentDate()
メソッドをyourpackage.DataDemo
クラスで使用します。<data>に組み込まれる要素は多数ありますが、ここでは<method>のみ使用します。
このページに入力してこれを実行した場合、yourpackage.DataDemoクラスがUIXによって検出されないとエラーが表示されます。その際、このクラスを記述する必要があります。
package yourpackage;
import oracle.cabo.ui.RenderingContext;
public class DataDemo
{
static public Object getCurrentDate(
RenderingContext context, String namespace, String name)
{
return new CurrentDateBean();
}
}
これは、単純なクラスです。uiXMLの<method>
要素によって参照されると記述された、すべてのJavaのデータ・プロバイダには、ここで確認できるものと同一のシグネチャが設定されている必要があります。このサンプル・コードにある3つのパラメータはいずれも必要ではありませんが、このJavaBeansを返す必要があります。このメソッドをCurrentDateBean
クラスに移動することも可能です。しかしこのコーディング・スタイルはお薦めしません。JavaBeansはどのアーキテクチャでも再利用可能なように設計する必要があります。さらに、UIX、サーブレットまたは他のテクノロジへの依存をできるだけ排除する必要があります。
先へ進む前に、2つの疑問点を解決しましょう。
DataDemo.getCurrentDate()
がコールされるタイミング
DataDemo.getCurrentDate()
は、ページがレンダリングされるたびに1回コールされます。CurrentDateBean
に複数のプロパティが設定されていて、そのすべてを使用した場合でも、Beanは1回ロードされます。しかし、getCurrentDate()
はページがレンダリングされるたびにコールされます。ページを100回表示する必要がある場合、このメソッドは100回コールされます。
CurrentDateBean.getTime()
がコールされるタイミング
CurrentDateBean.getTime()
は最低1回コールされます。UIXでは、これにより各プロパティが1回しか要求されないとは限りません。繰り返し要求される場合もあります。通常getTime
によって新しいDate
オブジェクトが作成されるので、ページでtimeを何度も使用すると、異なる時刻がページに表示される可能性があります。これが問題であるならば、Date
をCurrentDateBean
コンストラクタに作成し、getTime()
がコールされるたびに同じオブジェクトを再利用してください。
JavaBeansは、データをカプセル化する適切な方法です。しかしUIXで状態を設定するたびに新しいJavaBeansクラスを記述したり、新しいプロパティが使用されるたびに新しいgetterメソッドを追加したりするのは、単調で時間を要します。また、すべてのプロパティ名を事前に把握するのは不可能な場合もあります。その場合は実行時に検出するしかありません。UIXでは、このような事態に対応するために、Java Map
インタフェースがサポートされています。
次に示すのは、JavaBeansのかわりにマップを使用するデータ・プロバイダです。
package yourpackage;
import oracle.cabo.ui.RenderingContext;
import java.util.HashMap;
public class DataDemo
{
static public Object getURLAndText(
RenderingContext context, String namespace, String name)
{
HashMap map = new HashMap();
map.put("text", "Oracle Corporation");
map.put("url", "http://www.oracle.com");
return map;
}
}
さらに、これを使用するUIXを次に示します。
<dataScope xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:data="http://xmlns.oracle.com/uix/ui">
<provider>
<data name="link">
<method class="yourpackage.DataDemo" method="getURLAndText"/>
</data>
</provider>
<contents>
<text data:text="text@link"
data:destination="url@link"/>
</contents>
</dataScope>
この方法で開発するのは簡単ですが、JavaBeansを使用する場合ほど適切ではありません。いつどの方法を使用するかは個人の好みの問題であり、ほとんどの開発者は両方の手法を使用することになるでしょう。
最初の例を少し拡張してみましょう。単に時刻を表示するかわりに、時刻の前に「The time is:」と表示します。この静的テキストが含まれた<text>
要素を追加する方法もありますが、ここでは異なる手法を示します。data:を使用する場合、JavaBeansから値を取得できますが、操作はできません。<boundAttribute>
要素により、高度な設計ができます。簡単な連結法則を使用した、非常に複雑な式が可能です。
<dataScope xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:data="http://xmlns.oracle.com/uix/ui">
<provider>
<data name="currentDate">
<method class="yourpackage.DataDemo" method="getCurrentDate"/>
</data>
</provider>
<contents>
<text>
<boundAttribute name="text">
<concat>
The time is:
<dataObject select="time" source="currentDate"/>
</concat>
</boundAttribute>
</text>
</contents>
</dataScope>
上の例から変更された部分は<text>
要素のみです。data:textを使用してtextをデータ・バインドするかわりに、<boundAttribute>
を使用しています。これには<concat>
要素が含まれており、これによりBeanから(<dataObject>
要素とともに取得された)timeが静的文字列に結合されます。
<boundAttribute>
内で使用される要素は数十個ありますが、ここではすべてを説明しません。これらの要素の詳細は、JDeveloperのuiXMLエディタにある「コード・インサイト」を使用するか、「UIX要素リファレンス」を参照してください。これらの要素は、対応するJavaインタフェースに由来してBoundValue
要素と呼ばれます。他のいくつかのBoundValue
要素には、次のものが含まれます。
これらの大半については後半のトピックで説明しますが、全リストは「要素リファレンス」を参照してください。
ここまでで、動的データを含むページを設定する方法を学びました。しかし、このインタフェースでは、順序付けられていない一連のプロパティがサポートされるだけです。表形式のデータ、またはJava配列のようなその他のデータを表示する場合、これは非常に不便なインタフェースです。UIXでは、List
およびJava配列の反復をサポートしています。また、UIX固有のAPIであるDataObjectList
もサポートしています。これについては後述します。
ここで、Java配列およびList
からデータを取り出して、ページに戻す方法について説明します。それには、現在のDataObject
をコールする設定が必要です。すべてのデータ・フレームワークで、データの反復処理がサポートされています。これはJavaではforループに相当します。データベースではカーソルに相当します。UIX Componentsでは、現在のDataObject
の指定がそれに当たります。現在のDataObject
からデータを取得するために、ソース全体を省略します。
<styledText>
<boundAttribute name="text">
<dataObject select="textKey"/>
</boundAttribute>
</styledText>
<!-- OR -->
<!-- Note that there's no "@" sign here -->
<styledText data:text="textKey"/>
<dataScope>のcurrentData属性を使用して、現在のDataObject
を明示的に設定できます。
<dataScope data:currentData="currentDate">
<!-- Now we have a current data object -->
...
</dataScope>
しかし、これは通常使用される方法ではありません。さらに一般的な方法として、List
または配列を自動的に反復し、DataObject
を自動設定するコンポーネントを使用します。UIXの<table>は、このようなコンポーネントの1つです。ここでは<table>の詳細は省略します。<table>は、1つのトピックを設ける必要があるほど多機能だからです。ここでの説明は、現在のDataObject
の機能を理解するためのみにとどめておきます。
最初に、表に対してList
を作成します。ここで、Map
のArrayList
を使用します。Map
の配列やJavaBeans
の配列なども、同じくらい簡単に使用できます。
package yourpackage;
//...
import java.util.List;
import java.util.ArrayList;
public class DataDemo
{
// ...
static public Object getTableData(
RenderingContext context, String namespace, String name)
{
List list = new ArrayList();
for (int i = 0; i < 5; i++)
{
HashMap row = new HashMap();
row.put("text", "Row " + i);
row.put("text2", "Column 2, row " + i);
list.add(row);
}
return list;
}
}
次に、データを<table>
に格納します。<table>
の各列は、<contents>
の単一の子によって表されています。このような子が3つある場合、表には列が3つ設定されます。
異なる方法でレンダリングされる各子を行ごとに取得する点が特長です。これは、現在のDataObject
の指定を使用することで可能になります。表で最初の行をレンダリングする際、現在のDataObject
により、List
の最初のMap
がポイントされます。2行目のレンダリング時には、現在のDataObject
により、List
の2番目のMap
がポイントされます。以下同じ処理が繰り返されます。
ここで表に2つの列を追加します。1つは通常のテキスト列、もう1つは赤色の列です。(この例では、ビルトインのUIX Stylesの1つである、OraErrorText CSSスタイルを使用しています。)
<dataScope>
<provider>
<data name="someTableData">
<method class="yourpackage.DataDemo" method="getTableData"/>
</data>
</provider>
<contents>
<table data:tableData=".@someTableData">
<contents>
<styledText data:text="text"/>
<styledText styleClass="OraErrorText" data:text="text2"/>
</contents>
</table>
</contents>
</dataScope>
これで終了です。コードはそれほど長くありませんが、利点は多いです。
まず、<table>要素は、データ・バインドされたtableData属性を使用してデータを取得しています。ここで注目すべき点は、データ・バインドの式にあるピリオド(.)です。先に記述したgetTableData()
では、ArrayList
が返されました。表のデータには、ArrayList
のプロパティではなく、ArrayList
そのものが必要です。この構文によって、それが可能です。
次に、2つの子が追加されました。1つ目は、textキーからテキストを取得する<styledText>
です。ここにアットマーク(@)がないことに注意してください。つまり、テキストは現在のDataObjectから取得されています。2つ目は、別のキーにバインドされたテキストが設定された、もう1つの<styledText>です。データ・モデルをまったく使用せずに、列の追加、削除およびレンダリングをUIで直接行えます。
これは単純な例です。列ヘッダーや行ヘッダー、あるいは表で実行可能な多数の処理は追加していません。また、編集不可の普通のテキストのみを使用しています。ただし、これらはすべて同じ方法で機能します。表の行ごとにレンダリング方法を変える場合、現在指定されているDataObject
に、その変更内容を示す情報が指定されている必要があります。
たとえば、各行にリンクを指定し、それぞれの宛先が異なるとします。この場合は簡単です。<link>
要素を使用してdestination属性を現在指定されているDataObject
にデータ・バインドし、各行の宛先を含むようにDataObject
を更新します。UIXでは、すべてについてデータ・バインドが可能なため、各行が異なるようにできます。
このセクションでは、キーとデータ・オブジェクトをネストして、さらに複雑なデータ・バインドを生成する方法を説明します。最初に、次のフォームを使用して、ネストされたキー・データ・バインドを生成します。
data:attributeName="keyN@...@key2@key1@name"
この場合、この属性の値を計算する際に、name
で参照されるDataObject
についてkey1
が使用され、別のDataObject
が生成されます。2番目のDataObject
にはkey2
が使用され、3番目のDataObject
が生成されます。このプロセスは、keyN
が使用され、この属性の最終値が生成されるまで続きます。すべてのDataObject
の値自体がDataObject
であることが必要です。最後のキーkeyN
の値のみは、(属性が必要とするタイプであれば)DataObject
でなくてもかまいません。ネストされたキー・バインドの例を次に示します。
<dataScope>
<provider>
<data name="families">
<inline>
<Smith members="4">
<address number="2255" street="37th Ave" city="San Francisco"
state="CA" zip="94116" />
</Smith>
<Jones members="3">
<address number="500" street="Oracle Parkway" city="Redwood Shores"
state="CA" zip="94065" />
</Jones>
</inline>
</data>
</provider>
<contents>
<messageTextInput prompt="The Smiths live in"
data:text="city@address@Smith@families" /> <!--San Francisco-->
<messageTextInput prompt="The Jones's live in"
data:text="city@address@Jones@families" /> <!--Redwood Shores-->
</contents>
</dataScope>
上の例では、最初のmessageTextInput
で「The Smiths live in San Francisco」が生成され、2番目で「The Jones's live in Redwood Shores」が生成されます。
ネストされたデータ・オブジェクト・バインドを生成するためにカッコも使用できます。次の例について考えてみます。
data:attributeName="(key1@name1)@name2"
上の例では、name1
(で参照されるDataObject
)でkey1
が使用されてキーを生成し、そのキーがname2
で使用されてこの属性の最終値を生成しています。これは、キー自体がデータ・バインドされる例です。データ・オブジェクトの一部をデータ・バインドすることもできます。次の2つの例は同じ意味です。
<!-- These two are equivalent -->
data:attributeName="key2@(key1@name)"
data:attributeName="key2@key1@name"
次に示すのは、前の例で作成されたfamilies
データ構造を使用したカッコの例です。これによって、「The Jones family has 3 members.」が生成されます。
<dataScope>
<provider>
<data name="families">
... as above ...
</data>
<data name="selection">
<inline current="Jones" />
</data>
</provider>
<contents>
The<link data:text="current@selection"/> family
has<link data:text="members@(current@selection)@families" />
members.
</contents>
</dataScope>
カッコは、次の例のようにネストすることもできます。
data:attributeName="((key1@name1)@name2)@name3"
この例では、key1
がname1
で使用されてキーを生成し、そのキーがname2
で使用され、最終値を計算するname3
に対する別のキーを生成します。
アットマーク(@)の前後のテキストはオプションです。キーを省略すると、次の例のようにDataObject
自体が返されます。
<!-- The following attribute is bound to the data object itself -->
data:attributeName="@name"
データ・オブジェクト名の一部を省略すると、次の例のように、現在のデータ・オブジェクトでキーが使用されます(アットマーク(@)が指定されない場合のデフォルト)。
<!-- These keys are used with the current data object -->
data:attributeName="key@"
data:attributeName="key"
アットマーク(@)が指定されない場合、暗黙的にアットマーク(@)があるものと仮定されます(上の例の2番目のバインドの場合)。次の例で、この詳細を示します。
<!-- These are different bindings -->
data:attributeName="key@name"
data:attributeName="(key@name)"
上の例の2つのバインドは同じではありません。2番目のバインドには暗黙のアットマーク(@)があり、data:attributeName="(key@name)@"
と記述することもできます。2番目のバインドでは、key
をデータ・オブジェクトname
に適用し返される値が、現在のデータ・オブジェクトのキーとして使用されます。
キーおよびデータ・オブジェクト名には一定の制限があります。たとえば、キーのテキストにはアットマーク(@)を使用できません。uiXMLの将来のバージョンでは、このような文字のエスケープが可能になり、使用できるようになる予定です。
DataObject
Javaインタフェースは、UIX ComponentsおよびuiXMLのページの汎用データソースです。UIXでは、JavaBeansやマップを渡す場合、軽量アダプタ・クラスを使用して、これらのオブジェクトをDataObject
に変換します。このアダプタについては後述します。
ここでは、DataObject
そのもののJava APIについて、およびこれが関心を持たれる理由を説明します。DataObject
は、1つのメソッドのみを含む、非常に単純な新たなインタフェースです。
public interface oracle.cabo.ui.data.DataObject
{
/**
* Select a single value out of the DataObject.
* @param context the current RenderingContext
* @param select a select key
* @return the selected value
*/
public Object selectValue(RenderingContext context, Object select);
}
DataObject
は小規模のインタフェースであり、開発者またはデータ構造に対する要件はごくわずかです。そのため、次のような任意のデータ形式に容易に適応できます。
UIXには、BC4Jへのビルトイン・バインド(「Business Components for Javaの統合」を参照)と、前述のJavaBeansおよびマップへのビルトイン・バインドが組み込まれています。
ほとんどの開発者は、最初にselect
パラメータを目にした際にHashtable
のキーを想定するでしょうが、DataObject
はそのようには機能しません。select
パラメータは次のいずれかになります。
Integer
インデックスUIXによる開発では多くの場合、DataObject
APIに対してコード化する必要はありません。しかし、マップやJavaBeansのかわりにDataObject
を使用することを考慮してもいい理由があります。
DataObject
はUIXでデータ用に使用される実際の概念です。したがって、Java APIを使用してBeansを作成する場合、メソッドの取得対象であるDataObject
を使用する必要があります。(後述するとおり、BeanAdapterUtils
を使用してJavaBeansをDataObject
に簡単に変換することもできます。)
また、これらのJavaBeansに対するビルトイン・サポートによるオーバーヘッドがまったくないわけではありません。パフォーマンス上のコストがかかります。最初に、DataObject
を実装するアダプタ・オブジェクトを作成する必要があります。これらは軽量であるとは言え、オブジェクト作成は可能ならば避けた方が無難です。次に、イントロスペクション(実行時におけるメソッドの動的な検出およびコール)は本質的に動作が遅くなります。このプロセスの最もコストが高い部分(Method
オブジェクトの検出)のキャッシュについては快適な動作が提供されますが、Method
オブジェクトへのコールについては依然として、手動コーディングされたDataObject
におけるコンパイル済メソッド・コールよりも動作が遅くなります。
ただし、「早まった最適化は諸悪の根源である(Premature optimization is the root of all evil)」というプログラミングについての有名な格言を思い出してください。手動コーディングされたDataObject
がより速いとしても、DataObject
の記述に時間を費やすべきということにはなりません。適度に使用されていれば、JavaBeansの使用がパフォーマンスに重大な影響を与える理由はありません。本質的に動作が遅いJavaBeans(データベースにアクセスする必要がある場合など)については、アダプタの追加オーバーヘッドは少なくほとんど問題になりません。ただしプロファイリングを実行して、Beansの1つの適合オーバーヘッドがパフォーマンスに重大な影響を与えていると判断した場合は、オブジェクト作成およびイントロスペクション・オーバーヘッドの両方の問題に取り組むことができます。このパフォーマンスに関する問題を解決するいくつかの簡単な方法は、後で説明します。
最後に、そして最も有用な点ですが、DataObject
はRenderingContext
を受け取ります。RenderingContext
には多くの有用な値が含まれています。Locale
を使用してdate文字列を適切に書式化する例を示します。
package yourpackage;
import oracle.cabo.ui.RenderingContext;
import oracle.cabo.ui.data.DataObject;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
public class CurrentDateObject implements DataObject
{
public Object selectValue(RenderingContext context, Object select)
{
if (!"time".equals(select))
return null;
Locale locale = context.getLocaleContext().getLocale();
DateFormat formatter = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM, DateFormat.MEDIUM, locale);
return formatter.format(new Date());
}
}
CurrentDateBean
のかわりにこのクラスを使用する場合、データはLocale
に対して自動的に正しく書式化されます。
uiXML内にデータとデータ・リストの両方を直接定義できます。ページが実際のデータソースに連結される前にダミー・データを作成できるため、非常に便利です。
まず、DataProvider
の新しい要素、<inline>
を紹介します。
<dataScope xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:demo="http://www.example.org/">
<provider>
<data name="demo:linkData">
<inline>
.... data object is in here ...
</inline>
</data>
</provider>
<contents>
...
</contents>
</dataScope>
この要素は、前述した<method>
要素に似た動作をしますが、Javaコードはコールしません。uiXML内でデータを直接定義します。
<inline>
のすべての属性によって、作成されるデータ内にキーと値のペアが定義されます。次に例を示します。
<inline url="http://example" text="Inline text">
</inline>
上の例では、2つの値を持つデータを定義しています。1つのキーはurl、もう1つのキーはtextです。簡単に定義できます。
ただし、子要素を使用し、サブ値としてデータおよびデータ・リストを追加することもできます。要素の名前は、子のデータまたはデータ・リストが格納されるキーです。同じ要素名が複数ある場合は、それらの要素がまとまってデータ・リストを定義します。1つしかない場合、1つのデータの集合と単一要素のデータ・リストの両方が定義されます。次の例でわかりやすく示します。
<inline foo="Inline text">
<stuff random="Stuff text"/>
<row text="First row"/>
<row text="Second row"/>
</inline>
この例には、3つの内容を含むデータがあります。
文字列
Inline Text。
<dataScope>
<provider>
<data name="inlineData">
<inline>
<tableKey text="Row 0" text2="Column 2, Row 0"/>
<tableKey text"Row 1" text2="Column 2, Row 1"/>
... etc ...
<tableKey text="Row 4" text2="Column 2, Row 4"/>
</inline>
</data>
</provider>
<contents>
<table data:tableData="tableKey@inlineData">
<contents>
<styledText data:text="text"/>
<styledText styleClass="OraErrorText" data:text="text2"/>
</contents>
</table>
</contents>
</dataScope>
次のいくつかのセクションで、Java API固有の多くの詳細を説明します。全体をuiXMLから開発している場合、独自に作成したuiXMLの機能およびフレームワークの拡張方法を取得するとき、これは重要になる可能性があります。
BoundValue
インタフェースは、UIX Componentsの動的ページにとって不可欠です。これは、次のようにRenderingContext
が渡され、任意のJavaオブジェクトが返される1つのメソッドで構成されています。
public interface oracle.cabo.ui.data.BoundValue
{
public Object getValue(RenderingContext context);
}
この単純なインタフェースが便利なのは、すべてのUIX Components Beanであらゆる属性値のかわりに使用できるためです。属性の値がBoundValueに設定されている場合、その属性の値を取得しようとしてもBoundValueは返されません。UIX ComponentsではBoundValue.getValue()
をコールし、結果を返します。次に例を示します。
// Remember the code to set the text of a StyledTextBean...
styledTextBean.setText("Some text");
// ... is really a cover for:
styledTextBean.setAttributeValue(UIConstants.TEXT_ATTR,
"Some text");
// So you can also set a BoundValue:
BoundValue boundValue = ...;
styledTextBean.setAttributeValue(UIConstants.TEXT_ATTR,
boundValue);
この後、表示時にStyledTextBeanのテキストを要求する場合、次のように指定します。
text = styledTextBean.getAttributeValue(renderingContext,
UIConstants.TEXT_ATTR)
// .. is equivalent to writing:
BoundValue boundValue = ...;
text = boundValue.getValue(renderingContext);
setAttributeValue()
でBoundValue
を使用して、すべての属性をデータ・バインドできることを覚えておいてください。属性のデータ・バインドのための専用のメソッドがそのBeanにあるかどうか、以前にその属性がデータ・バインドされたかどうか、あるいはその属性が必要とするJavaオブジェクトのタイプはどれかなどは関係ありません。すべての属性をデータ・バインドできます。
多くのBeanで、属性をバインドするための便利なメソッドが用意されています。StyledTextBean
のtext属性もその1つです。次のように記述できます。
BoundValue boundValue = ...;
styledTextBean.setTextBinding(boundValue);
ただし、これは利便性が高いというだけです。便利なメソッドがあるかどうかにかかわらず、すべてのBeanですべての属性をバインドできます。
BoundValue
には多数の実装が用意されていますが、次にカスタムの実装について説明します。次のコードでは常に現在の日付が返されます。
public class CurrentDate implements BoundValue
{
public Object getValue(RenderingContext context)
{
return new Date();
}
}
これをDateFieldBean
で使用します。
DateFieldBean dateField = new DateFieldBean();
dateField.setValueBinding(new CurrentDate());
上の例で生成される日付フィールドは、常に現在の日付で初期化されます。上のコードは次のコードとよく似ています。
DateFieldBean dateField = new DateFieldBean();
dateField.setValue(new Date());
ただし大きな違いがあります。2番目の例では日付の設定は1回です。Beanを1回だけ使用して解放する場合は、問題ありません。ただし、UIX ComponentsではBeanは何度も再利用できます。最初の例で使用したBoundValue
の場合、UIX Componentsにより、ページのレンダリングのたびに日付が要求されます。このため、DateFieldBean
が作成された後でも常に正確な月日が表示されます。
次に、独自のDataObject
クラスの記述方法を説明します。
このクラスでは、JavaのResourceBundle
をDataObject
に変換します。このクラスは大変便利なため、UIX Componentsに含まれています。ここではその説明を通して、DataObject
を実装する際の基本的な考え方を示します。この例では、DataObject
で例外を処理する方法の1つも示されます(他の方法は「エラーの処理」のトピックを参照してください)。
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import oracle.cabo.ui.data.DataObject;
public class BundleDataObject implements DataObject
{
public BundleDataObject(ResourceBundle bundle)
{
_bundle = bundle;
}
public Object selectValue(RenderingContext context, Object select)
{
try
{
return _bundle.getObject(select.toString());
}
catch (MissingResourceException e)
{
context.getErrorLog().logError(e);
return null;
}
}
private ResourceBundle _bundle;
}
留意点:
ResourceBundle
をラップした場合、1つのオブジェクトしか割り当てられません。しかし、Map
を作成してすべてのデータをResourceBundle
から取り出した場合、さらに多数のオブジェクトを作成する必要があります。また、前述の例の場合は、実行時に実際に使用されているキーのみが取得されます。
DataObject
で、例外がスローされないようにする必要があります。ここでは、MissingResourceException
を検出し、標準のUIX ErrorLog
に記録してから、安全な値すなわちnullが返されています。
DataObject
の独自の実装は、非常に簡単に記述できます。カスタムの実装を使用することによって、アプリケーションの効率やパフォーマンスが向上します。
DataObject
の作成後、RenderingContext
インタフェースに連結して、UIX Componentsのページで使用できるようにします。(前述のように、このインタフェースが、UIX Components BeanとuiXMLのページに、レンダリングに必要なコンテキストを渡します)。ここで、RenderingContext
の特定のメソッドに注目します。
public DataObject getDataObject(String namespaceURI,
String localName)
このメソッドによって、UIX Components BeanまたはBoundValue
では、任意の数のDataObject
へのアクセスが可能になります。UIXでは通常、これらのDataObject
は2つの文字列で識別されます。WebサイトのURLに基づく1つ以上のネームスペースと、他のコードと競合しない任意のローカル名を使用します。
ここで、DataObject
をRenderingContext
に渡す方法からは一度離れ、DataObject
からデータを取得してUINode
またはuiXMLに渡す方法を説明します。ここで、DataBoundValue
クラスが渡されます。UIX Componentsでは、DataBoundValue
クラスを使用して、RenderingContext
のDataObject
を取得できます。
LinkBean bean = new LinkBean();
DataBoundValue destinationBinding =
new DataBoundValue(_EXAMPLE_NAMESPACE,
"dataObjName",
"url");
bean.setAttributeValue(UIConstants.DESTINATION_ATTR,
destinationBinding);
// ...
static private final String _EXAMPLE_NAMESPACE =
"http://www.example.org";
これは、レンダリング時に次のJavaコードを実行していることと同じです。
// ...
DataObject dataObject = context.getDataObject("http://www.example.org",
"dataObjName");
Object destination = dataObject.selectValue(context, "url");
// ...
DataBoundValue
が有用なため、DataBoundValue
を作成する次のような便利なメソッドがBeanに用意されています。
LinkBean bean = new LinkBean();
bean.setDestinationBinding(_EXAMPLE_NAMESPACE
"dataObjName",
"url");
ここまでで、独自のDataObject
を記述する方法、それらをRenderingContext
から取得する方法、およびそれらを属性にバインドする方法を学びました。この他に説明が必要なものは、DataObject
をRenderingContext
に渡す方法です。この方法では別のインタフェース、DataProvider
インタフェースを使用します。これは小規模なインタフェースです。
public interface DataProvider
{
public DataObject getDataObject(
RenderingContext context,
String namespace,
String name);
// ... methods omitted
}
まだ説明していないメソッドがいくつかありますが、中でもgetDataObject
は重要なメソッドです。これはDataObject
を取得するためにRenderingContext
でコールするメソッドです。このメソッドにはネームスペースと名前が渡されるため、各DataProvider
では様々な多くのDataObject
を提供できます。最後に、DataProvider
をRenderingContext
に連結する方法です。これは、もう1つのメソッドを使用するだけで可能です。
public interface RenderingContext
{
// ...
public void addDataProvider(DataProvider provider);
// ...
}
DataProvider
をRenderingContext
にする回数で何度でもこのメソッドをコールできます。(実際には、すべてのDataProvider
をTableDataProvider
の中に入れ、それをCachingDataProvider
の中に入れる方が適切です。)これで準備ができました。次の要約の後、全工程を示す例に進みます。図4-1を参照してください。
図4-1: DataObject、DataProvider、RenderingContextおよびUINodeの関係
DataProvider
はDataObject
のコレクションです。DataObject
は任意のデータのコレクションです。RenderingContext
は、DataProvider
からDataObject
を取得します。DataBoundValue
オブジェクト(またはuiXMLのdataObject
要素)により、すべてがUINode属性に連結されます。 非常に単純なページを作成します。リンクが1つだけあり、コンテンツを1つのDataObject
にデータ・バインドします。すべてをJSPで表示します。最初にいくつかの定数を定義します。
public class DataDemo
{
// ...
private static final String _DEMO_NAMESPACE = "http://www.example.org/";
private static final String _DATA_OBJECT_NAME = "TextData";
private static final Object _TEXT_KEY = "textKey";
private static final Object _DESTINATION_KEY = "urlKey";
}
ここで、UIX Componentsのノードを作成する関数を記述します。
public class DataDemo
{
static public UINode getPage()
{
// An ultra-simple example - we'll create a single link:
LinkBean link = new LinkBean();
link.setTextBinding(
new DataBoundValue(_DEMO_NAMESPACE,
_DATA_OBJECT_NAME,
_TEXT_KEY)
);
// Or, we can use the built-in DataBoundValue convenience methods
link.setDestinationBinding(_DEMO_NAMESPACE,
_DATA_OBJECT_NAME,
_DESTINATION_KEY);
// And put the link inside of a "BodyBean" (see below)
BodyBean body = new BodyBean();
body.addIndexedChild(link);
return body;
}
// ...
}
このコードは、前に作成したコードによく似ています。リンクのBeanを作成し、2つの属性をデータ・オブジェクトにバインドしています。新たに追加したのはBodyBean
のみです。これはデータ・バインドの一部ではありません。HTMLの<body>
タグの作成に常に使用されるUIX Components Beanです。
ここで、DataProviderを設定する関数を記述します。
注意: これは非常に基本的な例です。データがハードコードされており、基本のDictionaryData
クラスを使用してそのデータを格納しています。ただし、レンダリング時にJSPのPageContext
を渡すことができます。つまり、そのコンテキストを使用してここで必要なすべての状態を取得できます。さらに、DataProvider
およびDataObject
にデータが要求される際、RenderingContext
はその両方に渡されます。また、ServletRequest
やHttpSession
などの一般的なサーブレット・オブジェクトをRenderingContextから取得できるため、すべてのデータを事前にロードしておく必要はありません。データが実際に要求されるまで待機できます。
public class DataDemo
{
// ...
static public DataProvider getDataProvider()
{
DataObject data = _getData();
// And put it in a DataProvider
TableDataProvider provider = new TableDataProvider();
provider.put(_DEMO_NAMESPACE, _DATA_OBJECT_NAME, data);
return provider;
}
static private DataObject _getData()
{
// Build up a DataObject
DictionaryData data = new DictionaryData();
data.put(_TEXT_KEY, "Shameless promotion!");
data.put(_DESTINATION_KEY, "http://www.oracle.com");
return data;
}
// ...
}
ここで使用したTableDataProvider
に注意してください。このクラスでは、DataObject
やその他のDataProvider
を、ネームスペースおよび名前によって分割します。
最後に、レンダリングを行うJSPを記述します。
<%@ page contentType="text/html" %>
<%@ page import='oracle.cabo.ui.ServletRenderingContext'%>
<%@ page import='oracle.cabo.ui.beans.StyleSheetBean'%>
<%@ page import='oracle.cabo.ui.data.DataProvider'%>
<%@ page import='yourpackage.DataDemo'%>
<html>
<head>
<%
// Create a rendering context
ServletRenderingContext rContext =
new ServletRenderingContext(pageContext);
// Include the stylesheet that UIX Components needs
StyleSheetBean.sharedInstance().render(rContext);
%>
</head>
<%
// Get the data provider, and attach it to the
// rendering context
DataProvider provider = DataDemo.getDataProvider();
rContext.addDataProvider(provider);
DataDemo.getPage().render(rContext);
%>
</html>
この例を実行すると、「Shameless promotion!」という語句が表示されます。これは、オラクル社のWebサイトにリンクされています。
この例では、別のBean、StyleSheetBean
も使用しています。このBeanでは、UIX Componentsのすべてのページで必要とされるスタイルシートを自動的に追加します。このBeanの使用による、1行のコードでの高性能処理やカスタマイズの実現については、後半のトピックで説明します。ここでは、このBeanがHTMLのすべての<head>
に属することを理解してください。
必要な作業はこれですべてです。1つの単純なリンクをレンダリングするには少々過度な処理です。ただし、データベースとの対話、ローカライズされたテキストの提供、またはローカライズされたデータに関するその他のソースの提供を可能にする、汎用のDataProvider
が使用されています。
ここまでで、BoundValue
とDataObject
を使用して、動的データを含むページを設定する方法を学びました。ただし、DataObject
インタフェースでは、一連のプロパティがサポートされるだけです。表形式のデータ、またはJava配列のようなその他のデータを表示する場合、これは非常に不便なインタフェースです。そのような場合に、DataObjectList
インタフェースが使用されます。
このトピックで説明する最後の新規インタフェースは、単純なインタフェースです。
public interface DataObjectList
{
public int getLength();
public DataObject getItem(int index);
}
DataObjectList
は、実際にはDataObject
配列です。(実際には配列を使用しないため、DataObjectList
を不変に保つことができます。何かを設定するメソッドはないため、DataObject
を作成するか、DataObject
インスタンスを再利用することになります。)
ArrayDataSet
クラスは、DataObjectList
の単純で便利な実装です。DictionaryData
のように使用方法が単純ですが、すべてのDataObject
を事前に作成する必要があります。
DataObject[] array = new DataObject[10];
for (int i = 0; i < 10; i++)
{
array[i] = ...;
}
ArrayDataSet list = new ArrayDataSet(array);
DataObjectList
の独自の実装を記述する必要があることを覚えておいてください。
ここで、データをDataObjectList
から取り出して、ページに戻す方法について説明します。それには、現在のDataObject
を指定することが必要です。
すべてのデータ・フレームワークで、データの反復処理がサポートされています。これはJavaではforループに相当します。データベースではカーソルに相当します。UIX Componentsでは、現在のDataObject
の指定がそれに当たります。
このトピックで前述したRenderingContext.getDataObject
メソッドを思い出してください。これまで触れていませんが、DataObject
を取得するためのメソッドがもう1つあります。
public interface RenderingContext
{
// ..
/**
* Get a DataObject by namespace and name.
*/
public DataObject getDataObject(String namespaceURI,
String localName)
/**
* Get the "current" DataObject.
*/
public DataObject getCurrentDataObject();
// ..
}
この新しいメソッドgetCurrentDataObject()
は、現在のDataObject
を返します。これは、データベース・カーソルに相当するUIX Componentsのメソッドであり、DataObjectList
へのバインドに使用します。
Javaで現在のDataObject
からデータを取得するには、1つの引数を使用するDataBoundValue
の特別なコンストラクタ、または1つの引数を使用するBeanの便利なメソッドを使用します。
StyledTextBean textBean = new StyledTextBean();
textBean.setAttributeValue(UIConstants.TEXT_ATTR,
new DataBoundValue(TEXT_KEY));
// ... OR, EASIER ...
textBean.setTextBinding(TEXT_KEY);
ここまでの内容をすべて使用して、TableBean
のためのDataObjectList
を作成してみましょう。
public class DataDemo
{
// ...
static private DataObjectList _getDataForTable()
{
DataObject[] rows = new DataObject[5];
for (int i = 0; i < rows.length; i++)
{
DictionaryData row = new DictionaryData();
row.put(_TEXT_KEY, "Row " + i);
row.put(_TEXT_2_KEY, "Column 2, row " + i);
rows[i] = row;
}
return new ArrayDataSet(rows);
}
// ...
private static final Object _TEXT_2_KEY = "text2Key";
}
ここでも、例を単純にするためにハードコードされたデータを使用しましたが、実際のアプリケーションではより多くのデータを処理できます。ここで、このDataObjectList
を表に入れる必要があります。次のようにコールできます。
TableBean table = new TableBean();
table.setTableData(_getDataForTable());
ただし、このように設定されたデータは動的にならないため、リクエストごとにTableBean
を再利用することはできません。データを動的にするために、前のデモで使用したものと同じDataObject
にデータを追加できます。新しいキーも必要になります。また、コードを変更して、TableBean
をページに追加します。
public class DataDemo
{
// ...
static public UINode getPage()
{
// ... old code ...
// And now, let's create the table
TableBean table = new TableBean();
table.setTableDataBinding(_DEMO_NAMESPACE,
_DATA_OBJECT_NAME,
_TABLE_DATA_KEY);
body.addIndexedChild(table);
return body;
}
static private DataObject _getData()
{
// Build up a DataObject
DictionaryData data = new DictionaryData();
// ... old code ...
// And now, add the DataObjectList
data.put(_TABLE_DATA_KEY, _getDataForTable());
return data;
}
// ...
static private final Object _TABLE_DATA_KEY = "tableKey";
}
前に示した例のように、DataProvider
を使用して、DataObject
がRenderingContext
に追加されます。このコードを変更する必要はありません。また、表データのバインドに以前と同じコードを使用していることに注意してください。tableDataは単に別の属性であり、その他の要素は変わっていません。
ここには多数のオブジェクトがあります。作成したオブジェクトについて見なおします。
DataProvider
は1つのDataObject
を提供します。
DataObject
には、2つのテキストと1つのDataObjectList
が含まれます。
DataObjectList
には、5つのDataObject
が含まれます。
DataObject
には2つのテキストが含まれます。
現在のDataObject
の指定が機能している例はまだ示していませんが、表に列を追加した後で、現在のDataObject
の指定を使用する必要が生じます。TableBean
の各列は、索引付けされた子によって表されます。addIndexedChild()
を3回コールすると、表に3つの列が作成されます。これらの各列は、行ごとに子要素をスタンプとして一度使用することによってレンダリングされます。
異なる方法でレンダリングされる各要素を行ごとに取得する点が特長です。これは、現在のDataObject
の指定を使用することで可能になります。表で最初の行をレンダリングする際、RenderingContext.getCurrentDataObject()
により、tableDataのDataObjectList
の最初のDataObject
が返されます。2行目のレンダリング時には、getCurrentDataObject()
gは2番目のDataObject
を返します。以下同じ処理が繰り返されます。
ここで表に2つの列を追加します。1つは通常のテキスト列、もう1つは赤色の列です。(この例では、StyleSheetBean
で提供されるスタイルの1つである、ビルトインのOraErrorText CSSスタイルを使用しています。)
public class DataDemo
{
// ...
static public UINode getPage()
{
// ... old code ...
// And now, let's create the table
TableBean table = new TableBean();
table.setTableDataBinding(...);
// Create the first column
StyledTextBean firstColumn = new StyledTextBean();
// Use _TEXT_KEY for the data of the first column
firstColumn.setTextBinding(_TEXT_KEY);
// Add it to the table
table.addIndexedChild(firstColumn);
// Create the second column, using _TEXT_2_KEY
StyledTextBean secondColumn = new StyledTextBean();
secondColumn.setStyleClass("OraErrorText");
secondColumn.setTextBinding(_TEXT_2_KEY);
table.addIndexedChild(secondColumn);
// ...
}
// ...
}
これで終了です。ここで、このレンダリング方法について説明します。表では、外枠や背景の設定など多くの処理が実行されます。その後で最初のセルがレンダリングされます。
行0、列0については、表は最初のStyledTextBean
を使用します。RenderingContext.getCurrentDataObject()
によって、DataObjectList
の最初のDataObject
が返されます。これには、「Row 0」および「Column 2, row 0」のエントリがあります。StyledTextBean
により、テキストであるDataBoundValue
が要求されます。現在のDataObject
が取得され、_TEXT_KEY
が要求されます。_TEXT_KEY
は「Row 0」を返し、そのセルにレンダリングされます。
次に、行0、列1が処理されます。同じ、現在指定されているDataObject
が有効ですが、もう1つのStyledTextBean
がレンダリングを行います。これは_TEXT_2_KEY
を要求し、「Column 2, row 0」をレンダリングします。
次は行1です。最初のStyledTextBean
に戻りますが、ここではすでにDataObjectList
の次のDataObject
に移動しています。そのため、RenderingContext.getCurrentDataObject()
は異なるDataObject
を返します。この行の最初の列には「Row 1」、2番目の列には「Column 2, row 1」が入ります。
表全体についてこの処理が繰り返されます。これは単純な例です。列ヘッダーや行ヘッダー、あるいは表で実行可能な多数の処理は追加していません。また、編集不可の普通のテキストのみを使用しています。ただし、これらはすべて同じ方法で機能します。表の行ごとにレンダリング方法を変える場合、現在指定されているDataObject
に、その変更内容を示す情報が指定されている必要があります。
たとえば、各行にリンクを指定し、それぞれの宛先が異なるとします。この場合は簡単です。LinkBean
を使用して、destination属性を現在指定されているDataObject
にデータ・バインドし、各行の宛先を含むようにDataObjectList
を更新します。UIX Componentsでは、すべてについてデータ・バインドが可能なため、各行が異なるようにできます。
多くの開発者にとって、JavaBeans、マップ、リストおよび配列がDataObject
またはDataObjectList
に変換されるメカニズムは依然として理解しにくいでしょう。しかし、詳細を理解するのは有用だと考える開発者もいるでしょう。まず、いくつかのAPIは、特にJava Beansで、DataObject
またはDataObjectList
を明示的に受け入れます。第2に、アダプタをカスタマイズして、機能を追加したり、パフォーマンスを改善したりする場合があります。どちらの場合も、oracle.cabo.ui.data.bean.BeanAdapterUtils
を使用する必要があります。
任意のオブジェクト(Bean、Map
またはDataObject
そのもの)をDataObject
に変換するには、BeanAdapterUtils.getAdapter()
メソッドの1つを使用します。ほとんどの場合、次のとおりです。
public static DataObject getAdapter(RenderingContext context, java.lang.Object instance)
配列またはList
をDataObjectList
に変換するには、次のメソッドを使用します。
public static DataObjectList getAdapterList(RenderingContext context, java.lang.Object listInstance)
パフォーマンスを改善する前に、問題があることを確認します。アダプタ・レイヤーは軽量でパフォーマンスが良好なので、ほとんどのアプリケーションではコードの一部の最適化を問題視する必要がありません。
まずは、必要になるたびにアダプタ・オブジェクトを作成することを避けます。UIXで自動的にアダプタを作成するのではなく、手動で作成してその結果をキャッシュします。次に例を示します。
public class DataDemo
{
static public Object getLinkBean(
RenderingContext context,
String namespace,
String name)
{
return _sAdapter;
}
static private LinkDataBean _sInstance =
new LinkDataBean("http://www.oracle.com",
"Shameless bean promotion!");
// Create a static adapter:
static private DataObject _sAdapter;
static
{
try
{
_sAdapter = BeanAdapterUtils.getAdapter(_sInstance);
}
catch (InstantiationException ie) { }
catch (IllegalAccessException iae) { }
}
}
これは、Beanの存続期間が長い場合のみ有効です。Beanが1つのリクエストのみで使用されている場合、アダプタのキャッシュはそれほど有効ではありません。ただしそれがセッション・レベルまたはアプリケーション・レベルのBeanの場合、これはちょっとしたことですが簡単なパフォーマンス向上につながります。
次に、独自のDataObject
アダプタを記述して、イントロスペクションの本質的なパフォーマンスの問題に取り組みます。もしくはより効果的な方法として、アダプタを自動的に記述できます。UIXには、コンパイル済JavaBeansを取得しDataObject
アダプタを自動的に記述する、BuildBeanDOAdapter
というJavaベースのツールが組み込まれています。コマンドラインで次のように入力します。
java oracle.cabo.ui.tools.BuildBeanDOAdapter yourpackage.LinkDataBean
ここで生成されるコードすべては列記しませんが、次に注目すべき部分を示します。selectValue()
コードが例のLinkDataBean
に対して生成されています。
public Object selectValue(RenderingContext context, Object select)
{
LinkDataBean instance = _instance;
if (instance == null)
return null;
try
{
if ("url".equals(select))
return instance.getUrl();
if ("text".equals(select))
return instance.getText();
}
catch (Exception e)
{
context.getErrorLog().logError(e);
}
return null;
}
これはイントロスペクションよりもはるかに速い動作です。最新のベンチマーク(Windows 2000上のJava 1.3.0)では、20から30倍速いとされています。ただし、数百ものプロパティがある大規模なJavaBeansでは、String.equals()
に対するこれらのコールは重大な問題となり、この最適化が実際にはパフォーマンスにとって有害となる可能性があります。これらのケースのため、アダプタ構築ツールは"-fast"コマンドライン・オプションをサポートしています。結果として生じたクラスはStringハッシュコードを効果的に使用して、selectValue()
のスピードを基本イントロスペクションよりも速いレベルまで再び引き上げます。どちらの方法でアダプタ・クラスを構築しても、これらのアダプタは、boolean
およびint
の戻り値をBoolean
およびInteger
のオブジェクトに変換するような一般的なイントロスペクションよりもはるかにスムーズでもあります。
これらのアダプタ・クラスを一度作成すると、データ・バインド・コードを変更してこれらのアダプタを明示的に作成できます。
public class DataDemo
{
static public Object getLinkBean(
RenderingContext context,
String namespace,
String name)
{
LinkDataBean bean = ...;
return new LinkDataBeanDataObject(bean);
}
}
ただし、ここまでのすべての手順を確実にこなしていくことを確認するのは冗長な(エラーも起こりやすい)作業です。代替案としてアダプタを、静的なregisterAdapter()
コールによってBeanAdapterUtils
に直接登録できます。このコールを初期化コードに追加すると、残りのコードは以前のようにBeanインスタンスを続けて返すことができます。
public class DataDemo
{
static public Object getLinkBean(
RenderingContext context,
String namespace,
String name)
{
// Under the covers, UIX will use LinkDataBeanDataObject to
// adapt this bean, but we don't to know that here!
return new LinkDataBean(...);
}
// Register the adapter once
static
{
LinkDataBeanDataObject.registerAdapter();
}
}
UIXでは、JavaBeans仕様のすべての側面にデータが完全に依存している必要はありませんが、完全な方がより好ましい状態です。次のような状況はすべて自動的に処理されます。
LinkDataBean
を次のように記述することも可能です。
public class LinkDataBean
{
public LinkDataBean(String url, String text)
{
this.url = url;
this.text = text;
}
public String url;
public String text;
}
これは通常、独自のJavaBeansでは良い設計と言えませんが、特別なアダプタがなくてもjava.awt.Point
のようなレガシー・クラスをサポートできることを意味しています。
// The interface is public...
public interface LinkData
{
public String getText();
public String getUrl();
}
// ...but the bean is private
class LinkDataBean implements LinkData
{
...
}