ナビゲーション・ヘッダーをスキップ
Oracle ADF UIX開発者ガイド 目次へ
目次
前のページへ戻る
前へ
次のページへ進む
次へ

9.A. ADF UIXの表の例

このトピックでは、Oracle ADF UIXのtableの例を示します。 実装するtable機能には、レコード・ナビゲーション、ソート、複数選択およびディテール公開が含まれます。 この例で使用するUIX XMLおよびJavaコードは、開発環境にコピーした後で簡単に変更できるように設計されています。そのため、このコードを基にして、実用的なコードをすぐに生成できます。 このトピックを読む前に、次の内容について理解しておく必要があります。

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

表データ

このデモの表データは、本のリストです。 それぞれの本には、タイトル、説明、著者、ページ数、ISBN、発行元および出版日の各プロパティがあります。 そのため、本に対して次のBeanを使用します。

public class Book
{
  public Book(String title, String description, String author,
              String isbn, int pages, Date published, String publisher)
  {
    _title = title;
    _desc = description;
    _author = author;
    _isbn = isbn;
    _pages = pages;
    _published = published;
    _publisher = publisher;
  }

  public String getTitle()
  {
    return _title;
  }

  public String getDescription()
  {
    return _desc;
  }

  public String getAuthor()
  {
    return _author;
  }

  public String getISBN()
  {
    return _isbn;
  }

  public int getPages()
  {
    return _pages;
  }

  public Date getPublishedDate()
  {
    return _published;
  }

  public String getPublisher()
  {
    return _publisher;
  }

  private final String _title, _desc, _author, _isbn, _publisher;
  private final int _pages;
  private final Date _published;
}

このような本のライブラリが存在するのが理想的です。ユーザーは、ライブラリに接続して検索を実行します。 検索条件に一致する本は本のリストにコピーされ、ユーザーは表を使用してそのリストを参照します。

このデモでは検索は実装しません。 かわりに、リストに本をランダムに移入して、ユーザーに対して表示します。 このランダムな本のリストは、クラスoracle.cabo.doc.demo.table.BookUtilsによって作成されます。ただし、このクラスの実際の機能については、ここでは示しません。

BookListは、oracle.cabo.doc.demo.table.UserDataオブジェクトに格納されます。 通常、このオブジェクトには、ユーザーの名前や作業環境などが格納されますが、このデモでは、格納されるプロパティは本のリストのみです。 UserDataオブジェクトはHttpSessionに格納されるため、本のリストはユーザーごとに存在します。

このList<table>要素のtableData属性にデータ・バインドされると、<column>要素を使用して、Bookの各プロパティを表の列に表示できるようになります。 次の例では、Published Dateプロパティが列に表示されます。

<table name="books"
       tableData="${sessionScope.userData.bookList}">
 <contents>
  <column>
   <columnHeader>
    <sortableHeader text="Published Date"/>
   </columnHeader>
   <contents>
    <!-- standard Java introspection calls the method
         getPublishedDate() to resolve the property
         publishedDate -->
    <text text="${uix.current.publishedDate}"/>
   </contents>
  </column>
  <!-- Other columns -->
  ...
 </contents>
</table>

publishedDateキーをBeanのgetPublishedDate()メソッドにマップする方法の詳細は、「ADF UIXでのデータ・バインディング」のトピックの関連するセクションを参照してください。

Bookのプロパティを表示するには複数の<column>要素が必要なため、列テンプレート<demo:sortableColumn>を作成する必要があります。 <column>から継承される属性および子に加えて、テンプレートでは、属性headerおよびkeyが認識されます。 headerのテキスト(「Published Date」など)は列のヘッダーに表示され、key(「publishedDate」など)はBeanのプロパティを取得するために使用されます。 次に、テンプレートのUIX XMLコードを示します。

<templateDefinition targetNamespace="http://xmlns.oracle.com/uix/demo"
                    localName="sortableColumn">
 <type base="data:column">
  <attribute name="key" javaType="string"/>
  <attribute name="header" javaType="string"/>
  <!-- by default this column will be sortable.
          However, if this attribute is set to 'false',
          then this column will not be sortable -->
  <attribute name="sortable" javaType="boolean"/>
 </type>

 <content>
  <column>
   <attributeMap><rootAttributeMap/></attributeMap>

   <columnHeader>
    <sortableHeader text="${uix.rootAttr.header}"
                    value="${uix.rootAttr.key}">
     ...
    </sortableHeader>
   </columnHeader>
   <contents>
    <text text="${uix.current[uix.rootAttr.key]}"/>
   </contents>
  </column>
 </content>
</templateDefinition>

この新しいテンプレートを使用すると、表は次のようになります。

<table name="books"
       tableData="${sessionScope.userData.bookList}">
 <contents>
  <demo:sortableColumn key="title" header="Title"/>
  <demo:sortableColumn key="author" header="Author">
   <columnFormat width="20%"/>
  </demo:sortableColumn>
  <demo:sortableColumn key="pages" header="Pages">
   <columnFormat width="1%" columnDataFormat="numberFormat"/>
  </demo:sortableColumn>
 </contents>
</table>

この例では、それぞれのBookのタイトル、著者およびページ数の各プロパティに対応する3つの列を表示します。

表の状態

表にBookを表示したので、次はレコード・ナビゲーション、ソートおよびディテール公開を実装します。 これらの機能は、クラスoracle.cabo.doc.demo.table.TableStateによって実装します。 このクラスのインスタンスは、ListBook Listなど)およびblockSize(1ページに表示するレコードの最大数)を使用して構築されます。 このクラスには、一度に1ページずつ表のデータを取得するメソッド、行のディテール公開状態を切り替えるメソッド、およびBeanプロパティによってListをソートするメソッドが含まれています。

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

レコード・ナビゲーションを実装するために、<table>要素のblockSizevalueおよびmaxValueを、TableStateblockSizestartIndexおよびlastIndexにデータ・バインディングします。 また、<table>要素のtableData属性を、TableStatecurrentRecordSetプロパティにバインドする必要があります。 このプロパティは、各blockSize要素のページ数のデータを返します。

Book Listを直接返すかわりに、getBookTableState()メソッドを使用して、対応するTableStateオブジェクトを返すようにUserDataクラスを変更します。 この結果、<table>要素は次のようになります。

<table tableData="${sessionScope.userData.bookTableState.currentRecordSet}"
       blockSize="${sessionScope.userData.bookTableState.blockSize}"
       maxValue="${sessionScope.userData.bookTableState.lastIndex}"
       value="${sessionScope.userData.bookTableState.startIndex}">
 ...
</table>

この表では、ナビゲーション・コントロールをクリックすると、UIXサーブレットのgotoイベントが起動されます。 このイベントには、表示する新しいレコード・セットの索引を示すvalueパラメータがあります。 次のコードは、TableStateに新しいレコード・セットの索引を設定して、このイベントを処理します。

UserData uData = _getUserData(..); // from HttpSession
TableState tState = uData.getBookTableState();
String valueParam = event.getParameter(UIConstants.VALUE_PARAM);
int value = Integer.parseInt(valueParam);
tState.setStartIndex(value);

ソート

表にソート可能なヘッダーを実装するために、<sortableHeader>要素を使用します。 TableStateオブジェクトには、sortByプロパティがあります。このプロパティは、表の現在のソート条件(authorなど)を示す(Bookの)Beanプロパティです。 また、このオブジェクトには、ソートの方法(昇順または降順)を示すsortOrderプロパティがあります。 これら2つのプロパティはロジックを使用して組み合され、各<sortableHeader>要素のsortable属性に対して値ascendingまたはdescendingが生成されます。 次に、(<demo:sortableColumn>の)関連するUIX XMLを示します。

<sortableHeader text="${uix.rootAttr.header}"
                value="${uix.rootAttr.key}">
 <boundAttribute name="sortable">
  <if>
   <!-- is this table currently sorted by the bean property displayed by
        this column? -->
   <comparison type="equals">
    <dataObject select="sortBy">
     <contextProperty select="demo:tableState"/>
    </dataObject>
    <dataObject source="${uix.rootAttr.key}""/>
   </comparison>

   <!-- if so, then we need to return either "ascending" or "descending"
        depending on the sortOrder -->
   <if>
    <dataObject select="sortOrder">
     <contextProperty select="demo:tableState"/>
    </dataObject>
    <fixed>ascending</fixed>
    <fixed>descending</fixed>
   </if>

   <!-- Otherwise, we might be sortable (though not sorted in any
        particular order) as long as the user has not set the sortable
        attribute to false -->
   <if>
    <defaulting>
     <dataObject source="${uix.rootAttr.sortable}"/>
     <!-- by default, we are sortable -->
     <fixed text="true"/>
    </defaulting>
    <fixed>yes</fixed>
    <fixed>no</fixed>
   </if>

  </if>
 </boundAttribute>
</sortableHeader>

ソート可能な列ヘッダーをクリックすると、sortイベントが生成されます。 各イベントには、列に表示されているBeanプロパティを示すvalueパラメータがあります。 このイベントの処理では、そのBeanプロパティに対してComparatorが作成され、TableStateに対してsetSortByメソッドがコールされます。

UserData uData = _getUserData(..); // from HttpSession
TableState tState = uData.getBookTableState();
String valueParam = event.getParameter(UIConstants.VALUE_PARAM);
// create a Comparator that will sort our list of Book beans by the
// required property:
BeanComparator sorter = new BeanComparator(Book.class, valueParam);
tState.setSortBy(valueParam, sorter);

ディテール公開

TableStateオブジェクトには、表のディテール公開状態を保持する、detailDisclosureListというプロパティがあります。 このプロパティは、<table>要素のdetailDisclosure属性にデータ・バインドする必要があります。

<table tableData="${sessionScope.userData.bookTableState.currentRecordSet}"
       detailDisclosure="${sessionScope.userData.tableState.detailDisclosureList}"
       blockSize="${sessionScope.userData.bookTableState.blockSize}"
       maxValue="${sessionScope.userData.bookTableState.lastIndex}"
       value="${sessionScope.userData.bookTableState.startIndex}">
 ...
</table>

(詳細列内の)表示/非表示リンクがクリックされると、UIXサーブレットのイベントhideおよびshowが起動されます。 これらのイベントにも、クリックされた行の索引を示すvalueパラメータがあります。 このイベントの処理では、TableStateに対してtoggleDisclosedメソッドがコールされます。

UserData uData = _getUserData(..); // from HttpSession
TableState tState = uData.getBookTableState();
String eventName = event.getName();
String valueParam = event.getParameter(UIConstants.VALUE_PARAM);
// This handles detail disclosure.  The event must have a "value"
// parameter which is the index of the row to disclose or
// undisclose. This index is based at zero.  The "value" might also be
// VALUE_SHOW_ALL which signals a show-all or hide-all.
if (UIConstants.VALUE_SHOW_ALL.equals(valueParam))
{
  // if the event is "show" then do a show-all. Otherwise, do a hide-all:
  tState.showAllDetails(UIConstants.SHOW_EVENT.equals(eventName));
}
else
{
  int value = Integer.parseInt(valueParam);
  tState.toggleDisclosed(value);
}

表のイベント・ハンドラ

このデモでは、前述の(レコード・ナビゲーション、ソートおよびディテール公開用の)すべてのイベントを、oracle.cabo.doc.demo.table.BookEventsクラスのbooks_tableEventメソッドで処理します。 このメソッドは、sourceパラメータを確認し、イベントを生成した表を識別して(このデモでは表は1つのみです)、対応するTableStateオブジェクトを取得して前述のイベント処理を実行します。 イベント・ハンドラは、UIX XMLに次のように登録します。

<event name="goto sort show hide" source="books">
 <method class="oracle.cabo.doc.demo.table.BookEvents"
         method="books_tableEvent"/>
</event>

複数の表のサポート

同じページで複数の表をサポートすることは簡単です。 各表に個別のTableStateオブジェクトが存在する必要があります。 (表イベント用の)イベント・ハンドラでは、sourceパラメータを確認することによって、使用する適切なTableStateを判断できます。

UIX XMLでは、各<table>の属性tableDatadetailDisclosureblockSizemaxValueおよびvalueが、対応するTableStateの適切なプロパティにデータ・バインドされている必要があります。 たとえば、booksという<table>で、UserDataに対してgetBookTableState()メソッドをコールすることによって、TableStateを取得します。

<table name="books"
       tableData="${sessionScope.userData.bookTableState.currentRecordSet}"
       detailDisclosure="${sessionScope.userData.tableState.detailDisclosureList}"
       blockSize="${sessionScope.userData.bookTableState.blockSize}"
       maxValue="${sessionScope.userData.bookTableState.lastIndex}"
       value="${sessionScope.userData.bookTableState.startIndex}">
 ...
</table>

すべての表に対してこのようなデータ・バインディングを実行する必要があるため、この方法は非効率的です。 より効率的に実装するには、テンプレート<demo:table>を使用します。 このテンプレートは、<table>のすべてのプロパティおよび子を継承しますが、tableState属性も使用します。

<templateDefinition targetNamespace="http://xmlns.oracle.com/uix/demo"
                    localName="table">
 <type base="data:table">
  <attribute name="tableState"
             javaType="oracle.cabo.doc.demo.table.TableState"/>
 </type>

 <content>
    <table tableData="${uix.rootAttr.tableState.currentRecordSet}"
           detailDisclosure="${uix.rootAttr.tableState.detailDisclosureList}"
           blockSize="${uix.rootAttr.tableState.blockSize}"
           maxValue="${uix.rootAttr.tableState.lastIndex}"
           value="${uix.rootAttr.tableState.startIndex}">
     <childList><rootChildList/></childList>
     <attributeMap><rootAttributeMap/></attributeMap>
     <childMap><rootChildMap/></childMap>
    </table>
 </content>
</templateDefinition>

これによって、開発者は、次のように記述してbooks表を作成できます。

<demo:table name="books"
            tableState="${sessionScope.userData.bookTableState}">
 ...
</demo:table>

tableDatadetailDisclosureblockSizemaxValueおよびvalueを個別にデータ・バインドする必要がないことに注意してください。この処理は、テンプレートによって行われます。 そのため、UserDatawishListTableStateプロパティからデータを取得するwishListという別の表を作成するには、別の<demo:table>を作成します。

<demo:table name="wishList"
            tableState="${sessionScope.userData.wishListTableState}">
 ...
</demo:table>

複数選択

選択は、<table>要素に子<tableSelection>を設定すると、有効になります。 このデモでは、ユーザーの本のリストから本を削除する機能をサポートします。 そのため、クリックするとサーバーにdeleteイベントが送信されるボタンが、表のコントロール・バーに必要です。

<demo:table name="books" ... >
 <tableSelection>
  <multipleSelection text="Select and ...">
   <contents>
    <submitButton text="Delete" event="delete"/>
   </contents>
  </multipleSelection>
 </tableSelection>
 ...
</demo:table>

選択の処理

選択に関連するイベントのイベント・ハンドラでは、oracle.cabo.servlet.ui.data.PageEventFlattenedDataSetおよびoracle.cabo.ui.beans.table.SelectionUtilsを使用して、現在選択されている索引を取得できます。 次に、前述のdeleteイベントのイベント・ハンドラに関連するコード部分を示します。

// the data that is coming from the client are form elements inside a
// table. therefore, we can use some FlattenedDataSet implementation to
// get at the data:
PageEventFlattenedDataSet clientData =
  new PageEventFlattenedDataSet(event, "books");
// get the indices that were selected (note that these indices are
// relative to the current record set):
int[] selection = SelectionUtils.getSelectedIndices(clientData);

これによって、変更するTableState(この場合はBook表の状態)から表データを取得して、削除を実行できます。

UserData uData = _getUserData(..); // from HttpSession
TableState tState = uData.getBookTableState();
List tableData = tState.getTableData();

// this is the offset to add to convert a selection index into an actual
// book index. Note that the -1 is significant since getStartIndex is
// based at 1 and the selection indices are based at zero:
int currentRecordOffset = tState.getStartIndex() - 1;

// BEGIN handle select-all
// ** select-all not yet implemented **
// END handle select-all

// if we loop forward then the books we delete first will affect the
// indices of the books we delete second. So must loop backwards:
for(int i=selection.length-1; i>=0; i--)
{
  // each selected index is relative to the current record set. therefore
  // we need to add an offset to get the index of the actual book:
  tableData.remove(selection[i] + currentRecordOffset);
}

「すべて選択」の処理

「すべて選択」がクリックされた場合に、(現在のレコード・セット内のレコードのみでなく)表のすべてのレコードが削除されるようにします。 この機能をサポートするために、ユーザーが「すべて選択」を使用したかどうかを検出する必要があります。 これを行うには、表のFlattenedDataSetからUIConstants.SELET_MODE_KEYの値を取得して、値が"all"かどうかを確認します。 (前述のコードを更新した)次のコード部分では、「すべて選択」および削除を順に処理します。

// BEGIN handle select-all
// check to see if user has picked select-all:
Object selectMode = clientData.selectValue(null /*renderingContext*/,
                                           UIConstants.SELECT_MODE_KEY);
if ("all".equals(selectMode))
{
  // we need to handle select-all+delete carefully - we can't just delete
  // everything since the user might have unselected something in the
  // current record set. so first, delete everything upto (but not
  // including) the current record set:
  tableData.subList(0, currentRecordOffset).clear();

  // now the current record set starts at index 0:
  currentRecordOffset = 0;

  // second, delete everything that follows the current record set:
  int sz = tableData.size();
  int firstIndexOnNextPage = tState.getBlockSize();
  // check to make sure that there are records on the next page:
  if (firstIndexOnNextPage < sz)  tableData.subList(firstIndexOnNextPage, sz).clear(); 
  // third, continue deleting the selected records in the current record
  // set:
}
// END handle select-all

モックアップ表

表データは、問合せに応じてJavaコードで作成するのが理想的です。 ただし、UIモックアップを使用している場合、表データを静的インライン・データとしてUIX XMLに定義すると便利です。 このセクションでは、インライン・データを使用して対話型の表を作成する方法を示します。

これを行うには、クラスoracle.cabo.doc.demo.table.MockupUtilsの適切なメソッドを使用して、TableStateオブジェクトを作成します。 次に、簡単なUIX XMLコード部分を示します。


<dataScope>
 <provider>
  <data name="tableStates">
   <method class="oracle.cabo.doc.demo.table.MockupUtils"
           method="createTableState"/>
  </data>
  <!-- create some static data for the Roster table -->
  <data name="Roster">
   <inline>
    <row subject="Math" students="33" teacher="Jacobs"/>
    <row subject="Physics" students="34" teacher="Kim"/>
    <row subject="Chemistry" students="31" teacher="Dutta"/>
    <row subject="Biology" students="25" teacher="Liang"/>
    <row subject="Phys Ed" students="50" teacher="Goldstein"/>
    <row subject="English" students="29" teacher="Sulaiman"/>
    <row subject="French" students="20" teacher="Simone"/>
    <row subject="Spanish" students="23" teacher="Fernando"/>
    <row subject="German" students="11" teacher="Bosch"/>
   </inline>
  </data>
  ...
 </provider>
 <contents>
  ...
  <demo:table name="rosterTable"
              tableData="${uix.data.Roster.row}"
              tableState="${uix.data.tableStates[0]}">
   <contents>
    <demo:sortableColumn key="subject" header="Subject"/>
    <demo:sortableColumn key="teacher" header="Teacher"/>
    <demo:sortableColumn key="students" header="Students"/>
   </contents>
   ...
  </demo:table>
  ...
 </contents>
</dataScope>

最後に、モックアップ表のイベント・ハンドラを登録する必要があります。


<handlers>
 <!-- the examples in this file require an HttpSession to be created
     for each user -->
 <event name="null">
  <method class="oracle.cabo.doc.demo.table.MockupUtils"
          method="createSession"/>
 </event>

 <!-- handle all the table events -->
 <event name="goto sort show hide">
  <method class="oracle.cabo.doc.demo.table.MockupUtils"
          method="handleTableEvent"/>
 </event>
</handlers>

まとめ

これで、Book表の例は終了です。 環境でHttpSessionを使用している場合、この例で使用されているクラスおよびコードを(若干変更して)簡単にアプリケーションに適合できます。