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

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

14. uiXMLでのインクルードおよびテンプレート定義

これまでのトピックで、uiXMLページの作成について一通り学びました。しかし、すべてのページで同じようなUIXを何度も記述するのは煩雑です。煩雑なだけではなく、メンテナンスも困難になります。他のグローバル・ボタンを追加する、またはブランド・イメージを変更するためには、アプリケーションのすべてのUIXを更新する必要があるからです。

より効率的な方法があります。作成するページを再利用可能にするため、UIXではインクルードとテンプレート定義の2つの機能が用意されています。ユーザー・インタフェースのメンテナンスおよびカスタマイズが容易になり、メモリー消費量も少なくなります。

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

問題点

次のようなページを記述したと想定します。

 <pageLayout>

   <globalButtons>
     <globalButtonBar>
        ...
     </globalButtonBar>
   </globalButtons>

   <tabs>
     <tabBar>
         ...
     </tabBar>
   </tabs>

   <productBranding>
     <image .../>
   </productBranding>

   <!-- etc... -->
 </pageLayout>

作成するページがすべてこのページと同様である場合があります。各ページに同じ<globalButtons>、同じ<tabs>、同じブランド・イメージの<pageLayout>が記述されます。これをすべてコピー&ペーストする方法もありますが、グローバル・ボタンをもう1つ追加するときなど、すべてのページにわたってコンテンツの変更が必要な場合には対応できません。すべてのuiXMLファイルで修正が必要です。この作業は煩雑で、エラーの原因となります。また、各ページが同じコンテンツをロードおよび再解析する必要があり、キャッシュされた結果ツリーがまったく共有されないため、メモリーと時間を浪費します。これよりも、UIX要素を一度定義して再利用する方が効率は上がります。

解決方法: インクルード

UIXの<include>要素を使用すると、この問題を解決できます。同じコンテンツを何度も繰り返すかわりに、そのコンテンツを1回だけ記述して、それを必要とする各ページからインクルードできます。たとえば、次のようなページを作成します。

ファイルsomePage.uix

 <pageLayout xmlns:ctrl="http://xmlns.oracle.com/uix/controller">

   <globalButtons>
     <include ctrl:node="gbInclude"/>
   </globalButtons>

   <tabs>
     <include ctrl:node="tabsInclude"/>
   </tabs>

   <productBranding>
     <include ctrl:node="pbInclude"/>
   </productBranding>

   <!-- etc... -->
 </pageLayout>

インクルードされた要素のあるファイルを次に示します。

ファイルgbInclude.uix

<globalButtonBar xmlns="http://xmlns.oracle.com/uix/ui">
 <contents>
   <globalButton .../>
   <globalButton .../>
 </contents>
</globalButtonBar>

前述の<include>要素の1つを詳しく説明します。

     <include ctrl:node="gbInclude"/>

uiXMLの<include>要素の属性はnodeの1つのみです。ここでは、その属性をControllerネームスペースに入れています。これによって、UIXではターゲットがファイル名ではなく、UIX Controllerのページ名として解釈されます。gbIncludeの後に .uixがないのは、そのためです。node属性を使用する他の構文については後述しますが、uiXMLを使用する場合、この形式をお薦めします。

ターゲットのuiXMLファイルは、Controllerベースの他のuiXMLファイルと類似しています。開始は次のようになります。

 <?xml version="1.0" encoding="UTF-8"?>
 <!-- an included file -->
 <page xmlns="http://xmlns.oracle.com/uix/controller">
     ... etc.

任意のデータ・バインドまたはコンテンツをインクルードできます。(UIX 2.0.5の場合、インクルードされたファイルにイベント処理を組み込むことができますが、それらのハンドラは無視されます。この制限には、今後対応する予定です。)ただし、<page>および<content>要素を省略して、コンテンツを直接記述することもできます。

 <?xml version="1.0" encoding="UTF-8"?>
 <!-- an included file, but just content -->
 <pageLayout xmlns="http://xmlns.oracle.com/uix/ui">
     ... etc.

相対ページ名とフル・ページ名

UIXでは、UIX Controllerのページ名を相対ページ名でもフル・ページ名でも指定できます。スラッシュ(/)で始まる名前は、フル・ページ名として扱われます(スラッシュは削除します)。したがって、たとえばすべてのインクルード・ファイルを、UIXディレクトリのルート以下の/commonというサブディレクトリに置いた場合、次の形で組み込むことができます。

     <include ctrl:node="/common/includedName"/>
スラッシュで始まらない名前は、インクルード・ファイルまでの相対パスと解釈されます。たとえば、foo/somePageというページにいる場合に、次のような2つの<include>を指定します。
     <include ctrl:node="anotherPage"/>
     <include ctrl:node="bar/thirdPage"/>
これらは、foo/anotherPageおよびfoo/bar/thirdPageの<include>として扱われます。

ctrl:nodeというインクルードの指定では、uiXMLファイルではなく、UIX Controllerのページをインクルードするので注意してください。すなわち、UIX Controllerに明示的に登録されたuiXMLベースまたはUIX Componentsベースのページは、uiXMLファイルにアクセスできない場合でもインクルードが可能だということです。

UIX Controllerを使用しないインクルード

UIX Controllerを使用していない場合でも、<include>を使用できます。node属性からControllerネームスペースを省略すると、その値はファイル名として解釈されます。

 <pageLayout>

   <globalButtons>
     <include node="gbInclude.uix"/>
   </globalButtons>
    ...etc...
 </pageLayout>

この方法でインクルードを記述した場合、インクルードされたターゲット・ファイルは、<page>要素ではなくUIコンテンツで直接始まる必要があります。そのため、このタイプのインクルードではイベント・ハンドラをインクルードできません(ただし、前述したようにUIX Controllerでも、まだイベント・ハンドラはサポートされていません)。

また、node属性を指定することもできます。

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

   <globalButtons>
     <include data:node="gbInclude@demo:someSource"/>
   </globalButtons>
    ...etc...
 </pageLayout>

この例では、ノードはレンダリングの際にsomeSource DataObjectから取得されます。この手法の詳細は、「uiXMLページの動的構造」を参照してください。

インクルードの限界

<include>の最も根本的な問題は、インクルードされるファイルをそのまま使用する必要があるということです。親ページからは、インクルードされるファイル内の要素に属性を設定する、または要素を追加することができません。これがインクルードの限界です。これを回避する方法があります。親ページから参照されるDataObjectは、インクルードされるページからも参照できます。したがって、次のようなコーディングが可能です。

 <dataScope>
   <provider>
     <data name="demo:someData"> ...</data>
   </provider>
   <contents>
     <include .../>
   </contents>
 </dataScope>

ここでは、インクルードされるファイル内の"demo:someData"を参照しています。DataObject内にUINodeを追加すれば、<include data:node="..."/>を使用して内部の子要素を取得できます。ただし、これは洗練されたコーディングではありません。

たとえば、インクルードを次のように記述してみます。

 <pageLayout xmlns:ctrl="http://xmlns.oracle.com/uix/controller">

   <include ctrl:node="pageIncludes"/>
    ...
 </pageLayout>

そして、インクルードされるファイルを次のように変更します。

<globalButtons>
 <globalButtonBar>
    ...
 </globalButtonBar>
</globalButtons>

<tabs>
 <tabBar>
  ...
 </tabBar>
</tabs>

...etc...

これは手軽な方法ですが、機能しません。インクルードされるファイルは、妥当性のあるXMLファイルである必要があり、ルート要素は1つのみとなります。あるいは、次のようなコーディングも考えられます。

 <pageLayout xmlns:ctrl="http://xmlns.oracle.com/uix/controller">

   <include ctrl:node="gbInclude"/>
    ...
 </pageLayout>

インクルードされるファイルは、次のようになります。

<globalButtons>
 <globalButtonBar>
    ...
 </globalButtonBar>
</globalButtons>

こうすれば、インクルードするファイルごとに<globalButtons>を記述する必要がなくなります。しかし、これも機能しません。<include>要素に含めることができるのは、UINode要素(UIX ComponentsのUINodeに直接マップされる要素)のみです。<globalButtons>はそのような要素ではなく<pageLayout>により使用されるラッパー要素であり、単独では使用できません。

<pageLayout>の場合は特に、このような限界が障害となります。これらの問題は、すべてUIXのテンプレートで解決できます。

もう1つの解決方法: テンプレート

テンプレート定義では、モジュール化したuiXMLファイルを構築する際の問題をまったく異なる方法で解決します。テンプレートを使用すると、既存の要素にかわる新規のUINode要素を定義できます。この新規の要素は、ビルトイン・ノードのすべての属性と子要素を持つことができます。ノード・ツリーの下位から個々のコンテンツを抽出するかわりに、ツリーの最上位から始めることができます。

テンプレートは、他のuiXML要素を使用する場合と同じく簡単に使用できますが、テンプレート定義の作成はそれほど容易ではありません。テンプレート定義はデータ・バインドに依存しているため、UIX ComponentsのAPIについての基本的な知識が必要です。名前の付けられた子と索引付けされた子の違いを区別できないような場合には、テンプレート定義は難解になります。ただしテンプレート定義は、もともと記述する必要のあったJavaのRendererおよびNodeParserクラスを記述するより簡単です。

以降の各例では、次のような単純なテンプレートを作成し、demoPageLayout.uitという名前のファイル内にこのテンプレートを定義します。

まず最初にこのテンプレートを使用し、次にテンプレート定義の記述方法を説明します。

作成済テンプレートの使用方法

uiXMLファイル内のテンプレートを使用するのは簡単です。最初に、テンプレートをロードするためのタグをuiXMLファイルに追加する必要があります。次に、コンテンツでテンプレートを参照します。

テンプレートは、UIX Controllerの<page>要素の<templates>という新規のセクションでロードされます。<page>のほとんどの下位要素とは異なり、この要素はUIX Componentsネームスペースに属している必要があります。

 <page xmlns="http://xmlns.oracle.com/uix/controller">
    <templates xmlns="http://xmlns.oracle.com/uix/ui">
       ...
    </templates>
 </page>

この要素は、<head>または<content>より前に出現する必要があります。つまり、テンプレートは定義してから使用します。<templates>要素には、一連の<templateImport>要素が含まれており、その各要素によりテンプレート定義ファイルが検索されます。

 <page xmlns="http://xmlns.oracle.com/uix/controller">
    <templates xmlns="http://xmlns.oracle.com/uix/ui">
      <templateImport source="demoPageLayout.uit"/>
      <templateImport source="demoSomethingElse.uit"/>
    </templates>
 </page>

ここでは、テンプレート定義ファイルを通常のファイルと区別する規則として .uitという拡張子を使用しています。テンプレート定義ファイルを直接表示することはできません。<templateDefinition>要素を使用してテンプレートをインラインで定義することも可能ですが、これについては後述します。(テンプレートをロードするUIX Componentsのみの構文は、まだありません。その構文が必要になるかどうかは未定です。ただし、テンプレート定義に基づいてJavaBeansクラスを作成するツールは提供する予定です。)

各テンプレート定義ファイルでは、1つの要素を定義します。UIXの他の要素と同様、この要素も特定のネームスペースに属す必要があります。テンプレートにより定義される要素は、UIXの他の要素とネームスペースを共有できません。この例では、demoというネームスペースを使用します。

   xmlns:demoTmps="http://www.example.org/demo/templates"

次に、このネームスペースで、<demoPageLayout>という新規の要素(demoPageLayout.uitファイルで正しく定義されているものと想定)を使用します。

 <page xmlns="http://xmlns.oracle.com/uix/controller"
           xmlns:demoTmps="http://www.example.org/demo/templates">
    <templates xmlns="http://xmlns.oracle.com/uix/ui">
      <templateImport source="demoPageLayout.uit"/>
    </templates>

    <content>
      <demoTmps:demoPageLayout xmlns="http://xmlns.oracle.com/uix/ui"
                                   selectedTab="3">
        <start>
          <sideNav></sideNav>
        </start>

        <demoTmps:topHead>
          <header text="A first header">

          </header>
        </demoTmps:topHead>

        <contents>
          <header text="A second header">

          </header>
        </contents>
      </demoTmps:demoPageLayout>
    </content>
 </page>

このサンプル・コードの内容を確認します。ネームスペースに工夫があります。

  1. まず最初の行で、テンプレートのネームスペースを定義しています。これは、テンプレート定義で使用されるネームスペースと正確に一致している必要があります。
  2. 次に、<templateImport>要素を使用してテンプレートをインポートします。これは新規の<templates>要素の内部にあることに注意してください。
  3. 次に、コンテンツで<demoPageLayout>要素を使用します。この要素はdemoTmpsというネームスペースに属していますが、デフォルトのネームスペースはUIX Componentsに切り替えてあることに注意してください。
  4. selectedTab属性も定義します。これは、テンプレートのどのタブが選択されたかを指定する属性です。
  5. 次は<start>要素です。<demoPageLayout>の定義を記述する際、<pageLayout>要素に機能を追加します。そのため、<pageLayout>のすべての属性と要素がサポートされます。<start>はUIX Componentsネームスペースに属している<pageLayout>から派生しているため、この要素もUIX Componentsネームスペースに属す必要があります。
  6. 次は<demoTmps:topHead>要素です。これは、<pageLayout>のコンテンツの最初の要素として定義する新しい要素です。これは<pageLayout>に追加される要素なので、demoTmpsネームスペースに属します。
  7. 最後は<contents>要素ですが、この要素の使用方法は変更されていません。

新規のネームスペースに属すという点を除けば、この要素はUIXの他の要素と多くの点で共通しています。次に、テンプレートの定義方法を説明します。

テンプレート定義の記述方法

まず、完全なテンプレート定義ファイル(demoPageLayout.uit)を記述します。その後で、各セクションの内容を確認します。

例: 

<?xml version="1.0" encoding="UTF-8"?>
<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
          xmlns:ui="http://xmlns.oracle.com/uix/ui"
          xmlns:data="http://xmlns.oracle.com/uix/ui"
          targetNamespace="http://www.example.org/demo/templates"
          localName="demoPageLayout">

 <!-- define the template's type information -->
 <type base="ui:pageLayout">
   <namedChild name="topHead"/>
   <attribute name="selectedTab" javaType="int"/>
 </type>

 <!-- define the content of the page -->
 <content>
   <pageLayout>
     <!-- magic incantation to be explained below -->
     <attributeMap><rootAttributeMap/></attributeMap>

     <!-- another magic incantation -->
     <childMap><rootChildMap/></childMap>

     <!-- set up a default set of tabs -->
     <tabs>
       <tabBar data:selectedIndex="selectedTab@ui:rootAttr">
         <contents>
           <link text="Tab 1"/>
           <link text="Tab 2"/>
           <link text="Tab 3"/>
           <link text="Tab 4"/>
           <link text="Tab 5"/>
         </contents>
       </tabBar>
     </tabs>


     <!-- set up a default set of global buttons -->
     <globalButtons>
       <globalButtonBar><!-- ... --></globalButtonBar>
     </globalButtons>

     <!-- now, the content of the pageLayout -->
     <contents>
       <!-- Start with the "topHead" child at the top -->
       <rootChild name="topHead"/>
       <!-- Then add the indexed children -->
       <rootChild name="contents"/>
     </contents>
   </pageLayout>
 </content>
</templateDefinition>

<templateDefinition>要素

上のサンプル・コードで定義した<templateDefinition>要素は、次のとおりです。

<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
          xmlns:ui="http://xmlns.oracle.com/uix/ui"
          xmlns:data="http://xmlns.oracle.com/uix/ui"
          targetNamespace="http://www.example.org/demo/templates"
          localName="demoPageLayout">

テンプレート定義の他の要素と同様、<templateDefinition>要素もUIX Componentsネームスペースに属します。また、他に2つのネームスペースも設定しています。1つは明示的にUIX Componentsを参照するためのもの、もう1つはデータ・バインドのためのものです。

ここで定義する要素の名前を定義するために、targetNamespaceおよびlocalNameという2つの新規属性も使用しています。この2つの属性により、http://www.example.org/demo/templatesというネームスペースにおいて<demoPageLayout>要素を定義していることを宣言します。

<type>セクション

<type>セクションは、テンプレート要素の構文(つまり、開発者が情報をテンプレートに渡す方法)の定義に使用されます。上の例では、次のようになっています。


 <!-- define the template's type information -->
 <type base="ui:pageLayout">
   <namedChild name="topHead"/>
   <attribute name="selectedTab" javaType="int"/>
 </type>

ここでも、サンプル・コードを1行ずつ確認します。

  1. <type base="ui:pageLayout">
    <type>要素は、base属性を持つことができます。この属性で、拡張する要素の型を指定します。ui:pageLayoutを指定することによって、<demoPageLayout>要素では、通常の<pageLayout>要素でサポートされるすべての機能以外にここで追加する機能がサポートされますが、オーバーライドする機能はサポートされません。

    base属性を省略した場合、テンプレート要素はデフォルトのUIX Components属性をすべてサポートします。

  2. <namedChild name="topHead"/>
    次に、topHeadという名前で1つの名前の付けられた子を定義しています。これは、<demoPageLayout><topHead>という名前の子要素をサポートし、その子要素が1つのUIコンテンツを含むことを意味しています。UIコンテンツをプラグインする方法は、後述します。テンプレートでは、0個の場合を含め任意の数の<namedChild>要素を使用できます。
  3. <attribute name="selectedTab" javaType="int"/>
    最後に、selectedTabという属性を定義しています。base属性より上の属性は、すべてここでリスト表示されます。

    javaType属性は必須であり、属性の型を指定します。省略しないJavaクラス名を使用できますが、次の省略名もサポートしています。

    UIXでは、これらの型に対するビルトイン解析をサポートしているだけでなく、次の複合型(意味は後述)もサポートしています。

    他のJava型も許容されていますが、データ・バインドと組み合せてのみ使用できます。

型の情報として、標準的な形式、特にXML Schemaを使用せず、カスタムの形式を使用していることには理由があります。標準的な形式の使用も検討しましたが、それには3つの問題点があります。第1の問題点は、解析の負担が比較的高く、短時間でロードする必要があるテンプレート・ファイルに適していません。第2の問題点は、XML Schemaの表現の自由度が高すぎるということです。XML Schemaでは、要素の構文について膨大な種類の定義が可能ですが、テンプレート定義で使用できるのは、それよりはるかに少ない構文セットのみです。第3の問題点は、XML SchemaにはJava型へのマッピングのビルトイン・サポートがないということです。ただし、これらの問題点が理由で、テンプレート定義の<type>要素からXML Schema文書を生成できないわけではありません。この機能は追加される予定です。

コンテンツの定義

<template>ページのコンテンツは、<content>要素内に記述します。


 <!-- define the content of the page -->
 <content>
   <pageLayout>
       ...
   </pageLayout>
 </content>

このコンテンツにより、このテンプレートの外観が定義されます。ここでは、ページ・レイアウトのレンダリングを<pageLayout>要素で開始しています。サンプルでは<pageLayout>の拡張を宣言していますが、拡張した要素がコンテンツに実際に出現する必要はないため、これは当然のことです。<type>情報に指定されるのは、開発者がテンプレートに情報を渡す方法のみであり、その情報の使用方法は、作成するテンプレートにより完全に制御されます。

ここで、Javaコーディングの際にテンプレートを理解しやすくなる細かい点を指摘します。このテンプレートをページで使用する場合、その各ページに<pageLayout>要素が含まれ、UIX Componentsを使用したページの内容を確認すると、最終的にページ・レイアウトのUINodeが見つかると考えられます。しかし、テンプレートはそのように機能するわけではありません。ページに追加されるUINodeは、<demoPageLayout>のみです。テンプレートのコンテンツがどのくらい複雑かは問題ではなく、そのテンプレートを使用するページからは、1つのUINodeのみが作成されたように見えます。そのノードが出力を要求された場合、複雑な、ただし共有されたUINodeツリーがプライベートに使用されます。これらの処理の詳細は、複雑すぎるためここでは説明できません。テンプレートを使用する際にこのコードを理解する必要はありませんが、興味がある場合はoracle.cabo.ui.compositeパッケージを参照してください。

この方法による影響を1つ挙げます。<dataScope>要素は、データ・プロバイダを定義するテンプレート内で自由に使用できます。テンプレートを使用するファイルに対する影響はありません。この特性のため、悪影響や名前の競合を考慮する必要はありません。

属性へのアクセス

<demoPageLayout>要素で設定された(内部に追加された)コンテンツを使用します。まず、属性について考えます。属性は個々に取得することも、グループとしてまとめて取得することもできます。

最初に、属性をまとめて取得する方法を説明します。

   <pageLayout>
     <attributeMap><rootAttributeMap/></attributeMap>
      ...

<attributeMap>で始まる行では、<demoPageLayout>で定義されたすべての属性を取得し、それを<pageLayout>に追加することを宣言します。したがって、たとえば開発者が次のように記述したとします。

 <demoTmps:demoPageLayout rendered="true"
                              title="Some title"> ...

これら2つの属性は、いずれも<pageLayout>で暗黙的に設定されます。テンプレート内部の<pageLayout>で直接指定したものは、すべて親属性をオーバーライドします。たとえば、クイック・リンクを常にオフにする場合は、次のようにします。

   <pageLayout quickLinksShown="false">
     <attributeMap><rootAttributeMap/></attributeMap>
      ...

これで、<demoPageLayout>の設定にかかわらずクイック・リンクはオフになります。

属性は、1回に1つずつアクセスすることもできます。この特性により、テンプレートではデータ・バインドのための特別なDataObjectが使用できます。このDataObjectはrootAttrと呼ばれ、UIX Componentsネームスペースに属します。

     <tabs>
       <tabBar data:selectedIndex="selectedTab@ui:rootAttr">
         <contents>
            ...
         </contents>
       </tabBar>
     </tabs> 

ここでは、<tabBar>のselectedIndexの後に、<demoPageLayout>のselectedTab属性が続くことを宣言しています。

また、ここで<rootAttributeMap>を使用することも考えられます。その場合、selectedTab属性と、所定のすべての属性が<pageLayout>に追加されます。ただし、uiXML要素では、解釈できない属性は無視されます。そのため、属性名を再利用した際に問題が発生しても、このインスタンスは安全です。

名前の付けられた子へのアクセス

属性と同様、名前の付けられた子もまとめて、または個々に使用できます。最初に、名前の付けられた子をすべて取得するためのコードを示します。

   <pageLayout>
      ...
     <childMap><rootChildMap/></childMap>
      ...

<childMap>で始まる行では、<demoPageLayout>で定義された名前の付けられた子すべてを取得し、それらを<pageLayout>に追加することを宣言します。したがって、たとえば開発者が次のように記述したとします。

 <demoTmps:demoPageLayout>
   <pageHeader>
     <globalHeader>...</globalHeader>
   </pageHeader>
 </demoTmps:demoPageLayout>

この場合、pageHeaderという名前の付けられた子は、startendおよびcontentFooterなど<pageLayout>でサポートされる他のすべての名前の付けられた子とともに、<pageLayout>に暗黙的にコピーされます。ここでも、テンプレート内部で明示的に定義された子はすべてオーバーライドの機能を持ちます。

<demoTmps:topHead>という名前の付けられた子は、ページ・レイアウトでサポートされていないため、これをレンダリングする場合、<rootChildMap>では不十分です。したがって、サンプル・コードのように、<rootChild>要素を使用して明示的にこれを追加する必要があります。

 <pageLayout>
   ...
   <contents>
     <rootChild name="topHead"/>
      ...
   </contents>

この<rootChild>要素は、topHeadという名前の付けられた子を、<pageLayout>内の最初の(結果的には最上位の)要素として挿入します。この要素は、通常とは異なる方法で使用できます。たとえば、同じ名前の付けられた子を何度再利用しても問題はありません。

索引付けされた子へのアクセス

名前の付けられた子および属性とは異なり、索引付けされた子(すなわち、<contents>内の子)には、グループとしてのみアクセスできます。この場合も<rootChild>要素を使用しますが、そのname属性としてcontentsを指定します。

 <pageLayout>
   <contents>
     <rootChild name="topHead"/>
     <rootChild name="contents"/>
   </contents>
 </pageLayout>

この例では、索引付けされた子をtopHeadという名前の付けられた子のすぐ後ろ(直下)に記述しています。

次の2つの点に注意してください。

  1. <rootChild name="contents"/>は、<contents>内でのみ使用できます。他の要素内に記述することはできません。
  2. <rootChild name="contents"/>は、1つの<contents>につき1回のみ使用できます。テンプレート内にそれを数回追加しても問題ありませんが、各<contents>内に存在できるのは1つのインスタンスのみです。

他のテンプレートのインクルード

テンプレート定義では、他のテンプレートを参照および使用できます。実際、他のテンプレートを改良したテンプレートも存在します。これはUIXの強力なツールです。たとえば、次のような一連のテンプレートを設定することもできます。

このとき、共通ページ・レイアウトに対する変更は、アプリケーションの各ページにすべて伝播されます。これによって、メンテナンス性およびカスタマイズ性が大幅に改善されます。顧客は、1つのファイルを修正するだけで、アプリケーション全体の個々のコンテンツを変更できます。また、ページ開発者への負担も軽くなります。一度テンプレートを作成すれば、開発者に見えるのは通常のUIXのみです。テンプレートを使用するためにはJSPコードの外観と設計を根本的に変更しなければならない、JSPでのテンプレートの実装と、この点を比較してください。

他のテンプレートを参照する方法は、次のとおりです。<templates>要素を<templateDefinition>要素に追加します(これは、テンプレートをuiXMLファイルに追加する方法と類似しています)。

<templateDefinition ...>
 <templates>
   <templateImport source="subtemplate.uit"/>
 </templates>

 <type> ...</type>
 <content> ...</content>
</templateDefinition>

この<templates>要素が、追加されたテンプレートを使用するには、<type>および<content>より前に出現する必要があります。追加されたテンプレートは、<content>要素内で、または<type>要素のbase属性に使用できます。

定義への文書の追加

テンプレートを記述するということは、再利用可能なコンポーネントを他の多くの開発者に提供するということです。しかし再利用可能なコンポーネントも、文書化されていない場合ほとんど再利用されません。文書化にXMLのコメントを使用する単純な方法もありますが、コメントは解析が困難なうえ変換の際に失われることも多く、独自の子要素を持つこともできません。この問題を解決するため、<templateDefinition>では、文書化のみを目的とする複数の追加要素がサポートされています。

まず、各<templateDefinition>ファイルには、<version><author>および<documentation>の各要素を含めることができます。これらの各要素には任意の属性、テキストまたは子要素を含めることができます。コンテンツは、実行時に何の制限も受けません(ただし、XMLには厳密に準拠している必要があります)。

<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
          xmlns:ui="http://xmlns.oracle.com/uix/ui"
          xmlns:data="http://xmlns.oracle.com/uix/ui"
          targetNamespace="http://www.example.org/demo/templates"
          localName="demoPageLayout">

 <author>J. Developer</author>

 <version>2.0</version>

 <documentation>
    This element defines a subtype of &lt;pageLayout&gt;.
    It adds a prebuilt set of tabs and global buttons, and
    provides a new &lt;topHead&gt; child element.
 </documentation>

  ...
</templateDefinition>

また、<documentation>要素を<attribute>および<namedChild>要素の子として追加し、それらのエントリを直接文書化することもできます。

 <type base="ui:pageLayout">
   <namedChild name="topHead">
     <documentation>
        will be added above the page's contents
     </documentation>
   </namedChild>
   <attribute name="selectedTab" javaType="int">
     <documentation>
        the index of the selected tab
     </documentation>
   </attribute>
 </type>

複雑な属性

UIXにおける属性という用語が、混乱の原因となる可能性があります。テンプレートの属性を説明する場合は、技術的にはXMLの属性ではなくUIX ComponentsのUINodeの属性を意味しています。string、integer、characterおよびBooleanなどの単純な型の場合、2つの用語の意味は同一です。しかし、DataObjectDataObjectListなどの複雑なJava型の場合、XMLの単純な属性では、すべての型を表すには不十分です。そのため、これらの型に対しては属性のかわりに子要素を使用します。たとえば、次のテンプレート定義を考えます。

<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
          xmlns:ui="http://xmlns.oracle.com/uix/ui"
          xmlns:data="http://xmlns.oracle.com/uix/ui"
          targetNamespace="http://www.example.org/demo/templates"
          localName="someWidget">

 <type>
   <attribute name="someData"
                  javaType="oracle.cabo.ui.data.DataObject"/>
 </type>

 <content>
     ...
 </content>
</templateDefinition>

someData属性は、実際には次のような形でUIXで記述されます。

 <demoTmps:someWidget>
   <demoTmps:someData key1="value1" key2="value2"/>
 </demoTmps:someWidget>

(UIXでDataObjectおよびDataObjectListを定義する構文の詳細は、「データ・バインド」のトピックを参照してください。)

さらに混乱の原因となるのは、複雑な属性もデータ・バインドが可能だということです。固定値に設定する場合は要素を使用した指定が必要であるにもかかわらず、これらは通常の属性と同じようにデータ・バインドされています。

 <demoTmps:someWidget data:someData="key@aDataObject"/>

テンプレート内でのJavaコードの使用方法

テンプレートUIの一部が、UIXでは表現できない場合もあります。原因となる汎用的な機能が欠如している可能性もあるため、そのような場合はオラクル社のカスタマ・サポート・センターにお問い合せください。しかし、UIX ComponentsのDataProviderおよびUIXの<dataScope>要素を使用して、テンプレート内にJavaコードを統合することもできます。

テンプレート内の<dataScope>要素は、自動的に有効範囲を設定されます。任意の<dataScope>要素をすべて追加できますが、そのテンプレートを使用する開発者のUIXには影響しないようにしてください。たとえば、<data>要素のname属性は、一意である必要はありません。

これらのDataProviderを支えるJavaコードは、他のDataProviderと同様であり、DataObjectを作成する通常のテクニックをすべて使用できます。ただし、テンプレートの要素から属性を取得する場合、注意する点が1つあります。たとえば、disabled属性を持つfooというテンプレート要素を定義したとします。

<?xml version="1.0" encoding="UTF-8"?>
<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
          xmlns:data="http://xmlns.oracle.com/uix/ui"
          targetNamespace="http://www.example.org/demo/templates"
          localName="foo">

 <type>
   <attribute name="disabled" javaType="boolean"/>
 </type>

 <content>
   <dataScope>
     <provider>
       <data name="someData">
         <method class="..." method="getTemplateData"/>
       </data>
     </provider>
     <contents>
       ...
     </contents>
   </dataScope>
 </content>

この例ではJavaコード内でdisabled属性にアクセスを試行します。そのためには、これまでに使用したことのないgetParentContext()およびgetAncestorNode()という2つのRenderingContextメソッドが必要になります。

  static public DataObject getTemplateData(
    RenderingContext context,
    String           namespace,
    String           localName)
  {
    // Get the parent rendering context
    RenderingContext parentContext =
      context.getParentContext();

    // Get the template UINode
    UINode templateNode =
      parentContext.getAncestorNode(0);

    // Get the value of DISABLED - note that we use
    // the parent context
    Object disabledObj =
      templateNode.getAttributeValue(parentContext,
                                     DISABLED_ATTR);
    boolean disabled = Boolean.TRUE.equals(disabledObj);

    DataObject someDataObject = ...;

    return someDataObject;
  }

前述のメソッドが必要な理由は、複雑すぎるため説明を省略します。簡潔に言うと、外部コンポーネントへの干渉を回避するため、テンプレートでは独自のRenderingContextを使用してレンダリングを行います。学習を続行するうえでは、これを詳細に理解する必要はありません。

rendered属性: テンプレート作成上の注意点

rendered属性は、特殊なUIX属性です。他の属性とは異なり、この属性はコンポーネントそのものではなく親によって使用されます。これは、親がその子にレイアウト領域を確保するかどうかを知る必要があるためです。たとえば、<pageLayout>では、広告イメージの配置は、その全部が存在している場合と、1つしか存在していない場合とでは大きく異なります。広告の配置を行うのは<pageLayout>自体なので、<pageLayout>はそれらの広告イメージをすべてレンダリングするかどうかを知る必要があります。

つまり、renderedは、テンプレート定義自体に内部的に設定するのではなく、テンプレートのインスタンス化時に設定する必要があるということです。

たとえば、場合によって使用不可になったり使用可能になったりする電車を含むページ・レイアウト・テンプレートを作成するとします。その場合、次のようなテンプレートを記述します。

<templateDefinition ...>
  ...
 <content>
   <train data:rendered="doWeNeedATrain@aPrivateDataSource">
     <contents>
         ...
     </contents>
   </train>
 </content>
  ...
</templateDefinition>

次に、このテンプレートを<pageLayout>内で使用します。

 <pageLayout>
   <location>
     <myNS:myTrain/>
   </location>

    ...
 </pageLayout>

これは正しいように見えますが、実際には機能しません。<pageLayout>は、コンテンツをレンダリングするかどうかではなく、<myNS:myTrain>そのものをレンダリングするかどうかを尋ねてきます。したがって、正しくは次のように記述します。

 <pageLayout>
   <location>
     <myNS:myTrain data:rendered="doWeNeedATrain@aNotSoPrivateDataSource"/>
   </location>

    ...
 </pageLayout>

この例では、このロジックを<pageLayout>テンプレート内に組み込むことで抽象性を保持できます。

<templateDefinition ...>
  ...
 <content>
   <pageLayout>
     <location>
       <myNS:myTrain data:rendered="doWeNeedATrain@aPrivateDataSource"/>
     </location>>
      ...
   </pageLayout>
 </content>
  ...
</templateDefinition>

テンプレートとデータ・バインド

テンプレートとデータ・バインドの相互作用は微妙であるため、テンプレートを記述する際にはルールを意識する必要があります。

テンプレート機能は、テンプレート・ベースの要素を使用する開発者および設計者が、テンプレートを使用してその要素が実装されていることを意識しないようにすることを主な目的としています。つまり、テンプレートに影響がないようにするということです。したがって、テンプレート内で使用される<dataScope>は、テンプレート外からは見えません。理解しやすいよう、次に例を示します。最初がuiXMLファイル、次がそれにより使用されるテンプレートです。

<page xmlns="http://xmlns.oracle.com/uix/controller"
      xmlns:data="http://xmlns.oracle.com/uix/ui"
      xmlns:demoTmps="http://www.example.org/demo/templates">

 <templates xmlns="http://xmlns.oracle.com/uix/ui">
   <templateImport source="templateData.uit"/>
 </templates>

  <content>
    <dataScope xmlns="http://xmlns.oracle.com/uix/ui">
      <provider>
        <data name="commonName">
          <inline value="Outer text"/>
        </data>
        <data name="rareName">
          <inline value="Rare text"/>
        </data>
      </provider>
      <contents>
        <demoTmps:styledText data:text="value@commonName"/>
      </contents>
    </dataScope>
  </content>
</page>
<?xml version="1.0" encoding="UTF-8"?>
<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
          xmlns:ui="http://xmlns.oracle.com/uix/ui"
          xmlns:data="http://xmlns.oracle.com/uix/ui"
          targetNamespace="http://www.example.org/demo/templates"
          localName="styledText">

 <!-- define the template's type information -->
 <type base="ui:styledText">
 </type>

 <!-- define the content of the page -->
 <content>
   <dataScope>
     <provider>
       <data name="commonName">
         <inline value="Inner text"/>
       </data>
     </provider>
     <contents>
       <styledText data:text="value@commonName"/>
       <styledText>
         <attributeMap><rootAttributeMap/></attributeMap>
       </styledText>
       <styledText data:text="value@rareName"/>
     </contents>
   </dataScope>
 </content>
</templateDefinition>

このサンプル・コードを実行すると、次のような出力が表示されます。

Inner textOuter textRare text

実際のコンテンツはすべて内部テンプレート内に出力されるので、このテンプレートのコンテンツを確認していきます。テンプレート内の最初の<styledText>を見てください。

       <styledText data:text="value@commonName"/>

これには内部テキストが含まれているので、<dataScope>commonNameデータを定義しているこの .uitファイルを調べます。しかし、このテンプレートを使用しているuiXMLファイルでも、commonNameが定義されています。第1のルールとして、テンプレート内のデータ・バインドでは、テンプレートで定義されるデータが優先されます。

次に、2番目の<styledText>を確認します。

       <styledText>
         <attributeMap><rootAttributeMap/></attributeMap>
       </styledText>

このほうが、テンプレート行として一般的です。<attributeMap>などで始まる行では、uiXMLファイル内の親要素に定義されたすべての属性を取得し、この<styledText>に追加すると宣言されていました。このため、この<styledText>は、次のようにuiXMLファイル内に定義されている親要素のtext属性を書き出します。

        <demoTmps:styledText data:text="value@commonName"/>

では、なぜこれによって「Outer text」が出力されるのでしょう。テンプレートでは、value@commonNameinnerTextとして定義されています。ここが重要です。<demoTmps:styledText> で定義されているdata:textは、テンプレートの実装方法とは無関係に評価されます。そのため、uiXMLファイルにおけるデータ・バインドでは、テンプレートに定義されている<dataScope>を参照せず、かわりに独自の<dataScope>のみを参照します。第2のルールとして、テンプレート外のデータ・バインドでは、テンプレート内で定義されたデータ・バインドが常に無視されます。

確認のために、最後の<styledText>も確認します。

       <styledText data:text="value@rareName"/>

この結果は、「Rare text」です。rareNameデータに対するデータ・バインドは、UIX外にのみ存在します。第3のルールとして、テンプレート内のデータ・バインドは、テンプレート外で定義されるデータを参照できます。

3つのルールを再度復習します。

  1. テンプレート内のデータ・バインドでは、テンプレートで定義されるデータが優先されます。
  2. テンプレート外のデータ・バインドでは、テンプレート内で定義されたデータ・バインドが常に無視されます。
  3. テンプレート内のデータ・バインドは、テンプレート外で定義されるデータを参照できます。

テンプレートのグループ化

テンプレートの完全なセットを作成した後で、それらすべてを各ファイルに含め<templateImport>要素の長いリストをメンテナンスするのは煩雑な作業です。UIXでは、テンプレートをグループ化し、それらを1回の<templateImport>でインクルードできる<templateLibrary>要素をサポートしています。

構文は単純です。もう1つの .uitファイルで、1つの<templateLibrary>要素を定義し、グループ化するすべてのテンプレートを明示的にインポートします。

ファイルlibrary.uit

 <templateLibrary xmlns="http://xmlns.oracle.com/uix/ui">
   <templateImport source="firstTemplate.uit"/>
   <templateImport source="secondTemplate.uit"/>
   <templateImport source="thirdTemplate.uit"/>
 </templateLibrary>

次に、必要な任意の場所でライブラリ・ファイルをインポートします。

 <templates xmlns="http://xmlns.oracle.com/uix/ui">
   <templateImport source="library.uit"/>
 </templates>

テンプレートのライブラリは、Javaでワイド・インポートを使用する場合と類似しています。

  import oracle.cabo.ui.*;

しかし、各UIX要素を使用する際にそのネームスペースを明示的に指定するため、Javaのワイド・インポートとは異なり、曖昧性の問題は発生しません。

テンプレート、UIExtensionおよびJavaベースのUI

XMLを使用してユーザー・インタフェースを構築する場合、このすべてが重要です。しかし、Java APIを使用する場合、これが役に立つとはかぎりません。また、XMLを使用する場合でも、各ファイルでテンプレート・ファイルをインポートする必要があるため、テンプレートはニ次的なものです。これには、UIExtensionインタフェースという単純な解決方法があります。

UIExtensionインタフェースは、単一のオブジェクトで拡張に対するレンダリングおよび解析の動作を1つにまとめることのできる、ごく単純なインタフェースです。oracle.cabo.ui.composite.TemplateUIExtensionおよびoracle.cabo.ui.composite.TemplateLibraryクラスは、拡張として .uitファイルを使用する簡単な方法を提供します。これらはインポートせずにXML内で使用することも、またJavaから使用することもできる優秀なUIXインタフェースです。

テンプレートをUIExtensionに変換するプロセスには、3つのステップがあります。

  1. まず、テンプレートを<templateLibrary>ファイルにグループ化します。
  2. 次に、JavaコードでTemplateLibraryオブジェクトを作成します。たとえば、テンプレートがc:/foo/ディレクトリのlibrary.uitファイルにある場合、次のようになります。
      // Create a "NameResolver", which is used
      // to locate the library and any files it includes
      NameResolver resolver = new DefaultNameResolver(new File("c:\\foo\\"), null);
      // Create the library - see the Javadoc for information on this function
      TemplateLibrary library = new TemplateLibrary(resolver,
                                                    "library.uit",
                                                    null, null, null);
  3. 最後に、TemplateUIExtensionを作成します。
      UIExtension extension = new TemplateUIExtension(library);

UIExtensionは、LookAndFeelManagerまたはParserManagerに登録できます。

  // Register the rendering half of the extension
  LookAndFeelManager.getDefaultLookAndFeelManager().
     registerUIExtension(extension);

  // Register the XML parsing half of the extension
  ParserManager manager = ...;
  extension.registerSelf(manager);

しかし、より簡単なのは、uix-config.xml内で<template-library>要素を使用する方法です。


 <?xml version="1.0" encoding="ISO-8859-1"?>
 <configurations xmlns="http://xmlns.oracle.com/uix/config">
   <application-configuration>
     <ui-extensions>
       <template-library>c:\foo\library.uit</template-library>
       <template-library>aRelativePath/library/library.uit</template-library>
     </ui-extensions>
   </application-configuration>
 </configurations>

uix-config.xmlの詳細は、「構成」のトピックを参照してください。

Beanを使用してJavaのみをプログラミングする場合、重要なのはUIExtensionの半分のレンダリングを登録することのみで、これはLookAndFeelManager.registerUIExtension()へのコールにより処理されます。(つまり、ParserManagerにテンプレートを登録する必要はありません。)TemplateUIExtensionを登録すれば、それらのコンポーネントをJavaから直接利用できます。適切なネームスペースおよび名前を持つBeanを作成するだけで済みます。たとえば、demoPageLayout.uitファイルを登録してある場合、次のJavaコードによりそのコンポーネントへのアクセスを得られます。

  // Create a DemoPageLayout bean
  BaseWebBean bean = new BaseWebBean("http://www.example.org/demo/templates",
                                     "demoPageLayout", null);

  // Set "selectedTab"
  bean.setAttributeValue(AttributeKey.getAttributeKey("selectedTab"),
                         new Integer(1));
  

これでも十分ですが、実際には次のように記述します。

  // Wouldn't this be better?
  DemoPageLayoutBean bean = new DemoPageLayoutBean();
  bean.setSelectedTab(1);

DemoPageLayoutBeanクラスは自分で記述することもできますが、それは負担になります。今後、UIXでは .uitファイルを対応するBeanクラスのためのJavaソース・コードに自動変換するツールが提供される予定です。また、JDeveloper9i のスキーマ駆動によるXMLエディタで、UIXを検証しテンプレートを使用できるように、.uitファイルからXML Schemaの .xsdファイルに変換するツールも提供する予定です。

テンプレートは、UIX Componentsにおける優秀な技術です。テンプレートは、コードを記述せずにUIX Componentsのコンポーネントを作成する簡単な方法と考えることができます。

<include>要素を使用する理由

テンプレートが<include>要素以上の機能を提供しているにもかかわらず、なぜ<include>を使用するのでしょう。それにはいくつかの理由があります。

第1の理由は、<include>は単純だということです。テンプレートの使用方法は難しいため、インクルードされたuiXMLファイルを記述する方がテンプレート・ファイルを記述するよりはるかに簡単です。

第2の理由は、<template>がUIX Componentsのみということです。テンプレートでは、ctrl:eventなどのctrl:属性を使用できますが、イベント・ハンドラは指定できません。(前述したように、UIX2.0.5ではインクルードされるページで指定されたイベント・ハンドラが無視されます。ただしテンプレートはイベント・ハンドラをサポートしないため、この制限は無視できます。)

第3の理由は、テンプレートが不透明な場合があるということです。テンプレート要素は、インクルード・ファイルでの単一のUINodeに類似していることに注意してください。ただし<include>は、UINodesのツリー全体に類似しています。このことがレンダリング上の意味を持ちます。たとえば現時点で、<templateDefinition>内で定義されている<header>要素は、クイック・リンクでは無視されます。

JSP、サーブレットおよび他のHTMLコンテンツのインクルード

UIXでは、UIX以外のコンテンツをマージするためのその他のインクルード・フォームがあります。UIXでは、HTMLを直接インクルードするのみではなく、サーブレットおよびJSPもインクルードできます。

サーブレットおよびJSPのインクルードには、<servletInclude>要素(JavaではServletIncludeBean)を介してアクセス可能)を使用します。これは、2つの属性をサポートしますが、ほとんどの開発者は単に"source"を使用します。これは、JSPの<jsp:include>タグのpage属性に使用される値です。たとえば、JSPで次のように記述したとします。

 <jsp:include page="foo.jsp"/>

次に、UIXで記述します。

 <servletInclude source="foo.jsp"/>

<servletInclude>要素には、サーブレットおよびJSPを含めることができます。(JSPとApache JServを組み合せて使用した場合のみは例外で、含めることができるのはJSPのみです。)ただし、任意のURLを含めることはできません。たとえば、別のサーバーに格納されているHTMLからコンテンツを取得することはできません。これを実現するには、<urlInclude>要素を使用します。

 <urlInclude source="http://www.example.org/somefile.html"/>

UIXでは、必要とされるHTTPリクエストのヘッダー(User-Agentなど)の一部を自動的にコピーし、残りは明示的に、たとえばAccept-Languageなどに設定します。HTMLはそのまま挿入されるため、通常はHTMLファイル全体は挿入しません。通常、ターゲットはHTMLのSnippetのみです。(将来的には、<HTML><HEAD>および<BODY>のタグを自動的にフィルタリングして削除し、相対HREF属性を絶対HREF属性に変換するなどを予定しています。)

まとめ

インクルードおよびテンプレート定義を学ぶことによって、さらにモジュール化され再利用が可能なUIXを作成できるため、開発時の生産性とアプリケーションのカスタマイズ性を向上させることができます。