UIX開発者ガイド | ![]() 目次 |
![]() 前へ |
![]() 次へ |
レンダリングされる情報をあるものから別のものへ変えるために属性値を変更するUIXのデータ・バインド・モデルを説明しました。では、レイアウト構造を変更する場合はどのように処理するのでしょうか。ページのセクションを非表示または表示に設定するにはどうすればよいでしょうか。セクションを何度も繰り返し、そのたびに異なる動的データを使用するにはどうすればよいでしょうか。また、UIXにJavaコードを統合して、最もニーズの高い動的ページを処理するにはどうすればよいでしょうか。このトピックでは、これらのオプションすべてを簡単にするためにUIXでサポートしている機能について説明します。
このトピックでは、UIXフレームワークのコア機能を確実に理解していることを前提としています。特に、「データ・バインド」のトピックを理解していることが必要です。
ここでは、次の項目について説明します。
<switcher>
要素<include>
要素動的ページの最も単純な機能は、rendered
属性です。この属性は、すべてのUIX ComponentsのUINode
クラスおよび対応するUIX要素でサポートされます。false
に設定すると、要素およびそのすべての子は無視されます。たとえば、次のUIXは「UIX is cool.」と出力します。
<flowLayout>
<contents>
UIX is
<styledText rendered="false" text="not "/>
cool.
</contents>
</flowLayout>
非常に単純です。データがバインドされている場合は、1つの変数でページの小さい部分から大きい部分までを表示または非表示にできます。
<table data:rendered="hasTablePermission@permissions">
<contents>
...
</contents>
</table>
rendered
は強力かつ単純ですので、この属性を試した後で、このトピックで後述する複雑な手法を活用してください。
rendered
および<table>
の列を使用する際は、注意する必要があります。この<table>
コンポーネントでは、<contents>
要素内の各要素が列を定義しています。これらの列を定義している要素でrendered
を使用してもまったく問題ありませんし、rendered
をデータ・バインドすることもできます。しかし、現在の(行ごとの)DataObject
に対してrendered
をデータ・バインドすることはできません。
<table>
<contents>
<!-- Legal -->
<styledText rendered="false" ... />
<!-- Also legal -->
<styledText data:rendered="foo@someName" ... />
<!-- NOT legal -->
<styledText data:rendered="perRow" ... />
</contents>
</table>
列の内容を行ごとに変更するため、このような記述をする場合があります。ただし、より単純にするための回避方法があります。<switcher>
要素(次のセクションで説明)の使用です。しかし、rendered
を使用する場合は、その列に含める要素すべてを<column>
要素内にラップしてください。
<table>
<contents>
<!-- Legal -->
<styledText rendered="false" ... />
<!-- Now legal -->
<column>
<contents>
<styledText data:rendered="perRow" ... />
<styledText data:rendered="otherPerRow" ... />
</contents>
</column>
</contents>
</table>
rendered
属性はページの一部を表示または非表示にする場合に適していますが、複数あるオプションの1つを表示する場合は負荷がかかります。10の可能性が存在する場合、DataObject
から10のtrue
またはfalse
値を提供する必要があるためです。選択肢の名前など、1つの値を使用して分岐する方がはるかに簡単です。これを可能にするのがUIXの<switcher>
要素で、UIX ComponentsのSwitcherBean
を使用します。
<switcher>
要素には、childName
という1つの属性があります。この要素には、<case>
というタイプの要素のみが含まれます。<case>
要素には、name
という1つの属性があります。表示要求があると、nameがchildName
属性に一致する子が検出され、その子のみが表示されます。この例を次に示します。次のUIXは「Second case」と出力します。
<switcher childName="second">
<case name="first">
<styledText text="First case"/>
</case>
<case name="second">
<styledText text="Second case"/>
</case>
</switcher>
同じname
は2回使用できませんが、childName
がどのname
にも一致しないという状況となることは問題ありません。この場合<switcher>
は何もレンダリングしません。
重要な点は、<switcher>
がUINode要素であることです。したがって、この要素を使用できるのは、<styledText>
や<flowLayout>
など他の任意のUINode要素を使用できる場所のみです。<switcher>
要素をUIXの任意の場所に追加することはできません。たとえば、次のUIXは無効です。<data>
要素の内容は、UINodeではなくDataProvider UIX要素となるためです。
<dataScope>
<provider>
<data name="...">
<!-- ILLEGAL -->
<switcher ...>
</switcher>
<data>
</provider>
</dataScope>
なぜJavaのswitch文のように<default>
要素がないのでしょうか。UIXのデータ・バインドでは、余分な構文を追加せずにこのコードを処理できるためです。あるUIX属性をデータ・バインドして明示的に指定すると、その明示的な値がデフォルトとして使用されます。
<switcher childName="defaultName" data:childName="...">
<case name="first">
...
</case>
<case name="second">
...
</case>
<!-- A default choice -->
<case name="defaultName">
...
</case>
</switcher>
データ・バインドで使用されているchildName
を解決できない場合、つまり、nullという結果になる場合、UIXでは明示的に指定されたdefaultName
という値に戻ります。
SwitcherBean
クラスは、UIX ComponentsのBeanを使用してJavaコードを記述する場合に使用します。<case>
要素のかわりにsetNamedChild
をコールします。次に、Javaのみで使用される最初のUIXの例を示します。
SwitcherBean switcher = new SwitcherBean();
switcher.setChildName("second");
switcher.setNamedChild("first",
new StyledTextBean("First case", null));
switcher.setNamedChild("second",
new StyledTextBean("Second case", null));
<switcher>
はほぼすべてのケースで正しく動作しますが、問題の発生する要素もあるので注意してください。たとえば、<switcher>
は<column>
要素の内側に配置する必要があるため、<switcher>
で<table>
の<column>
要素をラップすることはできません。<table>
は子である<column>
を検索しますが、(<switcher>
でラップされているため)これらを参照せず、問題が発生します。あまり目立たないケースですが、このような問題が存在することを認識しておく必要があります。rendered
属性にはこのような問題はありません。
ページの一部分を表示または非表示にできるようになりました。しかし、ページのセクションを反復させるなどの非常に一般的なケースを処理するにはまだ不十分です。たとえば、次のHTMLが必要だと想定します。
同じコンテンツを複数回スタンプする必要がありますが、スタンプごとにデータは異なります。これは、UIXのchildData
属性およびUIX ComponentsのDataObjectListNodeList
クラスで実装できます。
まず、1行分のUIXを記述します。
<tableLayout>
<contents>
<rowLayout>
<contents>
<styledText text="Name:"/>
<textInput name="????" text="????"/>
<cellFormat width="10"/>
<styledText text="Phone number:"/>
<textInput name="????" text="????"/>
<contents>
</rowLayout>
</contents>
</tableLayout>
必要な結果を取得するためにchildData
を追加します。ほとんどの属性とは異なり、childData
は直接行には設定しません。<contents>
要素に設定します。もう1つ異なる点は、値を直接設定することができず、データ・バインドを使用する必要があるという点です。次の例は、childData
および<dataScope>
を追加してデータの取得場所を定義したものです。
<dataScope>
<provider>
<data name="source">
<method class="SampleClass" method="getRows"/>
</data>
</provider>
<contents>
<tableLayout>
<contents data:childData="rows@source">
<rowLayout>
...
</rowLayout>
</contents>
</tableLayout>
</contents>
</dataScope>
<rowLayout>
およびgetRows()
メソッドの内容は後述しますが、ここでは、data:childData
を<rowLayout>
ではなく<tableLayout>
の内容に配置していることに注意してください。行レイアウトの内容ではなく、行レイアウトを繰り返すよう記述しています。(行レイアウトの<contents>
要素に配置すると、同じ行が何回か繰り返されるのではなく、非常に長い1つの行になります。)
childData
のJava型は、UIXのDataObjectList
です。これらは、DataObject
の配列のようなものです。DataObjectList
の各DataObject
ごとにコンテンツを1回記述します。rows@source
にDataObject
が5つある場合は、行も5つです。レンダリングするたびに、現在のDataObject
がそのリストの次のDataObject
になります。これは、UIXで<table>
およびtableData
属性に対して使用するメカニズムとまったく同じです。
では、Javaモデルを記述します。UIXのイントロスペクション・レイヤーを使用し、単純なJavaクラスをDataObject
に変えます。
import oracle.cabo.ui.data.DataObjectList;
import oracle.cabo.ui.data.bean.BeanArrayDataObjectList;
public class SampleClass
{
// For simplicity's sake, we're using public fields.
// The introspection code can handle "get" methods too.
public class Person
{
public String id;
public String firstName;
public String phoneNumber;
}
static public DataObject getRows(
RenderingContext context, String ns, String name)
{
// Get all of the people in an array
Person[] people = _getPeople(...);
// Use introspection to turn it into a DataObjectList
DataObjectList list =
new BeanArrayDataObjectList(people);
// And return that one item in a DataObject
return new DictionaryData("rows", list);
}
}
<rowLayout>
要素を記述します。
...
<tableLayout>
<contents data:childData="rows@source">
<rowLayout>
<contents>
<styledText text="Name:"/>
<!-- text input: bind the "text" attribute
directly to the "firstName" property of our data.
For the "name", concatenate the ID and "Name" -->
<textInput data:text="firstName">
<boundAttribute name="name">
<concat>
name<dataObject select="id"/>
</concat>
</boundAttribute>
</textInput>
<cellFormat width="10"/>
<styledText text="Phone number:"/>
<!-- another text input - pretty much the same as before -->
<textInput data:text="phoneNumber">
<boundAttribute name="name">
<concat>
phone<dataObject select="id"/>
</concat>
</boundAttribute>
</textInput>
<contents>
</rowLayout>
</contents>
<tableLayout>
最初の<textInput>
を取り出して分析します。
<textInput data:text="firstName">
<boundAttribute name="name">
<concat>
firstName<dataObject select="id"/>
</concat>
</boundAttribute>
</textInput>
この例について注意する点が2つあります。
text
属性はDataObject
のfirstName
プロパティである必要があります。これはdata:text="firstName"
によって実行されます。
name
属性は注意が必要です。この属性はすべての行で一意で、行ごとに一貫したパターンに従っていることが望ましい場合があります。この例では、文字列の連結という単純な手法を使用してIDプロパティとfirstName
を連結しています。したがって、IDが1155のname
属性はfirstName1155です。
childData
については、<contents>
内で使用できる要素は1つのみなどの制限はありません。要素が2つある場合は、それぞれが繰り返されます。一般に、要素がM個あり、DataObjectList
にエントリがN個ある場合、UIXは要素がM×N個あるかのように動作します。たとえば、UIXを次のように変更します。
<tableLayout>
<contents data:childData="rows@source">
<rowLayout>
<styledText text="Name:"/>
<textInput .../>
</rowLayout>
<rowLayout>
<styledText text="Phone number:">
<textInput .../>
</rowLayout>
</contents>
</tableLayout>
すると、HTMLの結果は次のようになります。
1行を4列に渡ってスタンプするのではなく、それぞれ2列を持つ行を2つスタンプしています。Javaレイヤーには変更は何もありません。
Javaで開発している場合もこの機能をすべて利用できますが、構文は大きく異なります。属性を設定するかわりに、DataObjectListNodeList
クラスを使用します。UINodeList
はUINode
の索引付けされた子のための記憶域を管理する抽象化オブジェクトであり、UINodeList
を置き換えることでノードの動作を大幅に変更できます。次に、UIX ComponentsおよびJavaで記述した例を示します。
import oracle.cabo.ui.collection.DataObjectListNodeList;
import oracle.cabo.ui.data.DataBoundValue;
...
TableLayoutBean tlb = new TableLayoutBean();
BoundValue rows = new DataBoundValue(_YOUR_NAMESPACE, _YOUR_NAME, "rows");
tlb.setIndexedNodeList(new DataObjectListNodeList(rows));
RowLayoutBean rowLayout = new RowLayoutBean();
tlb.addIndexedChild(rowLayout);
...
注意する点が1つあります。UINodeList
は設定されると、そのノードの索引付けされた子をすべて消去してしまいます。そのため上の例のように、子を追加する前にsetIndexedNodeList()
をコールする必要があります。
これまでに説明した手法で、多くのことを実行できます。しかし、実際にはこれらの手法にも限界があります。UIXツリーにある要素を繰り返したり非表示にすることはできますが、ツリーに存在しない要素を追加することはできません。
たとえば、任意のSQL問合せの結果を表示および編集するとします。問合せを実行する以外に列数を知る方法はなく、各列の型を知る方法もありません。Javaでは、これはそれほど大きな問題ではありません。実行時にUINode
を作成し、キャッシュの情報を消去すればよいからです。ここで、UIXアプリケーションの部分で紹介した手法を再び利用します。Pure JavaベースのページをUIXページと組み合せる手法です。ただし、ページはできるだけUIXで記述し、必要な部分にのみJavaを使用してください。これは、<include>
要素(およびJavaのIncludeBean
)を使用して実行できます。
あるUIX Controllerのページを別のページにインクルードする場合に<include>
を使用するということは、すでに説明済です。
<include ctrl:node="aPageName"/>
UIX Controllerでインクルードを処理するかわりに、UIXの標準のデータ・バインドを使用できます。この場合のnode
は上の例におけるnode
とはまったく別の属性で、実際の型はUINode
です。では、UIXページを記述します。
<dataScope>
<provider>
<data name="source">
<method class="SampleClass" method="getNode"/>
</data>
</provider>
<contents>
<flowLayout>
<contents>
UIX knows
<include data:node="javaNode@source"/>
</contents>
</flowLayout>
</contents>
</dataScope>
次にJavaコードです。
public class SampleClass
{
static public DataObject getNode(
RenderingContext context, String ns, String name)
{
// Create a StyledTextBean
UINode node = new StyledTextBean("includes!", null);
// And return that one node in a DataObject
return new DictionaryData("javaNode", node);
}
}
これですべてです。ページは、次のように記述されているかのように動作します。
<flowLayout>
<contents>
UIX knows
<styledText text="includes!"/>
</contents>
</flowLayout>
非常に重要な注意点があります。<switcher>
と同様に、<include>
要素はUINode
要素です。したがって、この要素を追加できるのは、UINode
要素を配置できる場所のみです。字句取込み機能と誤解されることがありますが、<include>
は厳密にはUINode
の置換レイヤーです。
これと同じ手法を、完全にUIX Componentsベースであるページに使用できます。この考え方は意外にも有効です。レンダリングのたびにページ全体およびそのUINode
すべてを再作成するのではなく、ページの大部分をキャッシュに保持し、変更される部分のみ再作成します。IncludeBean
の直接の使用においては、(UIXでは表面化しない)複雑な点がいくつかあるため、試す前にドキュメントを参照することをお薦めします。
<include>
要素は、新規要素のページへの連結に適しています。データ・バインドにrendered
フラグ、<switcher>
およびchildData
を合せると、かなり大きなツールボックスになります。ここからは静的uiXMLファイルを取得して、それをJavaで任意に変更する方法について説明します。
uiXMLはXMLであるため、1つのアプローチとしてDOM、JDOM、SAX、XSLTなどの標準のXML解析APIの使用があげられます。UIXに読み込むには、XMLを変更し、変更したXMLをUIXに渡します。これは正常に機能します。つまり、パーサーがuiXMLを参照する前に変更を行うため、自由にソースXMLをマークアップすることができ、静的XML以外のソースから生成することもできます。ただし、UIX ComponentsのJava APIを使用するにはDeltaTree
APIが必要です。
DeltaTree
はUINode
を受け取り、その上部に差分(デルタ)を配置します。基礎となるJavaツリーはまったく変更しないため、完全にスレッド・セーフです。原理は簡単です。特定のUINode
要素をnodeID
属性でマークを付けます。(この属性は、サーバーでノードの識別に使用されます。クライアントには表示されません。)Javaからは、これらのノードを検索してプロキシにラップし、プロキシで属性を設定します。次に、元のツリーではなくプロキシをレンダリングします。このAPIの詳細はJavadocに記述されていますが、ここでは非常に単純な例を説明します。
まずは単純なuiXMLページです。ここではUINode
を1つ指定し、そのstyleClass属性を更新してCSSスタイルを変更するということを行います。
<page xmlns="http://xmlns.oracle.com/uix/controller">
<javaClass name="DeltaTreeTest"/>
<content>
<stackLayout xmlns="http://xmlns.oracle.com/uix/ui">
<contents>
<styledText text="One"/>
<styledText text="Two"/>
<styledText text="Three" nodeID="three"/>
</contents>
</stackLayout>
</content>
</page>
非常に簡単な作業ですが、次の2点に注意してください。
<javaClass>
要素を定義しました。この要素によって、このページの処理に使用したPageDescription
Javaクラスがオーバーライドされます。この例の目的においては、この方法によってDeltaTree
が簡単に組み込まれます。
<styledText>
要素の1つにnodeID
属性を設定しました。これにより、ノード階層がわからなくても推測せずにJavaコードからこの要素を検索できます。
次はJavaコードです。
package oracle.cabo.servlet.demo;
import oracle.cabo.ui.MutableUINode;
import oracle.cabo.ui.UINode;
import oracle.cabo.ui.beans.StyledTextBean;
import oracle.cabo.ui.path.DeltaTree;
import oracle.cabo.ui.path.PathUtils;
import oracle.cabo.ui.path.Path;
import oracle.cabo.servlet.ui.DefaultUINodePageDescription;
public class DeltaTreeTest extends DefaultUINodePageDescription
{
public UINode getRootUINode()
{
// Get the original root UINode
UINode node = super.getRootUINode();
// Wrap it in a DeltaTree
DeltaTree tree = new DeltaTree(node);
// Find the node we want
Path path = PathUtils.findPathWithNodeID(null, node, "three");
// Turn it into a MutableUINode
MutableUINode nodeThree = tree.getMutableUINode(null, path);
// Set the style class (using a static method, instead of casting)
StyledTextBean.setStyleClass(nodeThree, "OraErrorText");
// And render the DeltaTree, instead of the original root
return tree.getRoot();
}
}
コードを1行ずつ説明します。
public class DeltaTreeTest extends DefaultUINodePageDescription
PageDescriptionのクラスを設定します。PageDescriptionは、ページのレンダリング部分とイベント処理部分の両方をカプセル化するためにUIXで使用されます。デフォルト・クラスを拡張しています。
public UINode getRootUINode()
このメソッドは、ページのボディに対応するUINodeを返します。
UINode node = super.getRootUINode()
静的な変更前のページ・ノードを取得します。上の例では<stackLayout>
要素になりますが、一般的にはこれは、<stackLayout>
要素であるとはかぎりません。
DeltaTree tree = new DeltaTree(node);
DeltaTree
にラップしています。元のノードはまったく変更されていませんが、この時点から差分となるツリー部分(デルタ・ツリー)を参照するということに注意してください。デルタ・ツリーのルートをラップすることも重要です。親はまだ元の子を指しているため、DeltaTree
の子ノードだけをラップすることは適切ではありません。
Path path = PathUtils.findPathWithNodeID(null, node, "three");
Path
オブジェクトは、ツリー内でのノードの検索にUIX Componentsで使用されます。ルートからノードに到達するために使用されるすべてのステップ(すべてのgetIndexedChild()
またはgetNamedChild()
コール)を格納します。PathUtils
クラスには、複数の基準でノードを検索するユーティリティ・メソッドがあります。渡したnull
はRenderingContext
です。null
を渡すことは問題ありませんが、データ・バインドされた属性の値をレンダリング・コンテキストなしで取得することはできません。
MutableUINode nodeThree = tree.getMutableUINode(null, path);
Path
を使用して、ターゲット・ノードの可変のラッパーを取得します。
StyledTextBean.setStyleClass(nodeThree, "OraErrorText");
ターゲットに属性を設定します。StyledTextBean
でstaticメソッドを使用しています。可変のノードをStyledTextBean
に型変換することはできません。これはそのクラスのインスタンスではありません。
return tree.getRoot();
最後に、すべての変更済状態が含まれるルートをDeltaTree
に要求します。元のnodeオブジェクトはまったく変更していないため、ここでnode
を返すことは無意味で、その場合には変更は取得されません。
それほど難しくはありません。いくつかの属性を設定する以外にも数多くの作業ができます。作成したUINode
に対し、ノードの追加、ノードの削除、UINodeList
の設定、データ・バインドの設定など、何でも行うことができます。
UIX ComponentsのBeanを直接使用しているJava開発者は、DeltaTree
も使用できます。もちろん、BeanをJavaで作成しているため、これらのBeanを直接変更することもできます。しかし、あるリクエストで作成されたBeanのツリーを別のリクエストで再利用する場合は、DeltaTree
の使用が有用です。これらのBeanのツリーをスレッド・セーフにし、メモリーを効率的に使用できます。
データ・バインドの基礎とこのトピックで説明したすべての手法を利用すると、uiXMLおよびUIX Componentsを使用して動的ページを作成できます。