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

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

10. 表

tableは、UIX Componentsで使用可能なものの中で、最も強力で複雑なユーザー・インタフェース・コンポーネントです。表形式のデータを表示または更新するWebアプリケーションでは、tableを1つ以上使用する必要があります。このトピックでは、tableコンポーネントの構成方法をすべて調べます。

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

Tableの必要性

Tableで使用可能なオプションを見ると、どんな問題でも解決できるように考えるかもしれませんが、そうではありません。Tableは、2次元の形式で記述されたデータを表示(場合によっては更新)するために用意されています。Tableを使用すると効率的な場合をまとめると、次のようになります。

これらに当てはまらない場合、Tableは使用に適した部品ではありません。次の経験則は、Tableの使用が適切な場合を示していますが、すべての状況に適用されるわけではありません。

一方、Tableを使用できない状況もあります。

前述の条件を踏まえてもTableの使用が適切であると思われる場合のために、次のセクションから作成方法を説明します。

Tableの概念

Tableの外観を設計する際には、比較されるデータ項目が行として存在し、これらの項目に対する個々の内容または処理が列として存在することに常に留意してください。項目の性質が似ていることがすでに確認されているため、それらの項目に対する個々の内容数も一致する必要があります。したがって、Tableの列はすべての行に対して同じになります。ただし、表示するデータ項目数および行数が異なる場合があります。たとえば、Webアプリケーションのショッピング・カートを表すために使用するTableでは、最初は項目が1つもありませんが、ショッピングの後には複数の項目が存在します。

データ項目の行と列が交差する各部分をセルと呼びます。したがって、表は多くのセルの2次元表示になります。各データ項目に対して1行のセルがあり、セルの列は、多くのデータ項目間で比較される同一の内容または処理を表します。

Tableでは、ユーザーがデータを連想しやすいラベルが必要です。これには、列ヘッダーおよび行ヘッダーを使用します。列ヘッダーは列の最上部にある水平方向のラベルで、ショッピング・カート内の品目の価格など、列に含まれる内容または処理を表し、左端に垂直方向に表示される行ヘッダーは、各データ項目の前で使用して、たとえば実際に発注する注文を明確化します。データの概要を表示すると便利な場合は、表の下部で列フッターを使用します。一貫性を保つために、列ヘッダーおよび列フッターの項目にもセルがあるものとします。

Tableでは、拡張した表示および追加情報の表示に他の書式オプションを使用できますが、これらについてはこのトピック全体を通して説明していきます。それでは、表を作成しましょう。

スタンプ

先に進む前に、最初のuiXML例を見てみましょう。これは最も基本的な表です。

  <table xmlns="http://xmlns.oracle.com/uix/ui"/>

この例は設計が十分ではなく、実際には何も表示しません。これは、UIX Componentsのネームスペースにtable要素を表示することを宣言したものです。実際にはまだ表を構成していないため、空白のように見えます。何かを表示するには、データ項目および列を追加する必要があります。これにはスタンプを使用します。

スタンプとは何でしょうか。スタンプとは、特定のページで複数回レンダリングされる単なる(任意の)UINodeです。機密書類にスタンプで印を付けるように使用されるたびに同じマークを生成するため、スタンプと呼ばれます。スタンプには、繰り返す必要のあるボタン、テキスト・フィールドまたはその他の任意の部品を指定できます。Tableでは各列にスタンプが1つ存在し、このスタンプが表の列のセルすべてに対して下方向に繰り返され、レンダリングされます。表に列のスタンプを追加するには、tableに索引付けされた子を生成します。索引付けされた子はそれぞれ新規の列スタンプになります。最初の索引付けされた子が一番左の列スタンプになり、後続の子は直前のスタンプの右に追加されます。

表の列にデータをレンダリングするためにスタンプの概念を使用するのはなぜでしょうか。表は、類似するデータ項目の比較に使用されることに注意してください。各列はデータ項目の1つの内容を表すため、列のセルの外観を類似させることには意味があります。各セルのスタンプを類似させるが同一にはしない場合については、このトピックで後述します。

最初の列スタンプをtableの例に追加します。

<table ... >
  <contents>
    <!-- the first column stamp, a text node -->
      <text text="SampleText"/>
  </contents>
</table>

tableにスタンプを追加しましたが、まだ特に何も表示されません。これは、次の基本のルールによるものです。

各スタンプは、すべてのDataObjectに対して1回レンダリングされる。

このルールは、DataObjectがない場合、スタンプはレンダリングされないことを意味します。そこで、データをいくつか追加します。

必要なDataObjectは、最初に調べる属性であるtableDataにより提供されます。この属性は、表の行を表します。前述したように、表の行数はその存続期間中に変化することがあります(ユーザーがショッピング・カートの品を追加または削除した場合など)。可変長リストを伴うすべてのUIX Componentsの概念と同様に、このtableData属性では、タイプとしてDataObjectListが含まれています。DataObjectListは、長さを返すことと、各DataObjectへの索引によるアクセスを可能にすることという2つの処理のみを実行することに注意してください。tableDataでは、DataObjectListの長さで表示される行数が決まり、リスト内の各DataObjectで対応する行のデータを提供します。

例にサンプル・データを配置します。これは単なるデモであるため、uiXMLのインライン・データ機能を使用します。ほとんどの実際のアプリケーションでは、より複雑な動的ソースを介してデータを公開します。

<dataScope xmlns="http://xmlns.oracle.com/uix/ui"
           xmlns:data="http://xmlns.oracle.com/uix/ui">
  <provider>
    <!-- all the data used by our table demo -->
    <data name="demoTableData">
      <inline>
        <!-- all the row DataObjects used by our table for tableData -->
        <demoRowData/>
        <demoRowData/>
        <demoRowData/>
      </inline>
    </data>
  </provider>

  <contents>
    <table data:tableData="demoRowData@demoTableData">
      <contents>
        <!-- the first column stamp, a text node -->
          <text text="SampleText"/>
      </contents>
    </table>
  </contents>
</dataScope>

デモのコードにいくつかの項目を追加しました。

  1. 表を<dataScope>要素に配置し、表にデータソースを指定しました。表自体は<dataScope>のコンテンツ部に移動され、データは<provider>セクションに配置されています。
  2. データはuiXMLのインライン・データ構文を使用して指定し、この例ではdemoTableDataという名前で格納します。demoTableDataの内部では、3つのdemoRowData要素を宣言しました。この3つのデータ要素の名前はすべて同じであるため、uiXMLはこれらを1つのDataObjectListにマージします。マージされたDataObjectListは要素3つ分のサイズで、各要素はDataObjectListの索引付けされた3つのDataObjectの1つを表します。
  3. table要素には、data:tableDataという新規の属性があります。この属性は、行の表示に必要なDataObjectListをtableに提供します。具体的には、providerセクションに配置したdemoRowDataというDataObjectListが、行データソースに選択されます。

この例を実行すると、縦列に3つのセルがあり、各列に同じサンプル・テキストのある表が表示されます。これは、1つの列スタンプ(text要素)があり、tableDataのリストに3つのDataObjectがあるためです。前述のルールのとおり、テキスト・スタンプはリスト内のすべてのDataObjectに対して1回レンダリングされます。このため、テキストが3回表示されます。

少し変更を加えるとどうなるでしょうか。まず、行リストに4つ目のDataObjectを追加します。各スタンプはすべてのDataObjectに対して1回レンダリングされるという基本のルールのとおり、表に4行がレンダリングされます。

<dataScope ... >
  <provider>
    <!-- all the data used by our table demo -->
    <data name="demoTableData">
      <inline>
        <!-- all the row DataObjects used by our table for tableData -->
        <demoRowData/>
        <demoRowData/>
        <demoRowData/>
        <demoRowData/>
      </inline>
    </data>
  </provider>

  <contents>
   ...
  </contents>
</dataScope>

次に、2番目の列スタンプを追加して、表の大きさを変えてみます。今回のスタンプはボタンです。ルールのとおり、既存のテキスト・スタンプと新規のボタンの両方がそれぞれ4回レンダリングされます。

<dataScope ... >
  <provider>
   ...
  </provider>

  <contents>
    <table data:tableData="demoRowData@demoTableData">
      <contents>
        <!-- the first column stamp, a text node -->
        <text text="SampleText"/>
        <!-- the second column stamp, a button -->
        <button text="Push Me" destination="http://www.example.org"/>
      </contents>
    </table>
  </contents>
</dataScope>

この2つの概念を拡張し、表に任意の数の行を配置したり、列のタイプおよび数を変更することができます。試してみてください。

Tableでのデータ・バインド

スタンプは、すべての行に同じデータを表示する表には適していますが、実際にはこのような表はありません。有用な例を作成するには、データ・バインドが必要です。

データ・バインドは、レンダリングごとにページのコンテンツを変更するため、UIX Components全体で使用されることに注意してください。ここではデータ・バインドをさらに一歩進め、行ごとに表のコンテンツを変更します。UINodeでスタンプされる列は行ごとに変更されないため、データ・バインドを使用して、特定のセルで実際にスタンプされる内容を変更します。これにより、列の各セルにスタンプされるコンテンツのタイプは似ているが(列の各セルがテキスト・フィールドであるなど)、コンテンツ自体は異なる(各テキスト・フィールドのテキストが行ごとに異なるなど)というような、より有用な目的でスタンプを使用できます。

これは、UIX Componentsの現行のDataObjectの指定という概念で機能します。他のUIX Componentsのデータ・バインドと同様に、ノードの属性は、特定のDataObjectではなく現行指定されたDataObjectにバインドできます。これにより、問合せ時にどのDataObjectが現行であるかに基づいて、生成される結果が変わります。表では、表の各行をレンダリングする際に現行のDataObjectを変更することにより、この概念を可能にします。このため、列スタンプの属性が現行のDataObjectの同じキーにバインドされている場合、その属性の実際の値は表のすべての行で変わります。

表のtableData DataObjectListの各要素は、対応する行の現行のDataObjectとして使用されるため、DataObjectを使用してレンダリングする行数を決定することは適切な処理です。説明のため、前述の例を変更します。

<dataScope ... >
  <provider>
    <!-- all the data used by our table demo -->
    <data name="demoTableData">
      <inline>
        <!-- all the row DataObjects used by our table for tableData -->
        <demoRowData someText="First row"/>
        <demoRowData someText="Second row"/>
        <demoRowData someText="Third row"/>
        <demoRowData someText="Fourth row"/>
      </inline>
    </data>
  </provider>

  <contents>
    <table data:tableData="demoRowData@demoTableData">
      <contents>
        <!-- the first column stamp, a text node -->
        <text data:text="someText"/>
        <!-- the second column stamp, a button -->
        <button data:text="someText" destination="http://www.example.org"/>
      </contents>
    </table>
  </contents>
</dataScope>

これで、行ごとにコンテンツの異なる表ができました。変更箇所は次のとおりです。

  1. demoRowDataという4つのDataObjectそれぞれにデータを1つ追加しました。このデータは、DataObjectがキーsomeTextで問合せされた場合に返されるテキスト文字列です。結果は行ごとに変えました。
  2. 最初の列スタンプにあるテキストの値をバインドし、これがキーsomeTextで現行のDataObjectを問い合せた結果として返されるようにしました。属性値としてsomeText@aNamedDataObjectではなくsomeTextを使用したため、この問合せは、特定のDataObjectではなく、現行指定されたDataObjectに対して行われます。したがって、各行にスタンプするテキストは、表データから取得されます。
  3. 最後に、同じ手法を使用し、第2列のボタン・スタンプのテキストを変更します。ただし、ボタンのリンク先はすべての行で同じままにしてあるので注意してください。属性をすべてデータ・バインドする必要はありません。

明確にするため、テキスト・スタンプとボタン・スタンプで別のデータを使用するよう例を微調整します。

<dataScope ... >
  <provider>
    <!-- all the data used by our table demo -->
    <data name="demoTableData">
      <inline>
        <!-- all the row DataObjects used by our table for tableData -->
        <demoRowData firstColumnText="First row"  secondColumnText="Button #1"/>
        <demoRowData firstColumnText="Second row" secondColumnText="Button #2"/>
        <demoRowData firstColumnText="Third row"  secondColumnText="Button #3"/>
        <demoRowData firstColumnText="Fourth row" secondColumnText="Button #4"/>
      </inline>
    </data>
  </provider>

  <contents>
    <table data:tableData="demoRowData@demoTableData">
      <contents>
        <!-- the first column stamp, a text node -->
        <text data:text="firstColumnText"/>
        <!-- the second column stamp, a button -->
        <button data:text="secondColumnText" destination="http://www.example.org"/>
      </contents>
    </table>
  </contents>
</dataScope>

各行のDataObjectに、1つではなく、firstColumnTextおよびsecondColumnTextという2つのテキスト値を与えました。続いて、テキスト列のスタンプをfirstColumnTextにバインドし、ボタンをsecondColumnTextにバインドしました。その結果、2つの列が異なるテキストを持つようになります。

この例は単純ですが、考え方は非常に有用です。任意のUINodeを列スタンプとして使用することが可能で、そのスタンプの任意の属性を現行指定されているDataObjectにバインドできることに留意してください。これにより、表のセルのコンテンツを非常に柔軟に制御できます。

この時点で、いくつかの疑問が発生します。スタンプを使用し、データをわざわざ分離している理由は何でしょうか。開発者が、セルごとにコンテンツを、またはコンテンツとデータをまとめて指定できないのでしょうか。

表のモデル(またはデータ)をビュー(または外観)から分離することには相応の理由があります。分離することにより、開発者は、列スタンプを変更して表の外観を指定し、後で任意のデータソースをプラグインしてこれらのスタンプに具体的なデータを提供できます。各セルに入れるデータを厳密に指定すると、同じセットのUIX Componentsのノードをすべてのページ・ビューに使用することはできません。レンダリングごとに表の行数を変更したり、各セルのデータを変更することもできません。表の構造を一度指定し、その構造をすべてのページ・レンダリングに再利用することは、UIX Componentsフレームワーク全体のコア機能の1つです。

TableData

tableData属性はDataObjectListタイプであるため、いくつかの方法で設定できます。変更されない静的データは、次の例で示すようにuiXMLコードにインラインで定義できます。

<table ...>
 <tableData>
  <demoRowData firstColumnText="First row"  secondColumnText="Button #1"/>
  <demoRowData firstColumnText="Second row" secondColumnText="Button #2"/>
  <demoRowData firstColumnText="Third row"  secondColumnText="Button #3"/>
  <demoRowData firstColumnText="Fourth row" secondColumnText="Button #4"/>
 </tableData>

 ...
</table>

uiXMLパーサーは、tableDataDataObjectListタイプであることを認識し、tableData要素の子をDataObjectListに解析します。行キー(この場合はdemoRowData)は任意に設定できますが、4つの行すべてが同じ行キーを持ち、同じDataObjectListにグループ化される必要があります。ちなみに、ここにはデータ・バインドはありません。tableData属性は、新規に作成されるDataObjectListに設定されます。

このアプローチには、表データを複数の表で共有できないという問題があります。共有は、データ・バインドおよび(前述の)次のような構成を使用して行うことができます。

<dataScope ... >
  <provider>
    <data name="demoTableData">
      <inline>
        <demoRowData firstColumnText="First row"  secondColumnText="Button #1"/>
        <demoRowData firstColumnText="Second row" secondColumnText="Button #2"/>
        <demoRowData firstColumnText="Third row"  secondColumnText="Button #3"/>
        <demoRowData firstColumnText="Fourth row" secondColumnText="Button #4"/>
      </inline>
    </data>
  </provider>

  <contents>
    <table name="table1" data:tableData="demoRowData@demoTableData">
     ...
    </table>

    <table name="table2" data:tableData="demoRowData@demoTableData">
     ...
    </table>
  </contents>
</dataScope>

この例ではinlineデータ・プロバイダが、DataObjectListにバインドされる、単一のキーであるdemoRowDataのあるDataObjectを作成します。外部のDataObjectは不要です。次のセクションでは、Javaで表データを作成し、外部のDataObjectは作成しません。

Javaでの表データの作成

データ・バインドにより、JavaでDataObjectListを作成し、それらを表にデータとして提供できます。次に、簡単な例を示します。

<dataScope ... >
  <provider>
    <data name="demoTableData"> 
      <method class="test.MyTable" method="getTableData" /> 
    </data>
  </provider>

  <contents>
    <table data:tableData="@demoTableData">
     ...
    </table>
  </contents>
</dataScope>

前述したtableDataバインドでの変更点に注意してください。DataObjectの内部でDataObjectListをラップしないためです。表データを作成するJavaコードを次に示します。(データ・バインドのuiXMLおよびJavaは、どちらも「データ・バインド」のトピックを参照してください)。

package test;

public class MyTable
{
  public static DataObject getTableData(RenderingContext context,
                                        String namespace,
                                        String name)
  {
    DataObject[] data = new DataObject[4];
    data[0] = new MyDataObject("First Row",  "Button #1");
    data[1] = new MyDataObject("Second Row", "Button #2");
    data[2] = new MyDataObject("Third Row",  "Button #3");
    data[3] = new MyDataObject("Fourth Row", "Button #4");

    // convert the array into a DataObjectList
    return new ArrayDataSet(data);
  }

  private static final class MyDataObject implements DataObject
  {
    public MyDataObject(String column1, String column2)
    {
      _col1 = column1;
      _col2 = column2;
    }

    public Object selectValue(RenderingContext context, Object key)
    {
      if ("firstColumnText".equals(key))
        return _col1;
      else if ("secondColumnText".equals(key))
        return _col2;
      return null;
    }

    private final String _col1, _col2;
  }
}

この例では、2つのキーを認識するDataObjectの独自の実装を作成します。次にデータのインスタンスの配列を作成し、oracle.cabo.ui.data.ArrayDataSetクラスを使用してそこからDataObjectListを作成します。ArrayDataSetDataObjectおよびDataObjectListの両方を実装するため、DataProvider内で表データを返すために使用できることに注意してください。

これまでは静的データを作成してきました。ここでは、動的データを作成するJavaコードを実行します。次の例では、ディレクトリ・リストのある表データを作成します。

package test;

public class MyTable
{
  public static DataObject getDirectoryData(RenderingContext context,
                                            String namespace,
                                            String name)
  {
    // Make sure this directory exists on your file system
    return new DirDataObjectList(new File("/home/user/"));
  }

  private static final class DirDataObjectList
    implements DataObjectList, DataObject
  {
    public DirDataObjectList(File dir)
    {
      _files = dir.listFiles();
    }

    public int getLength()
    {
      return _files.length;
    }

    public DataObject getItem(int index)
    {
      // in a more prudent implementation, we would be caching these
      // DataObjects, rather than creating new ones each time.
      return new FileDataObject(_files[index]);
    }

    public Object selectValue(RenderingContext context, Object key)
    {
      // we don't support any properties on this DataObject, since this is
      // primarily a list of DataObjects.
      return null;
    }

    private final File[] _files;
  }

  private static final class FileDataObject implements DataObject
  {
    public FileDataObject(File file)
    {
      _file = file;
    }

    /**
     * This DataObject recognizes two keys: name which gives the
     * file name, and length which gives the file length.
     */ 
   public Object selectValue(RenderingContext context, Object key)
    {
      if ("name".equals(key))
        return _file.getName();
      else if ("length".equals(key))
        return new Long(_file.length());
      return null;
    }

    private final File _file;
  }
}

対応するuiXMLコードは次のようになります。


<dataScope ... >
  <provider>
    <data name="demoTableData">
      <method class="test.MyTable" method="getDirectoryData" />
    </data>
  </provider>

  <contents>
    <table data:tableData="@demoTableData">
      <contents>
        <text data:text="name"/>
        <text data:text="length"/>
      </contents>
      ...
    </table>
  </contents>
</dataScope>

Javaでの表データの作成に役立つもう1つのクラスは、Java Vector(およびJDK1.2以上のList)をDataObjectListに変換するoracle.cabo.ui.data.ListDataObjectListです。サーバー側での表データの処理に役立つクラスは他にもあります。これらのクラスについては、後のセクションで説明します。

表のヘッダーの作成

これまでは、表のデータ部分のみを扱い、データを取り囲む表の周辺に関する項目は扱いませんでした。ここではまず、各列にラベルを提供する列ヘッダーを調べます。次に、各行にラベルを提供する行ヘッダーを調べます。列ヘッダーを作成する方法は2つあります。ここでは最初の方法を説明し、2番目の方法は列のカプセル化に関するセクションで説明します。

列ヘッダー

列自体と同様に列ヘッダーもスタンプされますが、この場合は、列のセルを垂直方向にではなく、表の上部に水平方向にスタンプされます。スタンプ・ノードを列ヘッダーとして機能するよう登録するには、これをcolumnHeaderStampという名前の付けられた子として設定する必要があります。次の例でこれを示します。

<dataScope ... >
  <provider>
   ...
  </provider>

  <contents>
    <form name="testForm">
      <contents>
        <table ... >
          <contents>
           ...
          </contents>

          <!-- add a column header stamp node -->
          <columnHeaderStamp>
            <text text="Column Header"/>
          </columnHeaderStamp>
        </table>
      </contents>
    </form>
  </contents>
</dataScope>

データ列の上に、他のセルと異なる外観の特別なセクションが作成されたことに注意してください。列ヘッダー・スタンプは、データ列ごとに1回ずつ、表の上部に水平方向にスタンプされます。もちろん、すべてのセルに同じテキストを表示するのではなく、列ヘッダー・スタンプには列の内容に適したラベルを含めます。このため、表で新規の属性を使用して、columnHeaderDataという列ヘッダーをデータ・バインドする必要があります。

<dataScope ... >
  <provider>
    <!-- all the data used by our table demo -->
    <data name="demoTableData">
      <inline>
        <!-- all the row DataObjects used by our table for tableData -->
        <demoRowData ... />
        ...

        <!-- DataObjectList to provide information to the column header stamps -->
        <demoColumnHeaderData headerText="First Header"/>
        <demoColumnHeaderData headerText="Second Header"/>
      </inline>
    </data>
  </provider>

  <contents>
    <form name="testForm">
      <contents>
        <table data:columnHeaderData="demoColumnHeaderData@demoTableData"
               ... >
          <contents>
           ...
          </contents>

          <!-- add a column header stamp node -->
          <columnHeaderStamp>
            <text data:text="headerText"/>
          </columnHeaderStamp>
        </table>
      </contents>
    </form>
  </contents>
</dataScope>

これで、各ヘッダーに独自のテキストが生成されます。ここでは次の3つの作業を行いました。

  1. 新規のDataObjectListを作成しました。このDataObjectListのサイズは列数と同じで、各DataObjectには、headerTextキーで格納された、列ヘッダーに表示するテキストが含まれています。
  2. 表のcolumnHeaderDataがデータ・バインド属性から取得されるよう登録しました。この属性の値は、demoTableDataという名前のDataObjectの下にあるdemoColumnHeaderDataというDataObjectListです。これにより、表で新規のDataObjectListを使用して、ヘッダー・スタンプにデータを提供できます。
  3. 最後に、テキストの列ヘッダー・スタンプの値を、現行のDataObjectのheaderTextキーから取得されるようバインドしました。列ヘッダー・スタンプがレンダリングされる際、列ヘッダー・データ内の個々のDataObjectが、各ヘッダーの上にスタンプがレンダリングされるときの現行のDataObjectになります。これにより、テキストが列ヘッダーごとに変更されます。

これは、多くの表に当てはまります。ただし、一部の表では、ユーザーがなんらかの基準によりデータ項目をソートできるようにし、行が現在ソートされていることを示す必要もあります。このためUIX Componentsでは、このニーズを満たすSortableHeaderBeanという特定の列ヘッダー・スタンプを提供しています。このBeanについては、後のセクションで説明します。

行ヘッダー

一部の表では、個々のデータ行に付属のラベルでラベルを付けることが必要な場合があります。これを行うために、表のBeanではrowHeaderStampを提供しています。このスタンプは、列ヘッダー・スタンプに似ています。行ヘッダー・スタンプの使用方法を示す次の例について説明します。

<dataScope ... >
  <provider>
    <!-- all the data used by our table demo -->
    <data name="demoTableData">
      <inline>
        ...

        <!-- DataObjectList to provide information to the row header stamps -->
        <demoRowHeaderData headerText="1"/>
        <demoRowHeaderData headerText="2"/>
        <demoRowHeaderData headerText="3"/>
      </inline>
    </data>
  </provider>

  <contents>
    <form ... >
      <contents>
        <table ... 
               data:rowHeaderData="demoRowHeaderData@demoTableData" >
          <contents>
           ...
          </contents>

          <!-- row header stamp node -->
          <rowHeaderStamp>
            <text data:text="headerText"/>
          </rowHeaderStamp>
        </table>
      </contents>
    </form>
  </contents>
</dataScope>

行ヘッダーを追加するメカニズムは同じで、子および属性の名前のみが若干異なります。rowHeaderStampという名前の付けられた子およびrowHeaderDataというtableの属性を使用します。ただし、この例では、行ヘッダー・データの4つのDataObjectを使用していないので注意してください。これは、データがなくてもヘッダー・スタンプが行をレンダリングしますが、望ましい結果を得られない場合があることを示すためです。

表の編集可能なセル

ユーザーが編集可能なコントロールをレンダリングするスタンプを、意図的に表に含める場合があります。ユーザーが表のセルにデータを入力し、そのデータをサーバーに送信して処理するというものです。

たとえば、表の列のすべてのセルに編集可能なフォームの入力要素をレンダリングし、ユーザーがそれらのセルのデータを入力または変更できるようにする場合を考えます。通常この処理は、標準のフォーム送信を介して表の外部のページで実行されます。任意のフォームの入力項目には、name属性が関連付けられています。この属性は、フォームが送られる際に入力要素の値とともにサーバーに送信されます。この方法で、サーバーにページの値が通知されます。

前述のように、項目は入力要素であっても表の列スタンプとして使用できます。しかし、項目が列のセルを垂直方向に複数回スタンプされるとしたら、各セルはどのようにして一意の値としてサーバーに送信されるのでしょうか。各入力要素が同じname属性で複数回レンダリングされ、目的のHTMLが得られないのではないでしょうか。

その答えは、表が名前の変換と呼ばれるプロセスを介してこれを修正することにあります。基本的に、表はスタンプ上のname属性を特別なものとして扱い、スタンプをセルにレンダリングする前にこの属性を変更します。たとえば、列スタンプがfooという名前の入力コントロールだった場合、表は各セルにレンダリングされる実際のname値を次の形式に変更します。

tableName:foo:rowIndex

tableNameはtableのname属性の値で置換され、rowIndexはスタンプがレンダリングされる行を示す整数で置換されます。つまり、myTestTableという名前の表の第3行にレンダリングされるfooという名前の入力要素には、次の名前が与えられます。

myTestTable:foo:3

次の例では、表を変更して、最初の列にテキスト・ノードのかわりにtextInput要素を配置しました。このページのHTMLソースを表示すると、表の各行の生成済テキスト・フィールドの名前が前述のように変換されているのがわかります。

<dataScope ... >
  <provider>
   ...
  </provider>

  <contents>
    <form name="testForm">
      <contents>
        <table name="myTestTable"
               ... >
          <contents>
            <textInput data:text="firstColumnText" name="foo"/>
            <button data:text="secondColumnText" destination="http://www.example.org"/>
          </contents>
        </table>
      </contents>
    </form>
  </contents>
</dataScope>

ナビゲーション・バー・リンクまたはその他のフォーム起動アクションの結果としてこの表を含むフォームが送信されると、サーバーではtextInput列の値を表す4つの名前と値のペアを受信します。

フォーム・コントロール名 初期値
myTestTable:foo:0 First row
myTestTable:foo:1 Second row
myTestTable:foo:2 Third row
myTestTable:foo:3 Fourth row

UIXには、これらの値が送信された後にサーバーで値を取得するためのユーティリティ・クラスも用意されています。クラスoracle.cabo.data.ServletRequestDataSetは、ServletRequestから値を取得するために使用します。クラスoracle.cabo.servlet.ui.data.PageEventFlattenedDataSetは、UIX ControllerのPageEventで使用します。これらのクラスは、表の入力要素すべてによるDataObjectListを実装します。このリストの長さは、表の行数と同じです。このリストの各DataObjectは、表の行に対応します。入力要素のnameを(各DataObjectで)キーとして使用し、(各行の)その要素の値を取得できます。次の例では、表の入力要素すべての値を取得して連結するイベント・ハンドラを実装しています。

public static EventResult doSubmitEvent(BajaContext bc, Page page,
                                        PageEvent event)
{
  // create a new FlattenedDataSet for the table "myTestTable"
  DataSet tableInputs = new PageEventFlattenedDataSet(event,
                                                      "myTestTable");
  StringBuffer s = new StringBuffer(40);
  // this would be the number of rows in the table
  int sz = tableInputs.getLength();
  for(int i=0; i<sz ;i++)
  {
    // get the DataObject representing all the input elements on the current
    // table row.
    DataObject row = tableInputs.getItem(i);
    // get the value of the input element named "foo". we can safely use
    // null for the RenderingContext here:
    Object value = row.selectValue(null, "foo");
    s.append(value);
  }

  EventResult result = new EventResult(page);
  result.setProperty("case", "submit");

  // store the concatenation of the values of all the "foo" elements on the
  // EventResult
  result.setProperty("result", s);
  return result;
}

この自動的な名前変換が不要な場合があります。たとえば、列の垂直方向にラジオ・ボタン・グループをレンダリングする場合は、(各ラジオ要素が同じグループに属し、相互に排他的となるよう)各ラジオ要素を同じ名前にする必要があります。現在のところ、これを実行する唯一の方法は、自動的な名前変換をオフにし、各name属性をデータ・バインドすることにより名前変換をプライベートに処理することのみです。自動的な名前変換は、tablenameTransformed属性をfalse(またはJavaではBoolean.FALSE)に設定してオフにします。

<table ...
       nameTransformed="false">
 ...
</table>

上記の場合でも、受信するイベントでPageEventFlattenedDataSet(またはServletRequestDataSet)を使用してサーバー上でデータを取得したい場合は、送信側のページでoracle.cabo.ui.data.FlattenedDataSetを使用して表のすべての入力要素を(自動変換と同様に)変換します。表myTestTableの5行目の入力要素fooを変換するには、次のように指定します。

String newName = FlattenedDataSet.getFlattenedName("myTestTable", 5, "foo");

radioという名前の例にあげたようなラジオ・グループ列を変換するには、次のように指定します。

String newName = FlattenedDataSet.getFlattenedName("myTestTable", "radio");

最後のステップは、すべてを正しく動作させるために必要です。tableproxied属性を、trueに設定する必要があります(この属性については後のセクションで説明します)。これで、通常の方法でPageEventFlattenedDataSetを使用して、表の入力要素の値を取得できるようになります。例にあげたようなラジオ・グループ列の値を取得するには、次のように指定します。

  DataSet tableInputs = new PageEventFlattenedDataSet(event,
                                                      "myTestTable");
  Object radioValue = tableInputs.selectValue(null, "radio");

レコード・ナビゲーション

いくつかの行がある表の作成方法を見てきましたが、アプリケーションのデータ・セットに多くの項目が含まれることがよくあります。大企業の従業員をリスト表示する表などでは、すべてのレコードを同時に表示できないことは明白です。このため、レコード・ナビゲーションを使用して、このような表を管理しやすいように分割する必要があります。

わずかなデータを追加することにより、表のBeanで、現行のデータ行がより大きなデータの一部であることを示すナビゲーション領域をレンダリングできます。表の各行には索引番号が割り当てられ、どの行番号が現在表示され、どの行番号が表示されていないかがユーザーに通知されます。このナビゲーション領域をレンダリングするには、表に追加属性をいくつか指定する必要があります。

不明な場合は、4つの属性すべてを表に指定する必要はありません。4つのうち1つのみを指定しても、表にナビゲーション領域がレンダリングされ、明示的な値がなくてもこれらの属性がデフォルトになります。例として、現在画面に表示されているデータよりも多くのデータがあることを示すナビゲーション・プロパティを追加したデモの表を示します。

<dataScope ... >
  <provider>
   ...
  </provider>

  <contents>
    <table value="5"
           maxValue="50"
           blockSize="4"  
           ...  >
      <contents>
       ...
      </contents>
    </table>
  </contents>
</dataScope>

この例では、行を(合計50行のうちの)5行目から表示し、一度に最大4行を表示するよう表を設定しています。最小値は指定していませんが、1と想定されます。

ナビゲーション・バーは純粋に表面的なものであることに注意してください。実際にレンダリングされる行数や最初にレンダリングされる行の制御は行いません。value属性およびblockSize属性の値にかかわらず、表は常にtableData属性のDataObjectと同じ数の行をレンダリングします。

サーバーでのナビゲーション

ナビゲーション・リンクを使用すると、表はUIX ControllerのUIConstants.GOTO_EVENTというイベント(uiXMLではgoto)を生成します(イベントの詳細は、「UIX Controllerの概要」のトピックを参照してください)。このイベントには、次の表で説明する3つのパラメータがあります。

イベント・パラメータ UIConstant 説明
source SOURCE_PARAM イベントを生成した表を示します。値は、tableのname属性です。
value VALUE_PARAM ユーザーが現在表示している行セットを示します。セットの先頭行の索引に設定されます。
size SIZE_PARAM 現在表示されている行セットのサイズです。行の最後のセットが表示される場合を除き、これは通常、表のblockSizeです。

サーバーでは、要求された表の行のみを含む新規のDataObjectListを作成する必要があります。つまり、valueパラメータで指定した行から開始し、sizeパラメータと同じ行数のみが含まれます。これはoracle.cabo.ui.data.PagedDataObjectListクラスを利用して行います。

public class TableDemo  {
  public static EventResult doGotoEvent(BajaContext bc, Page page,
                                        PageEvent event)
  {
    // if this is a "goto" event, then we need to get the "value" parameter to
    // figure out what our start index is. If this is not a "goto" event, then
    // we want to start at index "1"
    String valueParam = ((event!=null) &&
                         UIConstants.GOTO_EVENT.equals(event.getName()))
      ? event.getParameter(UIConstants.VALUE_PARAM)
      : "1";

    // the "value" parameter starts at "1"; however, our data is zero based,
    // so adjust the offset
    int value = Integer.parseInt(valueParam)-1;
    DataObjectList tableData = new PagedDataObjectList(_TABLE_DATA,
                                                       _BLOCK_SIZE.intValue(),
                                                       value); //start index

    // in a more efficient implementation, we would not use DictionaryData;
    // instead, we would implement our own DataObject
    DictionaryData data = new DictionaryData();
    // we need to add one here, since our data is zero based, but the table
    // start index must start at 1
    data.put("value", new Integer(value+1));
    data.put("size", _BLOCK_SIZE);
    data.put("maxValue", new Integer(_TABLE_DATA.getLength()));
    data.put("current", tableData);

    EventResult result = new EventResult(page);
    result.setProperty("tableData", data);
    return result;
  }

  // we want to render at most 30 rows on a single page
  private static final Integer _BLOCK_SIZE = new Integer(30);
  private static final DataObjectList _TABLE_DATA = _sCreateTableData();

  /**
   * this method creates all the static table data.
   */
  private static DataObjectList _sCreateTableData()
  {
    int sz = 94;
    Object[] data = new Object[sz];
    for(int i=1; i<=sz; i++)
    {
      data[i-1] = "Test Data "+i;
    }
    return new ArrayDataSet(data, "firstColumnText");
  }
}

この例では、まずvalueパラメータの取得から始めています。これは、表に現在表示されている先頭行の索引です。DataObjectListの索引はゼロから始まりますが、valueパラメータは1から始まるため、オフセット調整が必要です。

PagedDataObjectListは、必要なブロック・サイズ(30)および現在の先頭の索引で作成されます。最後に、このDataObjectListDataObjectに配置します。DataObjectは、tableのvalue属性、blockSize属性、maxValue属性およびtableData属性にそれぞれバインドする必要のあるキー、value、size、maxValueおよびcurrentを実装します。これは、次のuiXMLで行います。

<table data:tableData="current@tableData@ctrl:eventResult"
       data:value="value@tableData@ctrl:eventResult"
       minValue="1"
       data:maxValue="maxValue@tableData@ctrl:eventResult"
       data:blockSize="size@tableData@ctrl:eventResult"
       name="table1"
       ... >
 ...
</table>

この例では、ある程度間接的なデータ・バインドを使用しています。データ・バインドの詳細は、「データ・バインド」を参照してください。

ナビゲーションURL

前述した例では、すべてのナビゲーション・リンクが現在のページをポイントしています。一部のアプリケーションでは、ナビゲーション・リクエストを他のURLに向ける必要があります。これには、tableにdestination属性を設定します。destinationには、イベント・パラメータを受け入れるサーバーのURLを整形式で指定する必要があります。宛先が指定されている場合、表はレコード・ナビゲーション・リンクをページにレンダリングし、必要なレコード・ナビゲーション・イベントとともにURLパラメータを指定の宛先に送信します。次に例を示します。

<table destination="http://www.example.org/eventHandler"
       ...  >
   ...
</table>

表のイベントをサーバーに送信する別の方法として、表が通信にHTMLフォームを使用するよう指定する方法があります。フォームを使用して得られる結果は、宛先URLを使用する場合と実際は同じで、サーバーはユーザーのアクションを示すキーと値のペアを受信しますが、フォームの使用には他にも利点があります。フォームを使用してレコード・ナビゲーションなどの表でのアクションを送る場合、フォームの他のすべての値が、表のナビゲーション・パラメータとともにサーバーに送信されます。

例として、表のナビゲーション制御およびtextInputの両方が同じページの、同じHTMLフォーム内にある場合を想定します。ユーザーが表のナビゲーション・バー・リンクをクリックして新規の行セットに移動する場合、新規のページがユーザーに対して生成される際にそのコンテンツが保持および再表示されるよう、textInputの値のサーバーへの送信が必要になる場合があります。これを行うには、tableのformSubmitted属性をtrueに設定します。このようにすると、ユーザーがナビゲーション領域のリンクをクリックすると、表はJavaScriptを介して表が属するフォームをフォーム内の他のすべての値とともに送ります。

しかし、表はどのようにして、ユーザーがどのレコード・ナビゲーション・イベントを選択したかを示す4つの必要なパラメータをサーバーに送信するのでしょうか。これは、4つの特殊な非表示フィールドを、表自身も存在するHTMLフォーム内にレンダリングすることで行います。フォームが表によって送信される前に、JavaScriptを使用してユーザーのアクションを示すようこれらの非表示フィールドの値が変更されます。フォーム送信の使用例を次に示します。

<dataScope ... >
  <provider>
   ...
  </provider>

  <contents>
    <form name="testForm">
      <contents>
        <table formSubmitted="true"
               ... >
          <contents>
           ...
          </contents>
        </table>
      </contents>
    </form>
  </contents>
</dataScope>

フォーム送信は、フォームの入力要素が表の列スタンプとして使用される場合にも必要です。

空の表

レコード・ナビゲーションには他にも特殊なケースがあり、なんらかの理由で表に現在表示する行が存在しないケースなどがあります。たとえば、用語の検索結果を表示する表で、検索条件に一致するものが含まれていないためにデータ行がないというケースがそうです。このような表では、データが表示されない理由を示すメッセージを表示するとユーザーにわかりやすくなります。このテキスト・メッセージは、次に示すようにalternateText属性で設定します。

<dataScope ... >
  <provider>
    <!-- all the data used by our table demo -->
    <data name="demoTableData">
      <inline>
        <!-- no rows in this example! -->

        ...
      </inline>
    </data>
  </provider>

  <contents>
    <table data:tableData="demoRowData@demoTableData"
           ...
           alternateText="(No search results were found)">

      ...
    </table>
  </contents>
</dataScope>

テキスト・メッセージは、tableにtableDataが設定されていない場合、またはtableDataで返される行数が0の場合にのみ表示されます。

ソート可能な列ヘッダー

列ヘッダーは、columnHeaderStampとしてSortableHeaderBeanを使用することによりソート可能になります。ただし、列がソート可能であるかは、追加情報を提供するまで明らかにはなりません。

ソート可能であることを示す方法でSortableHeaderBeanがレンダリングするには、sortable属性に値を指定する必要があります。この属性に指定できる値は3つあります。

uiXML UIConstant 説明
yes SORTABLE_YES 列はソート可能ですが、現在はソートされていないことを示します。列ヘッダーは、その列の値に基づいてデータ項目をソートする場合に、ユーザーがその列ヘッダーを選択する必要があることを示すような方法でレンダリングします。
ascending SORTABLE_ASCENDING 列ヘッダーがソート可能としてレンダリングされる必要があり、表がすでにこの列でソートされているという通知をユーザーに表示します。ソートされた列の値が、列を下に読み進むにつれて増えていくことも示します。
descending SORTABLE_DESCENDING 列ヘッダーがソート可能としてレンダリングする必要があり、表がすでにこの列でソートされていることをユーザーに通知します。ソートされた列の値が、列を下に読み進むにつれて減っていくことも示します。

列の1つを降(昇)順にソート可能にすることで、この機能をデモンストレーションしてみます。新規のSortableHeaderBeansortable属性をこれらの値にデータ・バインドすると、列ヘッダー・スタンプの外観がどのように変化するかに注意してください。

<dataScope ... >
  <provider>
    <data name="demoTableData">
      <inline>
        <!-- all the row DataObjects used by our table for tableData -->
        <demoRowData ... />
        ...

        <!-- DataObjectList to provide information to the column header stamps -->
        <demoColumnHeaderData ... sortValue="yes"/>
        <demoColumnHeaderData ... sortValue="ascending"/>
      </inline>
    </data>
  </provider>

  <contents>
    <form name="testForm">
      <contents>
        <table ... >
          <contents>
           ...
          </contents>

          <!-- add a sortable column header stamp node -->
          <columnHeaderStamp>
            <sortableHeader data:text="headerText"
                            data:sortable="sortValue"/>
          </columnHeaderStamp>
        </table>
      </contents>
    </form>
  </contents>
</dataScope>

サーバー上でのソート

ソート可能なヘッダーをクリックすると、表はUIX ControllerのUIConstants.SORT_EVENTというイベント(uiXMLではsort)を生成します。このイベントには、次の表で説明する3つのパラメータがあります。

イベント・パラメータ UIConstant 説明
source SOURCE_PARAM このイベントを生成する表を示します。これは、それぞれの表のnameです。
value VALUE_PARAM 列ヘッダー・セルのSortableHeaderBeanvalue属性として提供される任意のものを示します。デフォルト値は、列のゼロから始まる索引です。
state STATE_PARAM 列ヘッダー(valueパラメータで指定)が、すでにソートされているかどうかを示します。すでにソートされている場合、このパラメータは、列のソート方法に応じてUIConstant SORTABLE_ASCENDINGまたはUIConstant SORTABLE_DESCENDINGになります。列がまだソートされていない場合、state値としては何も送信されません。

次のセクションでは、サーバー上でソートを処理する方法の例を示します。この例は、一般的な環境で動作するよう設計されているため、本来必要である以上に複雑になっています。

最初の手順は、SortableHeaderBeanvalue属性の指定です。各列ヘッダーのvalue属性として、列データに使用するものと同じキーを使用します。サーバーでは、valueパラメータを取得し、それを表の行のDataObjectでキーとして使用することにより、ソートが必要な列データを取得できるため、列データと同じキーを使用することには意味があります。次のuiXMLは、この部分を示しています(name、ageおよびbloodという3つのソート可能な列があります)。

<table ... >
  <columnHeaderData>
   <col text="Name" sort="yes" value="name"/>
   <col text="Age" sort="yes" value="age"/>
   <col text="Blood Group" sort="yes" value="blood"/>
   <col text="Phone"/>
  </columnHeaderData>
  <columnHeaderStamp>
   <sortableHeader data:text="text"
     data:value="value" ... >
   </sortableHeader>
  </columnHeaderStamp>
  <contents>
    <text data:text="name"/>
    <text data:text="age"/>
    <text data:text="blood"/>
    <text data:text="phone"/>
  </contents>
</table>

次の手順は、サーバー上でのsortイベントの処理です。次のコードでは、valueパラメータおよびstateパラメータを取得し、EventResultに格納します。

public static EventResult doSortEvent(BajaContext bc, Page page,
                                      PageEvent event)
{
  EventResult result = new EventResult(page);
  result.setProperty(UIConstants.VALUE_PARAM,
                     event.getParameter(UIConstants.VALUE_PARAM));

  // if we are already sorting in ascending order, then we want to sort in
  // descending order. Otherwise, sort in ascending order
  Object state = event.getParameter(UIConstants.STATE_PARAM);
  result.setProperty(UIConstants.STATE_PARAM,
                     UIConstants.SORTABLE_ASCENDING.equals(state)
                     ? UIConstants.SORTABLE_DESCENDING
                     : UIConstants.SORTABLE_ASCENDING);
  return result;
}

次に、各列ヘッダーのsortable属性を決定するDataObjectを作成する必要があります。次のDataObjectでは、現在の列ヘッダーのvalue属性をkeyとしてコールします。続いて、keyをEventResult上のvalue(前述のイベント・ハンドラで設定)と比較することによって、これが、ソートされた列のヘッダーであるかどうかを確認します。適切な列ヘッダーである場合は、state(昇順または降順)を返します。適切な列ヘッダーでない場合は、nullを返します。

private static final DataObject _SORT_COLUMN_HEADER = new DataObject() {
    public Object selectValue(RenderingContext rc, Object key)
    {
      BajaContext bc = BajaRenderingContext.getBajaContext(rc);
      EventResult er = EventResult.getEventResult(bc);
      if (er!=null)
      {
        // check to see if it is this column that has been sorted. We assume
        // that "key" is the "value" attribute of this column header.
        if (key.equals(er.getProperty(UIConstants.VALUE_PARAM)))
          return er.getProperty(UIConstants.STATE_PARAM);
      }
      return null;
    }
  };

次の手順では、このDataObjectを使用するために、SortableHeaderBeansortable属性をデータ・バインドします。このデータ・バインドは、通常と異なる方法で行っています。最初に、このDataObjectを使用して値の取得を試行しますが、それに失敗した場合には、columnHeaderData内の、この列に対する適切なDataObjectから値を取得します。これは、次のuiXMLコードにより処理されます。

<table ... >
  <columnHeaderData>
   <col text="Name" sort="yes" value="name"/>
   <col text="Age" sort="yes" value="age"/>
   <col text="Blood Group" sort="yes" value="blood"/>
   <col text="Phone"/>
  </columnHeaderData>
  <columnHeaderStamp>
   <sortableHeader data:text="text"
     data:value="value">
    <boundAttribute name="sortable">
     <defaulting>
      <!-- first try to get a value from the sortColumnHeader
         dataObject. This dataObject is passed whatever the
         value key is bound to (in the current columnHeaderData) -->
      <dataObject data:select="value"
                  source="sortColumnHeader"/>
      <dataObject data:source="(value)@sortColumnHeader"/>
      <!-- if the above boundValue returns null, then try to get
           a value from the columnHeaderData  -->
      <dataObject select="sort"/>
     </defaulting>
    </boundAttribute>
   </sortableHeader>
  </columnHeaderStamp>

  ...
</table>

ここでのboundAttribute要素は、sortableHeadersortable属性をデータ・バインドするもう1つの方法を示しています。defaultingのboundValueでは、最初の子から値の取得を試行しますが、失敗した場合には2番目の子の値を返します。上の例では、最初の子がsortColumnHeaderデータ・プロバイダから値の取得を試行し(このデータ・プロバイダは、前述の例で作成した_SORT_COLUMN_HEADERにバインドされていると想定)、2番目の子がsortキーを使用して、columnHeaderData内のDataObjectからデフォルトのソート可能状態を取得します。

ここまでの処理を整理します。ソート可能なヘッダーは、クリックに対応しているだけでなく、クリックされるたびにソート表示の昇順と降順を切り換えます。ただし、列はまだソートされません。次のセクションでは、実際のソート処理を実装します。

public static DataObject getSortedTableData(RenderingContext rc,
                                            String namespace, String name)
{
  DataObject[] data;
  BajaContext bc = BajaRenderingContext.getBajaContext(rc);
  EventResult er = EventResult.getEventResult(bc);
  if (er!=null)
  {
    // we need to clone because we are going to mutate the array:
    data = (DataObject[]) _TABLE_DATA.clone();
    Object state = er.getProperty(UIConstants.STATE_PARAM);
    Object key = er.getProperty(UIConstants.VALUE_PARAM);
    Comparator comp = new DataObjectComparator(
         rc,
         key, // this is the key to sort the DataObjects on
         UIConstants.SORTABLE_ASCENDING.equals(state));
    Arrays.sort(data, comp);
  }
  else
    data = _TABLE_DATA;

  return new ArrayDataSet(data);
}

このデータ・プロバイダは、イベント・ハンドラdoSortEvent(...)によりEventResultに設定されたstateおよびvalueに従ってソートされた、DataObjectListを返します。_TABLE_DATAは、ソートされていないデータです。実際のソートは、java.util.Arraysクラスにより処理されます。DataObjectComparatorクラスにも注意してください。

private static final class DataObjectComparator implements Comparator
{
  public DataObjectComparator(RenderingContext context,
                              Object key, boolean ascending)
  {
    _context = context;
    _key = key;
    _ascending = ascending;
  }

  public int compare(Object o1, Object o2)
  {
    DataObject dob1 = (DataObject) o1;
    DataObject dob2 = (DataObject) o2;

    Comparable val1 = (Comparable) dob1.selectValue(_context, _key);
    Object val2 = dob2.selectValue(_context, _key);
    int comp = val1.compareTo(val2);

    // if we are not sorting in ascending order, then negate the comparison:
    return _ascending ? comp : -comp;
  }

  private final RenderingContext _context;
  private final Object _key;
  private final boolean _ascending;
}

このクラスでは、2つのDataObjectの比較を処理し、_keyを使用して両方の値を取得します。比較は、値がComparableであると想定して行っています(降順でソートしている場合は結果を反転します)。

選択

特定の行を処理するために一括選択する機能は、多くのアプリケーションで役に立ちます。Tableでは、tableSelectionという子を介して選択をサポートします。UIXには、SingleSelectionBeanおよびMultipleSelectionBeanの2つの選択Beanが用意されています。SingleSelectionBeanは、1行選択のみ可能な表で使用し、MultipleSelectionBeanは複数行選択が可能な場合に使用します。説明に進む前に、これらのBeanが正しく動作するためには、フォーム内部でTableを使用する必要があることに注意してください。これらのBeanが、フォーム要素を使用してサーバーに選択項目を指示するためです。

単一選択

表での単一選択は、tableSelectionの子としてSingleSelectionBeanを使用することにより処理されます。次に、単純な例を示します。

<table name="table1" ... >
  <tableSelection>
   <singleSelection/>
  </tableSelection>

  ...
</table>

最初は、どの行も選択済としてマークされていないことに注意してください。初期選択項目は、選択が必要な行の(ゼロから始まる)索引を、singleSelectionselectedIndex属性で指定することにより設定できます。

<singleSelection selectedIndex="1" />

次の手順は、選択項目に対してどんな処理が可能であるかをユーザーに知らせることです。これには、singleSelectiontext属性を使用します。ユーザーが処理を開始する際にボタンをクリックできるように、submitButtonをレイアウトに追加する必要もあります。次の例で示すように、ボタンは索引付けされた子として選択Beanに追加できます。

<tableSelection>
 <singleSelection text="Select record and ..."
                  selectedIndex="1" >
  <contents>
   <submitButton text="Copy" ctrl:event="copy" />
   <submitButton text="Delete" ctrl:event="delete" />
  </contents>
 </singleSelection>
</tableSelection>

サーバー上での選択の処理方法を説明します。上の例のsubmitButtonは、どちらもサーバーでのイベントを起動しています。次に、イベント・ハンドラを示します(ここで注意するポイントは、選択した索引の取得にoracle.cabo.ui.beans.table.SelectionUtilsを使用していることです)。

public static EventResult doSelectionEvent(BajaContext bc, Page page,
                                           PageEvent event)
{
  DataObject tableRows = new PageEventFlattenedDataSet(event, "table1");
  int index = SelectionUtils.getSelectedIndex(tableRows);
  String name = "Nothing Selected";
  // make sure that something was selected:
  if (index>=0)
  {
    DataObject row = _TABLE_DATA.getItem(index);
    name = row.selectValue(null, "name").toString();
  }
  EventResult result = new EventResult(page);
  result.setProperty("action", event.getName());
  result.setProperty("name", name);
  return result;
}

複数選択

表での複数選択は、tableSelectionの子としてMultipleSelectionBeanを使用することにより処理されます。singleSelectionと同様に、multipleSelectiontext属性および索引付けされた子をサポートします。次に例を示します。

<table ... >
  <tableSelection>
   <multipleSelection text="Select record and ...">
    <contents>
     <submitButton text="Copy" ctrl:event="copy"/>
     <submitButton text="Delete" ctrl:event="delete" />
    </contents>
   </multipleSelection>
  </tableSelection>

  ...
</table>

multipleSelectionselected属性およびselection属性をデータ・バインドすることにより、初期選択項目を設定できます。MultipleSelectionBeanが列にスタンプされるたびに、そのselected属性が評価されます。このときに、selection DataObjectListの対応する行が現行のDataObjectになります。

<multipleSelection data:selected="selectedKey" ...>
 <selection>
  <!-- create a dataObjectList, each dataObject has a selectedKey
       whose value is either true or false -->
  <row selectedKey="true"/> <!-- select the first row -->
  <row selectedKey="false"/>
  <row selectedKey="true"/> <!-- select the third row -->

  ...
 </selection>

 ...
</multipleSelection>

この例では、selection属性の値をインラインで設定していますが、これは(前述のとおり)簡単にデータ・バインドできます。

複数選択は、サーバーでSelectionUtils.getSelectedIndices(...)メソッドを使用して処理されます。このメソッドは配列を返します。配列の各要素は、選択された行の索引です。次に、イベント・ハンドラのコードを示します。

public static EventResult doMultiSelectEvent(BajaContext bc, Page page,
                                             PageEvent event)
{
  PageEventFlattenedDataSet tableRows =
    new PageEventFlattenedDataSet(event, "table1");
  int[] indices = SelectionUtils.getSelectedIndices(tableRows);
  DataObjectList resultTableData = new SelectedList(_TABLE_DATA, indices);

  EventResult result = new EventResult(page);
  result.setProperty("action", event.getName());
  result.setProperty("tableData", resultTableData);
  return result;
}

SelectedListクラスは、便利なユーティリティ・クラスです。このクラスは、DataObjectListおよび選択された索引の配列を受け取り、選択されたDataObjectのみを含むDataObjectListを実装します。

private static final class SelectedList implements DataObjectList
{
  public SelectedList(DataObjectList data, int[] selectedIndices)
  {
    _data = data;
    _indices = selectedIndices;
  }

  // returns the number of selected rows
  public int getLength()
  {
    return _indices.length;
  }

  // gets the selected row at the given index.
  public DataObject getItem(int index)
  {
    return _data.getItem(_indices[index]);
  }

  private final DataObjectList _data;
  private final int[] _indices;
}

multipleSelectionには、「すべて選択」/「選択しない」、という機能があります。通常これは、現在のレコード・セットに表示されている表の行を選択(または選択解除)することを意味します。ただし一部のアプリケーションでは、(現在表示されている行だけでなく)レコード・セットのすべての行でこの操作を実行する必要があります。これを実行可能にするため、multipleSelectionではselectModeというフォーム・パラメータを追加します。ユーザーが「すべて選択」/「選択しない」をクリックしていない場合、このパラメータの値は空になります。ユーザーが「すべて選択」をクリックすると、このパラメータの値はallになります。ユーザーが「選択しない」をクリックすると、この値はnoneになります。サーバー上のイベント処理コードは、ユーザーが「すべて選択」/「選択しない」をクリックしたかどうかを、このパラメータを使用して判断します。

選択の使用不可

singleSelectionおよびmultipleSelectionは、どちらもdisabled属性をサポートします。選択Beanが列にスタンプされる際に、disabled属性が評価され、Boolean.TRUEが返された場合は、その行に対する選択機能が使用不可になります(表の現在の行のDataObjectが現行のDataObjectになります)。

<dataScope>
  <provider>
    <data name="tableData">
     <inline>
      <row name="Person 1" age="11" disabledKey="true"/>
      <row name="Person 2" age="12" disabledKey="false"/>
      <row name="Person 3" age="13" disabledKey="false"/>
      <row name="Person 4" age="14" disabledKey="true"/>
     </inline>
    </data>
  </provider>

  <contents>
   <form name="form1">
    <contents>

     <table data:tableData="row@tableData" ... >
       <tableSelection>
        <multipleSelection ...
          data:disabled="disabledKey">
        </multipleSelection>
       </tableSelection>

       ...
     </table>
    </contents>
   </form>
  </contents>
</dataScope>

ディテール公開

一部のアプリケーションでは、表の行に特定要素のプロパティの概要のみが表示されます。このような場合、ボタンをクリックすることで、その行の詳細情報が表示されるようにすると便利です。TableBeanのディテール公開機能により、この機能が提供されます。

ディテール公開は、tabledetailという名前の付けられた子を設定することによりオンになります。この子は、ユーザーが行の詳細を要求したときのみレンダリングされる点を除き、他のスタンプと同様です。したがって、この子は現在の行の詳細をすべて表示できます。次に例を示します。

<table ... >
  <detail>
   <!-- this is the detailed stamp -->
   <labeledFieldLayout>
    <contents>
    Name
    <styledText data:text="name" styleClass="OraDataText"/>
    Age
    <styledText data:text="age" styleClass="OraDataText"/>
    Blood Group
    <styledText data:text="blood" styleClass="OraDataText"/>
    Phone
    <styledText data:text="phone" styleClass="OraDataText"/>
    </contents>
   </labeledFieldLayout>
  </detail>

  <columnHeaderData>
   <col text="Name"/>
   <col text="Age" />
  </columnHeaderData>
  <contents>
    <!-- these are the regular column stamps -->
    <text data:text="name"/>
    <text data:text="age"/>
  </contents>
</table>

この例では、通常の表に各行のNameプロパティおよびAgeプロパティのみ表示されます。ユーザーが詳細を要求すると、detailスタンプがレンダリングされ、Blood GroupやPhoneなどの追加情報が提供されます(detailという子は、現行のDataObjectとして表の現在の行であるDataObjectでレンダリングされます。そのため、データ・バインドは、列スタンプと同じ方法で動作します)。

ただし、この例では、詳細セクションが公開されていません。このため、レンダリングされたdetailスタンプを参照できませんでした。UIとしてレンダリングされる各矢印の公開状態は、tabledetailDisclosure属性により制御されます。これは、表の各行に対応するDataObjectを持つDataObjectListである必要があります。各DataObjectは、キーUIConstants.DISCLOSED_KEY(または、uiXMLではdisclosed)で問合せされます。この問合せの結果がBoolean.TRUEである場合は、その行の詳細情報が公開されます。次に例を示します。

<table ... >
  ...

  <detailDisclosure>
   <row disclosed="false"/>
   <row disclosed="false"/>
   <row disclosed="true"/> <!-- disclose the third row -->
   <row disclosed="false"/>
   <row disclosed="false"/>
   <row disclosed="true"/> <!-- disclose the sixth row -->
  </detailDisclosure>
</table>

前の例では、ディテール公開矢印は実際には何も行いません。これらの例は形式だけのものです。インタラクティブ・デモの作成方法を説明します。矢印をクリックすると、詳細を表示するか非表示にするかに応じて、それぞれUIConstants.SHOW_EVENTまたはHIDE_EVENTというUIX Controllerのイベント(uiXMLではshowまたはhide)が生成されます。イベントには次の2つのパラメータがあります。

パラメータ UIConstant 説明
source SOURCE_PARAM このイベントを生成した表を示します。表のname属性です。
value VALUE_PARAM 公開する(または非公開にする)必要のある行を示します。ゼロから始まる索引です。

valueパラメータにより、公開または非公開にする必要のある行が簡単にわかります。ただし、すでに公開されている行に関する十分な情報は得られません。表の現在の公開状態を保存するには、クライアント側のページに状態を追加する必要があります。この例では、disclosedという名前の非表示フォーム・パラメータを詳細セクションに追加します(このため、表はフォーム送信モードで使用されている必要があります)。

<table formSubmitted="true" ... >
 ...
 <detail>
  <labeledFieldLayout>
   <contents>
   ...
   <formValue name="disclosed" value="1"/>
   </contents>
  </labeledFieldLayout>
 </detail>
</table>

次のイベント・ハンドラでは、ユーザーが選択した行の索引を取得し、イベント名に応じて公開するか非公開にするかを決定します。

public static EventResult doHideShowEvent(BajaContext bc, Page page,
                                          PageEvent event)
{
  PageEventFlattenedDataSet tableRows =
    new PageEventFlattenedDataSet(event, "table1");

  // this is the row that must be (un)disclosed:
  int row = Integer.parseInt(event.getParameter(UIConstants.VALUE_PARAM));
  // decide whether we want to disclose or undisclose depending on the name
  // of the event
  boolean disclose = UIConstants.SHOW_EVENT.equals(event.getName());

  DataObjectList detailData = new DetailData(tableRows, row, disclose);

  EventResult result = new EventResult(page);
  result.setProperty("detailData", detailData);
  return result;
}

このコードでは、表の現在の公開状態を含むPageEventFlattenedDataSetを作成します。これを使用してdetailData DataObjectListを作成します。このリストには、ツリーの以前の公開状態および新規状態が含まれます。このリストはEventResultに挿入されるため、uiXMLコードからアクセスできます。DetailDataクラスを次に示します。

private static final class DetailData implements DataObjectList
{
  /**
   * @param pageEvent contains the current disclosure state of the table
   * @param index the index of the row that must have its disclosure state
   * changed
   * @param disclosure the new disclosure state for the row
   */
  public DetailData(DataObjectList pageEvent, int index, boolean disclose)
  {
    _pageEvent = pageEvent;
    // initially, none of the table rows will be disclosed, so there will be
    // no pageEvent data and this length would be zero:
    _length = pageEvent.getLength();
    _index = index;
    _disclose = disclose;
  }

  public int getLength()
  {
    // make sure that the length we return is sufficiently large enough that
    // we reach the index we want to change
    return (_index >= _length) ? _index+1 : _length;
  }

  public DataObject getItem(int index)
  {
    boolean disclose;
    if (index==_index)
    {
      // this is the index that we want to change.
      disclose = _disclose;
    }
    else if (index < _length)
    {
      // this index can safely be pulled from the pageEvent
      DataObject row = _pageEvent.getItem(index);
      // if there was a "disclosed" form element on this row then we
      // consider the row disclosed:
      disclose = (row.selectValue(null, "disclosed") != null);
    }
    else
      disclose = false;

    return disclose ? _DISCLOSE_TRUE : _DISCLOSE_FALSE;
  }

  private final DataObjectList _pageEvent;
  private final int _index, _length;
  private final boolean _disclose;

  private static final DataObject _DISCLOSE_TRUE = new DataObject() {
      public Object selectValue(RenderingContext rc, Object key)
      {
        return Boolean.TRUE;
      }
    };

  private static final DataObject _DISCLOSE_FALSE = new DataObject() {
      public Object selectValue(RenderingContext rc, Object key)
      {
        return Boolean.FALSE;
      }
    };
}

基本的に、このクラスは、表の現在の公開状態によりどの行が公開されているかを判断し、true(公開されている場合)またはfalse(公開されていない場合)の適切なDataObjectを返します。指定された索引が公開状態の変更対象の行である場合は、変更された新しい公開状態が返されます。PageEventFlattenedDataSetは、表のフォーム入力要素が設定されている場合にのみデータを含むことに注意してください。初期状態では、何も公開されていないため、レンダリングされる入力要素はなく、FlattenedDataSetはその長さとしてゼロを返します。したがって、この特殊なケースは慎重に処理する必要があります。

列フッターの作成

場合によっては、データ領域の下部にある特別なセクションに、表のデータを要約して入れると適切な場合があります。これは列フッターと呼ばれ、表の名前の付けられた子でもあります。ただし、列ヘッダー・スタンプや行ヘッダー・スタンプとは異なり、列フッターはスタンプではありません。列フッターは、すべての列にスタンプされるのではなく、表の一番下に1回のみレンダリングされます。ほとんどの列フッターは、列ごとの処理ではなく、表全体の統計や処理を要約するように設計されています。列フッターとしてのみ使用される特殊なUINodeには、addTableRowおよびtotalRowの2つがあります。この2つについて詳しく説明します。

AddTableRow Bean

まず、addTableRowです。この列フッター・ノードは、ユーザーが実行時にデータ行を追加できるようにする表で使用するためのものです。追加する行数や表示するテキストのカスタマイズ、URLリンク先の変更を行うことができます。ただし、指定されていない場合はすべてデフォルトになります。次に、使用例を示します。

<table ... >
  ...

  <!-- column footer node -->
  <columnFooter>
   <addTableRow rows="5"/>
  </columnFooter>
</table>

この例では、ユーザーは5行を追加するよう求められますが、そのrows属性がない場合は、Beanがアクティブになったときに新しい1行が要求されます。また、これは、イベントをサーバーに送信するよう要求する表のカスタマイズです。ただし、これだけでは行は実行時に追加されません。ページは、追加の行を含めて再生成される必要があります。つまり、サーバーが通知を受けて、データソースを更新する必要があります。次に、UIConstants.ADD_ROWS_EVENT(uiXMLではaddRows)イベントの発生時にサーバーに送信されるパラメータを示します(これらは、これまでに説明したパラメータと似ています)。

パラメータ UIConstant 説明
source SOURCE_PARAM 表の名前
size SIZE_PARAM 追加するよう要求された行数

TotalRow Bean

前述のBeanと似ているのはtotalRowです。行を追加するかわりに、このBeanを使用して表の数値列の合計を表示します。ただし、これだけではBean自体は合計を計算しません。サーバーまたはJavaScriptにより計算された合計を適切に書式設定されたコントロールに表示します。

totalRowには、ユーザーが合計を更新できるよう列フッターにボタンをレンダリングし、また、各列にレンダリングされるコントロールがその列の合計値を保持できるようにするという2つの役割があります。ボタンは、Beanのtext属性を使用して構成できるテキストとともに自動的にレンダリングされます。ボタンを再びアクティブにすると、イベントがサーバーに送信されます。このときのイベントはUIConstants.UPDATE_EVENT(uiXMLではupdate)です。

パラメータ UIConstant 説明
source SOURCE_PARAM 表の名前

addTableRowとは異なり、totalRowでは、索引付けされた子を使用でき、それによって各列の合計値を保持できます。totalRowの最初の索引付けされた子は、表の一番右の列に配置され、後続の索引付けされた子は前の列の左側に配置されます。このため、次の例で示すように、索引付けされた子は、追加されると右から左の順で各列に表示されます。

<table ... >
  ...

  <!-- column footer node -->
  <columnFooter>
    <totalRow>
      <contents>
        <textInput name="total" text="42" columns="5"/>
      </contents>
    </totalRow>
  </columnFooter>
</table>

ここでも、更新ボタンがアクティブな場合に、合計を表す索引付けされた子ノードに対する実際の更新は、表の作成者が実行する必要があります。

場合によっては、表にtotalRowおよびaddTableRowの両方が必要です。これは、次の例で示すように、addTableRowの索引付けされた子としてtotalRowを追加することにより実行できます。

<table ... >
  ...

  <columnFooter>
   <addTableRow>
    <contents>
     <!-- addTableRow beans may have at most a single child,
          and this child can only be a totalRow bean -->
     <totalRow>
       <contents>
         <textInput name="total" text="400" />
       </contents>
     </totalRow>
    </contents>
   </addTableRow>
  </columnFooter>
</table>

表のコンテンツの書式設定

これまでは、表のセルの実際のコンテンツ、およびサーバーと通信するいくつかの処理についてのみ扱ってきました。データの構造を明確にするため、または読みやすくするために、表の外観または書式を変更することが必要な場合があります。表の書式をカスタマイズする方法は多数あり、その組合せの数も膨大になります。幸い、これらの異なるカスタマイズを実行するメカニズムには一貫性があります。

表の外観を変更する最も簡単な方法は、表の幅を変更することです。表の幅は、表のwidth属性を設定するだけで変更できます。幅は、ピクセル単位または親要素の幅に対するパーセントで指定できますが、一般にはパーセントが好まれます。ただし、表の幅に指定した値が、コンテンツを十分に収容できる大きさでない場合は、表示の際に上書きされ、必要な最低容量が使用されます。

次に、コンテンツのセルの位置揃えについて説明します。ユーザー・インタフェースの設計に応じて、タイプの異なるコンテンツの配置方法を区別し、ユーザーが理解しやすいようにする必要があります。これは、書式で使用されるDataObjectの新規セットを使用して行うことができます。この場合、位置揃えは特定の列のセル全体で統一されている必要があるため、表のcolumnFormat属性を使用します。他の多くのデータ・ドリブン表属性と同様に、これもDataObjectListです。この場合は、リスト内の各DataObjectが、表レイアウトの左から右の順で列の1つに対応します。これらのDataObjectのいずれかで書式を指定すると、対応する列の外観がそれに応じて変更されます。次に最初の例を示します。

<dataScope ... >
  <provider>
    <!-- all the data used by our table demo -->
    <data name="demoTableData">
      <inline>
        ...

        <!-- DataObjectList to provide column formatting -->
        <demoColumnFormats/>
        <demoColumnFormats columnDataFormat="numberFormat"/>
        <demoColumnFormats columnDataFormat="iconButtonFormat"/>
      </inline>
    </data>
  </provider>

  <contents>
    <form ... >
      <contents>
        <table ...
               width="100%"
               data:columnFormats="demoColumnFormats@demoTableData">
          <contents>
            <!-- the first column stamp, a text node -->
            <textInput data:text="firstColumnText" name="foo"/>
            <!-- a second column stamp, a static text node -->
            <text text="42"/>
            <!-- the third column stamp, a button -->
            <button data:text="secondColumnText" destination="http://www.example.org"/>
          </contents>

          ...
        </table>
      </contents>
    </form>
  </contents>
</dataScope>

この例ではいくつかの項目を変更しました。順番に説明します。表の幅は100%に設定しました。続いて、表に別の列スタンプ(テキスト・ノード)を追加して、処理内容を増やしました。次に、前と同様に新しいDataObjectListを作成し、今回はdemoColumnFormatsという名前を付けました。このリスト内のDataObjectは、3つの各列の書式設定を行います。これらのDataObjectに、書式データを挿入しました。詳細は後述します。最後に、表自体に新規属性columnFormatsを設定しました。この属性は、新規のDataObjectListをポイントします。

表の書式に使用されるDataObjectをさらに詳しく説明します。書式情報を提供することはすでに示しましたが、これがどのように機能するのかについて説明します。実装の実行者が選択した任意のキーで値を格納する他のDataObjectとは異なり、書式オブジェクトは、既知の公開されたキーで値を格納する必要があります。レンダリングの際に、表はこれらの各列書式DataObjectに、これらの各パブリック・キーに格納された値を要求します。DataObjectがキーの値を返した場合は、その値が列書式の変更に使用されます。キーの値が返されなかった場合は、デフォルトの書式が使用されます。

この例では、書式DataObjectは、既知のキーの1つであるcolumnDataFormat文字列で値を格納しました。このキーは、宣言されているコンテンツのタイプに基づいて、列のコンテンツの位置揃えを決定します。最初のDataObjectは、キーの値を返しません。そのため、テキストのコンテンツの位置揃えはデフォルトの左揃えになります。2番目のDataObjectは、問合せされた際に値numberFormatを返します。仕様では、数値は右揃えにするよう規定されているため、この表の列のデータ・セルはそれに従って配置されます。最後に、3番目の列はそのデータ型としてiconButtonFormatを返します。現在、この型はコンテンツを中央揃えにします。中央揃えは、アイコンまたはボタンの列に対して適切な位置揃えです。これは、前の例を参照するとよくわかります。

列書式

列書式DataObjectは、4つのキーの値を返すことができます。このすべての例を次に示します。

<dataScope ... >
  <provider>
    <!-- all the data used by our table demo -->
    <data name="demoTableData">
      <inline>
        ...

        <!-- DataObjectList to provide column formatting -->
        <demoColumnFormats cellNoWrapFormat="true"/>
        <demoColumnFormats columnDataFormat="numberFormat"
                           displayGrid="false"/>
        <demoColumnFormats columnDataFormat="iconButtonFormat"
                           width="100%"/>
      </inline>
    </data>
  </provider>

  <contents>
   ...
  </contents>
</dataScope>

この例に基づき、列書式DataObjectで可能なすべての書式についてまとめると、次のようになります。

キー
UIConstant
説明
columnDataFormat
COLUMN_DATA_FORMAT_KEY
表自体にはコンテンツのタイプを認識する機能がないため、このキーを使用して、表の列に表示されるコンテンツのタイプを指定します。異なるデータ書式を指定すると、データ・セルの位置揃えが変更されます。
  • textFormat: ほとんどの列スタンプに使用され、テキスト・データを含めるために使用されるデフォルトの書式。現在は、左揃えになります。
  • numberFormat: この書式を指定するとデータが右揃えになり、数値が読みやすくなるため、データ・セルに数値が含まれている列に対して指定します。
  • iconButtonFormat: アイコンおよびボタンは、この書式の値により中央揃えにすることができます。
cellNoWrapFormat
CELL_NO_WRAP_FORMAT_KEY
セルのコンテンツは、複数の行にまたがって表示しないほうがよい場合もあります。折返しをしないほうが適切なデータ列については、このキーが問合せされた際に、列書式DataObjectでtrue(Boolean.TRUE)を返すようにします。折返しを禁止すると表の幅が広くなりすぎることがあるため、これはデフォルトの動作ではありません。
width
WIDTH_KEY
Used to indicate what percentage of extra width in the table this particular column should take.表にコンテンツで必要とされる以上の領域がある場合は、その領域を列に配分します。ただし、一部の列(ボタンなど)には、与えられた最小の領域よりも多くの領域は必要ありません。テキスト列などのその他の列で、コンテンツの表示に必要な行を削減できる場合は、追加の領域を使用するほうが理想的です。列書式DataObjectのとる値は、50%や100%などのパーセント値で、列が取得する追加領域の割合を示します。すべての列の合計は100%になる必要があります。
displayGrid
DISPLAY_GRID_KEY
デフォルトでは、グリッド線はすべての列の左に表示されます。場合によっては、垂直のグリッド線の数を減らし、他の列間の関係と比較して一部の列間の関係を強調するようにする場合もあります。特定のデータ列の前、つまり左側の垂直グリッド線をオフにする場合は、その列がこのキーで問合せされた際にfalseを返します。

これらすべての列書式の値は厳格なルールではなく、ヒントにすぎません。一部のレンダリングの外観やプラットフォームでは、すべての書式の値がサポートされているとはかぎりません、あるエージェントで特定のルールが通用しない場合もあります。書式は依存性に欠けるため、アプリケーションにとって重要ではありません。

垂直グリッド線の表示場所を列で制御できることと同様に、行の水平グリッド線の表示場所をカスタマイズすることもできます。このカスタマイズは、次の例で示すように、表のrowFormats属性を使用して行います。

<dataScope ... >
  <provider>
    <!-- all the data used by our table demo -->
    <data name="demoTableData">
      <inline>
        ...

        <!-- DataObjectList to provide row formatting information -->
        <demoRowFormats/>
        <demoRowFormats displayGrid="false"/>
      </inline>
    </data>
  </provider>

  <contents>
    <form ... >
      <contents>
        <table ...
               data:rowFormats="demoRowFormats@demoTableData">
          ...
        </table>
      </contents>
    </form>
  </contents>
</dataScope>

この例では、2行目の上に水平グリッドを表示しないよう要求する行書式DataObjectを追加しました。この機能が使用されることはほとんどありませんが、このように正常に動作します。

まれなケースとして、columnHeaderFormatsおよびrowHeaderFormatsが使用できる場合もあります。これらは、tableに属性として設定できるDataObjectListでもあります。これらのリストDataObjectが応答する唯一のキーは、前に述べたcellNoWrapFormatキーです。各DataObjectは、列ヘッダーまたは行ヘッダーに対応し、コンテンツを折り返す機能を制御します。この場合も、折返しを禁止すると表の幅が広くなりすぎることがあるため、デフォルトでは折返しが許可されます。これらの書式属性は、使用をなるべく控える必要があります。

バンド

最後に説明する書式タイプは、バンドです。バンドとは、セルまたは列のコンテンツが視覚的にグループ化されるよう、表のセルまたは行の色を変更する機能のことです。バンドを制御する方法は2通りあります。まず、最も簡単で一般的な方法は、tableFormat DataObjectを使用して表全体のバンドを指定することです。この単一書式オブジェクトは、表全体に適用されるため、DataObjectListではなくDataObjectとなっています。

デフォルトでは、表にはバンドがありませんが、tableFormat DataObjectが表に設定されている場合は、キーtableBanding(UIConstants.TABLE_BANDING_KEY)で問合せされます。次の例で示すように、DataObjectが既知の値columnBanding(UIConstants.COLUMN_BANDING)を返す場合は、表の列を変更すると、異なる背景色で表示されます。

<dataScope ... >
  <provider>
    <data name="demoTableData">
      <inline>
        ...

        <!-- DataObject for table-wide formatting (i.e. banding) -->
        <demoTableFormat tableBanding="columnBanding"/>
      </inline>
    </data>
  </provider>

  <contents>
    <form ... >
      <contents>
        <table ...
               data:tableFormat="demoTableFormat@demoTableData">
          ...
        </table>
      </contents>
    </form>
  </contents>
</dataScope>

同様に、次の例で示すように、値rowBanding(UIConstants.ROW_BANDING)を返すと、表の行の背景色が変更されます。bandingInterval(UIConstants.BANDING_INTERVAL_KEY)キーで問合せされた際に整数を返して、変更される各バンドの行数を制御することもできます。

<dataScope xmlns="http://xmlns.oracle.com/uix/ui"
           xmlns:data="http://xmlns.oracle.com/uix/ui">
  <provider>
    <data name="demoTableData">
      <inline>
        ...

        <!-- DataObject for table-wide formatting (i.e. banding) -->
        <demoTableFormat tableBanding="rowBanding"/>
      </inline>
    </data>
  </provider>

  <contents>
    ...
  </contents>
</dataScope>

ただし、列バンドは任意のパターンでも実行できます。これにより、個々の列を、通常の変更パターンを使用せずに明るい色にすることができます。このためには、bandingShade(UIconstants.BANDING_SHADE_KEY)キーで問合せされた際に、列書式DataObject(前述)の1つ以上でlight(BANDING_SHADE_LIGHT)またはdark(BANDING_SHADE_DARK)を返すようにします。次の例では、第3列のみが明るいバンド色で表示されます。

<dataScope ... >
  <provider>
    <data name="demoTableData">
      <inline>
        ...

        <!-- DataObjectList to provide column formatting -->
        <demoColumnFormats cellNoWrapFormat="true"/>
        <demoColumnFormats columnDataFormat="numberFormat"
                           displayGrid="false"/>
        <demoColumnFormats columnDataFormat="iconButtonFormat"
                           width="100%"
                           bandingShade="light"/>

        <!-- DataObjectList to provide row formatting information -->
        <demoRowFormats/>
        <demoRowFormats displayGrid="false"/>

        <!-- DataObject for table-wide formatting (i.e. banding) -->
        <demoTableFormat tableBanding="rowBanding"/>
      </inline>
    </data>
  </provider>

  <contents>
    ...
  </contents>
</dataScope>

任意の列書式オブジェクトに対してバンドが明示的に設定されている場合は、表全体の書式が完全に無視されるため注意してください。表全体のバンドを明示的な列バンドと組み合せることはできません。組み合せると、正常な配色パターンが生成されません。

TableStyleクラス

書式に関して最後に注目すべき点は、oracle.cabo.ui.beans.table.TableStyleクラスに、これらのほとんどの書式オプションをJavaプログラマが設定する際に便利な方法が提供されていることです。たとえば、数値を含み、グリッドを使用しない列の書式を作成するには、次のようにします。

DataObject columnFormat = new TableStyle(TableStyle.HIDE_GRID_MASK |
                                         TableStyle.NUMBER_FORMAT_MASK);

カプセル化された列

前のセクションでは、列スタンプの設定、columnHeaderStampの設定、columnHeaderDataのバインドおよびcolumnFormatsの設定について説明しました。これらの各プロパティは、表のグローバル属性の設定に関連しています。(1つの列に関する)すべてのプロパティを1つの場所で設定する方法があると便利です。この際、ColumnBeanが役立ちます。次に、使用例を示します。

<table ... >
  <contents>
    ...

    <column>
     <columnFormat columnDataFormat="iconButtonFormat"
        bandingShade="light" width="50%" displayGrid="false"/>
     <columnHeader>Third Column</columnHeader>
     <contents>
      <!-- This is the column stamp -->
      <button data:text="secondColumnText"/>
     </contents>
    </column>

  </contents>
</table>

最初に注目すべき点は、column要素が表の列スタンプとして設定されていることです。この要素は、この列のヘッダーのスタンプとして使用されるcolumnHeaderという名前を付けられた子(tablecolumnHeaderStampに似ています)をサポートします。columnHeaderData属性は、スタンプをデータ・バインドする必要がある場合にサポートされます。columnFormatおよびcolumnHeaderFormatは、どちらも列を書式設定するためにサポートされています。

(前の例で示したように)column要素が表の他の属性と調和して動作することにも注意する必要があります。ただし、columnで設定された属性は、tableの対応する属性を上書きします。

クライアント側の処理

JavaScriptを使用することにより、特定のTable操作をクライアント側で実行できます。これらのすべての動作には、TableProxy JavaScriptオブジェクトが必要です。このオブジェクトを使用するには、proxied属性をBoolean.TRUEに設定する必要があります。

<table proxied="true">
 ...
</table>

入力要素の読込み/書込み

次に、TableProxyを使用して表要素を読み込む例を示します。この例では、列のすべての値を総計し、合計を更新します。次に、uiXMLコードを示します。

<dataScope>
  <provider>
    <data name="tableData">
     <inline>
      <row name="Person 1" cost="11" />
      <row name="Person 2" cost="12" />
      <row name="Person 3" cost="13" />
      <row name="Person 4" cost="14" />

      ...
     </inline>
    </data>
  </provider>

  <contents>
   <form name="form1">
    <contents>
     <table proxied="true" data:tableData="row@tableData"
            name="table1" ... >
       <contents>
         <text data:text="name"/>
         <textInput name="cost" data:text="cost"/>
       </contents>
       <columnFooter>
        <totalRow destination="javascript:updateTotal();">
         <contents>
          <textInput name="total" text="50"/>
         </contents>
        </totalRow>
       </columnFooter>
     </table>
    </contents>
   </form>
  </contents>
</dataScope>

更新ボタンをクリックすると、updateTotal() JavaScriptメソッドがコールされます。このメソッドは、表のnameを使用して新規のTableProxyを作成し、getLength()メソッドをコールすることにより表の行数を取得します。さらに、(各行の)列のフォーム要素を取得し、値を総計して適切なテキスト・フィールドに合計を書き込みます。getFormElement(...)メソッドは注意して使用してください。このメソッドは、要素のnameおよび行索引を受け取り、その要素を返します。

function updateTotal()
{
 var proxy = new TableProxy('table1');
 var rowCount = proxy.getLength(); 
 var total = 0;
 for(var i=0; i<rowCount; i++)
 {
  var currTextField = proxy.getFormElement('cost', i);
  // the minus zero here is necessary to convert the string value
  // to a number
  total += currTextField.value - 0;
 }
 document.form1.total.value = total;
}

選択の処理

表が単一選択モードで使用されている場合は、TableProxygetSelectedRow()メソッドを使用し、現在選択されている行の索引を取得できます。次に例を示します。

function hire()
{
 var proxy = new TableProxy('table1');
 var row = proxy.getSelectedRow();
 // check to make sure that something was selected
 if (row < 0)
 {
  alert('You have not chosen anyone!');
 }
 else
 {
  var name = proxy.getFormElement('theName', row).value;
  alert('You have chosen to hire '+name);
 }
}

この関数は、次のuiXMLによりコールされます。

<table name="table1" ...>
  <tableSelection>
   <singleSelection ...>
    <contents>
     <button text="Hire" destination="javascript:hire()"/>
    </contents>
   </singleSelection>
  </tableSelection>
  <contents>
    <text data:text="name"/>
    <text data:text="cost"/>
    <formValue name="theName" data:value="name"/>
  </contents>
</table>

同様に、TableProxyに対してgetSelectedRows()メソッドを使用し、複数選択モードで選択された行索引を取得します。このメソッドは配列を返します。配列の各要素は、選択された行の索引です。次に、JavaScriptコードのサンプルを示します。

function recruit()
{
 var proxy = new TableProxy('table1');
 var rows = proxy.getSelectedRows();
 var length = rows.length;
 // make sure that something was selected
 if (length > 0)
 {
  var list = "";
  // loop through each selected row and concatenate the name
  for(var i=0; i < length; i++)
  {
   // get the next selected row index
   var row = rows[i];
   // get the selected row (from the index) and pull out the name
   var name = proxy.getFormElement('theName', row).value;
   list += '\n'+name;
  }
  alert("You have chosen to recruit "+list);
 }
 else
 {
  alert("You have not chosen anyone to recruit!");
 }
}

このメソッドは、次のuiXMLからコールされます。

<table ... >
  <tableSelection>
   <multipleSelection text="Select ...">
    <contents>
     <button text="Recruit" destination="javascript:recruit()"/>
    </contents>
   </multipleSelection>
  </tableSelection>

  ...
</table>

まとめ

このトピックでは、TableBeanについて説明し、これを使用して表形式のデータを表示する方法、およびレコード・ナビゲーション、ソート、選択、ディテール公開および合計を実装する方法を説明しました。詳細は、TableBeanのJavadoc、またはuiXMLの<table>要素のリファレンスを参照してください。