別のブラウザで表示すると、JavaScriptによってこのドキュメントの表示形式が変わる場合があります。ただしドキュメントの内容に影響はありません。

UIX開発者ガイド Go to Table of Contents
目次
Go to previous page
前へ
Go to next page
次へ

12. uiXMLページの動的構造

レンダリングされる情報をあるものから別のものへ変えるために属性値を変更するUIXのデータ・バインド・モデルを説明しました。では、レイアウト構造を変更する場合はどのように処理するのでしょうか。ページのセクションを非表示または表示に設定するにはどうすればよいでしょうか。セクションを何度も繰り返し、そのたびに異なる動的データを使用するにはどうすればよいでしょうか。また、UIXにJavaコードを統合して、最もニーズの高い動的ページを処理するにはどうすればよいでしょうか。このトピックでは、これらのオプションすべてを簡単にするためにUIXでサポートしている機能について説明します。

このトピックでは、UIXフレームワークのコア機能を確実に理解していることを前提としています。特に、「データ・バインド」のトピックを理解していることが必要です。

ここでは、次の項目について説明します。

rendered属性

動的ページの最も単純な機能は、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は強力かつ単純ですので、この属性を試した後で、このトピックで後述する複雑な手法を活用してください。

<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>

<switcher>要素

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属性にはこのような問題はありません。

反復: childData属性およびDataObjectListNodeList

ページの一部分を表示または非表示にできるようになりました。しかし、ページのセクションを反復させるなどの非常に一般的なケースを処理するにはまだ不十分です。たとえば、次の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@sourceDataObjectが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つあります。

  1. text属性はDataObjectfirstNameプロパティである必要があります。これはdata:text="firstName"によって実行されます。
  2. 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クラスを使用します。UINodeListUINodeの索引付けされた子のための記憶域を管理する抽象化オブジェクトであり、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()をコールする必要があります。

Javaとの統合: <include>要素

これまでに説明した手法で、多くのことを実行できます。しかし、実際にはこれらの手法にも限界があります。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では表面化しない)複雑な点がいくつかあるため、試す前にドキュメントを参照することをお薦めします。

Javaとの統合: DeltaTree

<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が必要です。

DeltaTreeUINodeを受け取り、その上部に差分(デルタ)を配置します。基礎となる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点に注意してください。

  1. <javaClass>要素を定義しました。この要素によって、このページの処理に使用したPageDescription Javaクラスがオーバーライドされます。この例の目的においては、この方法によってDeltaTreeが簡単に組み込まれます。
  2. 3つの<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行ずつ説明します。

  1. public class DeltaTreeTest extends DefaultUINodePageDescription

    PageDescriptionのクラスを設定します。PageDescriptionは、ページのレンダリング部分とイベント処理部分の両方をカプセル化するためにUIXで使用されます。デフォルト・クラスを拡張しています。

  2. public UINode getRootUINode()

    このメソッドは、ページのボディに対応するUINodeを返します。

  3. UINode node = super.getRootUINode()

    静的な変更前のページ・ノードを取得します。上の例では<stackLayout>要素になりますが、一般的にはこれは、<stackLayout>要素であるとはかぎりません。

  4. DeltaTree tree = new DeltaTree(node);

    DeltaTreeにラップしています。元のノードはまったく変更されていませんが、この時点から差分となるツリー部分(デルタ・ツリー)を参照するということに注意してください。デルタ・ツリーのルートをラップすることも重要です。親はまだ元の子を指しているため、DeltaTreeの子ノードだけをラップすることは適切ではありません。

  5. Path path = PathUtils.findPathWithNodeID(null, node, "three");

    Pathオブジェクトは、ツリー内でのノードの検索にUIX Componentsで使用されます。ルートからノードに到達するために使用されるすべてのステップ(すべてのgetIndexedChild()またはgetNamedChild()コール)を格納します。PathUtilsクラスには、複数の基準でノードを検索するユーティリティ・メソッドがあります。渡したnullRenderingContextです。nullを渡すことは問題ありませんが、データ・バインドされた属性の値をレンダリング・コンテキストなしで取得することはできません。

  6. MutableUINode nodeThree = tree.getMutableUINode(null, path);

    Pathを使用して、ターゲット・ノードの可変のラッパーを取得します。

  7. StyledTextBean.setStyleClass(nodeThree, "OraErrorText");

    ターゲットに属性を設定します。StyledTextBeanでstaticメソッドを使用しています。可変のノードをStyledTextBeanに型変換することはできません。これはそのクラスのインスタンスではありません。

  8. return tree.getRoot();

    最後に、すべての変更済状態が含まれるルートをDeltaTreeに要求します。元のnodeオブジェクトはまったく変更していないため、ここでnodeを返すことは無意味で、その場合には変更は取得されません。

それほど難しくはありません。いくつかの属性を設定する以外にも数多くの作業ができます。作成したUINodeに対し、ノードの追加、ノードの削除、UINodeListの設定、データ・バインドの設定など、何でも行うことができます。

UIX ComponentsのBeanを直接使用しているJava開発者は、DeltaTreeも使用できます。もちろん、BeanをJavaで作成しているため、これらのBeanを直接変更することもできます。しかし、あるリクエストで作成されたBeanのツリーを別のリクエストで再利用する場合は、DeltaTreeの使用が有用です。これらのBeanのツリーをスレッド・セーフにし、メモリーを効率的に使用できます。

まとめ

データ・バインドの基礎とこのトピックで説明したすべての手法を利用すると、uiXMLおよびUIX Componentsを使用して動的ページを作成できます。