コンポーネントテンプレート

Tapestryのコンポーネントテンプレートはページまたはコンポーネントのクラスと関連付けられたファイルで、 埋め込みコンポーネント とともにコンポーネントのマークアップを含んでいます。

Tapestry 4とは異なりTapestry 5のコンポーネントテンプレートは well-formedなXML文書 です。 これは全ての開始タグは終了タグと対になっていなければならず、全てのアトリビュートは引用符で囲まれていなければならない、などを意味します。

これらテンプレートのほとんどの部分は標準的な(X)HTMLです; 通常のマークアップに対するTapestryの拡張をTapestry名前空間によって提供します。

テンプレート固有のことについて述べる前に、まずはコンポーネントとそのテンプレートの関係についての詳細を少し述べます。

テンプレートの配置場所

コンポーネントテンプレートはコンポーネントクラスと一緒に配置します。 拡張子は ".tml" (Tapestry Markup Language)で、対応するコンポーネントクラスと同じパッケージに置きます。

Mavenの典型的なディレクトリ構成のもとでは、src/main/java/org/example/myapp/components/MyComponent.java というコンポーネントのJavaクラスがあるとき、これに対応するテンプレートは src/main/resources/org/example/myapp/components/MyComponent.tml です。

同様に、src/main/java/org/example/myapp/pages/MyPage.java というページのJavaクラスに対応するテンプレートは src/main/resources/org/example/myapp/pages/MyPage.tml です。

テンプレートとコンパイルしたクラスを、アプリケーションのWARファイル内の WEB-INF/classes に一緒にパッケージします。

ページ には2番目の場所があります(コンポーネント にはありません): Webアプリケーションコンテキストです。 その場所はページの論理名に基づいていて、前の例では MyPage.tml をWebアプリケーションのルートフォルダに置くことになります。

ある場合には、Tapestryはページの論理名を簡素化します。例えば、org.example.pages.address.CreateAddress というページクラスには "address/Create" という論理名が与えられます(末尾の冗長な "Address" が取り除かれます)。 しかし、これはページがURL内でどのように参照されるかということだけに影響します; テンプレートファイルは、クラスパス上でもWebコンテキスト内でも CreateAddress.tml のままです。

クラスパス内のテンプレートはWebアプリケーションコンテキストのファイルよりも優先されます。

テンプレートのローカライズ

テンプレートのローカライズはコンポーネントメッセージカタログの各ファイルと同じように行います: 有効なロケールをファイル名に挿入します。例えばドイツ語ユーザーには MyPage_de.tml から生成されたコンテンツが表示され、フランス語ユーザーには MyPage_fr.tml から生成さらたコンテンツが表示されます。特定のローカライズが無い場合はデフォルト(MyPage.tml)が使用されます。

テンプレートの継承

テンプレートを持たないコンポーネントがテンプレートを持つコンポーネントを継承している場合、親クラスのテンプレートが子コンポーネントでも使用されます。

これにより、基底クラスを継承したコンポーネントを作る際に基底クラスのテンプレートを複製しなくて済みます。

テンプレートのDoctype

上で述べたように、コンポーネントテンプレートはwell-formedなXML文書です。 これは、(&   < > © といった)HTMLエンティティを使用したい場合は テンプレートにはHTMLまたはXHTMLのdoctypeを使わなければならないということを意味します。 テンプレートに(X)HTML doctypeを使う場合、クライアントに出力される(X)HTMLにそのdoctypeが渡されます。 テンプレートを持つ複数のコンポーネントでページが構成され、その各テンプレートがdoctype宣言を含んでいる場合、 テンプレートパーサが最初に処理したdoctypeだけがクライアントに渡されることに注意してください。

もうひとつ言及しなければならないことがあります。XHTMLのDTDはXMLのDTDとして有効ですが、HTMLのDTDはXMLのDTDとしては有効ではないということです。 これは、XMLパーサに対してHTMLのdoctypeを使うことはできないということです。TapestryはHTMLのDTDを使用しているテンプレートをパースする際に、 内部的にXHTMLのDTDを用いることでこの制限を回避しています。この内部的な読み替えが可能なのは、W3Cによって XHTML1.0は「HTML4の3つのドキュメントタイプをXML1.0によって再定義した」だけのものとされているためです。 心配しないでください -- クライアントに送出されるのは元の HTML4 doctype です。

以下のdoctypeが最も一般的な(X)HTMLのdoctypeです:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

Tapestry名前空間

コンポーネントテンプレートは、テンプレートのルート要素で定義されるTapestry名前空間内に含まれている必要があります。

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
    <head>
        <title>Hello World Page</title>
    </head>
    <body>
        <h1>Hello World</h1>
    </body>
</html>

これは標準的な "t:" プリフィックスを用いて名前空間を定義しています。このページに出てくる例はすべてこの標準プリフィックスを前提としています。

Tapestry要素

Tapestry要素とはTapestry名前空間で定義されている要素のことです。

それ以外の要素はプリフィックス無しでデフォルトの名前空間にある必要があります。

<body>

多くの場合、コンポーネントテンプレートはそのコンテナのテンプレートに統合されるように設計されます。

<body>要素はコンポーネントテンプレート内のどこに(コンテナテンプレートの)ボディーをレンダリングするかを指定するのに用います。

コンポーネントはそのボディーをレンダリングするかどうか、何回レンダリングするかも、制御します。

次の例は、ページのコンテンツを囲む標準的なHTML要素を与えるLayoutコンポーネントです。

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
    <head>
        <title>My Tapestry Application</title>
    </head>
    <body>
        <t:body/>
    </body>
</html>

このコンポーネントを使用するページは次のようになります:

<t:layout xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">

  My Page Specific Content

</t:layout>

このページがレンダリングされるとき、ページのテンプレートとLayoutコンポーネントのテンプレートはマージされて次のようになります:

<html>
  <head>
    <title>My Tapestry Application</title>
  </head>
  <body>
    My Page Specific Content
  </body>
</html>

Tapestry 4ユーザーは<body>要素がRenderBodyコンポーネントに替わるものであると気づくでしょう。

<container>

conteiner要素はテンプレートの一部とみなされないマークアップです。これは、トップレベルで複数のタグを出力するコンポーネントに役立ちます。 例えば次のような、テーブルの行内にいくつかのカラムを出力するコンポーネントです:

<t:container xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
  <td>${label}</td>
  <td>${value}</td>
</t:container>

このコンポーネントは、コンテナのテンプレートの<tr>要素内で使用することのみを想定しています。

<t:container>要素なしではXMLドキュメントとして有効なテンプレートを作る手段がありません。 XMLドキュメントのルート要素はひとつだけでなければならないからです。

<block>

blockはコンポーネントテンプレートの一部を構成するコンテナです。blockはそのままではレンダリングされません; blockの内側にあるコンポーネントやコンテンツは通常のようにはレンダリングはされませんが、 そのblockをインジェクトすることでいつどういう条件でレンダリングされるかを詳細に制御することができます。

blockには匿名のものと(idアトリビュートで指定された)idを持つものがあります。非匿名のblockはコンポーネントにインジェクトすることができます。

idはJava識別子として有効でなければなりません: 文字ではじまり、文字と数字およびアンダースコアのみを含むことができます。

idパラメータはTapestry名前空間内のもの ではありません (block要素 自体 が常にTapestry名前空間内にあるからです)。

<parameter>

<parameter>要素は特殊なblockです。これは埋め込みコンポーネントのボディ内に置かれます。 <parameter>によって定義されたblockはコンポーネントに渡されます。 <parameter>にはnameアトリビュートが必須で、それによりコンポーネントのどのパラメータにバインドされるかが特定されます。

例:

<t:if test="loggedIn">
  Hello, ${userName}!
  <t:parameter name="else">
  Click <a t:type="actionlink" t:id="login">here</a> to log in.
  </t:parameter>
</t:if>

式展開

レンダリング時のもうひとつのオプションに 式展開 があります。 式展開はテンプレートのボディに埋め込まれる特殊な文字列で、その構文はAntビルドツールから取り入れています。

  Welcome, ${userId}!

上の ${userId} が式展開です。この例ではコンポーネントのuserIdプロパティが文字列に変換されて出力に流されます。

式展開はテキスト内と、任意の要素及びコンポーネントの要素のアトリビュート内で使うことができます。例:

  <img src="${request.contextPath}/images/catalog/product_${productId}.png"/>

この例ではコンポーネントクラスにrequestプロパティとproductIdプロパティがあり、<img>要素のsrcアトリビュートを組み立てるためにテンプレート内で使われています。 <img>要素はコンポーネントではありませんがコンポーネント風に使われています。

式展開はパラメータバインディングと同じように行われます。 デフォルトのバインディングプリフィックスは "prop:" (つまり、プロパティ名)ですが、 他のバインディングプリフィックス、特に "message:" は(コンポーネントメッセージカタログのローカライズメッセージにアクセスするのに)役に立ちます。

Tapestry 4ユーザーにとって式展開が簡潔であると気づくでしょう。 Tapestry 4のInsertコンポーネントと<span key="...">ディレクティブの代替となります。

Component要素

テンプレート内の名前空間 t: の要素は埋め込みコンポーネントです。

例:

  You have ${cartItems.size()} items in your cart.
  <t:actionlink t:id="clear">Remove All</t:actionlink>.

要素名 "actionlink" によってコンポーネントタイプ "ActionLink" を指定しています (Tapestryはコンポーネントタイプの大文字小文字を区別しません)。

埋め込みコンポーネントにはふたつのTapestry固有のパラメータがあります:

  • id: (コンテナ内で)一意となるコンポーネントidを指定します
  • mixins: オプションで、コンポーネントへのMixinをカンマ区切りのリストで指定します

これらのアトリビュートは名前空間 t: で指定します(t:id="clear")。

idアトリビュートを省略した場合、Tapestryはその要素に一意のidを割り当てます。

idはJava識別子として有効でなければなりません: 文字ではじまり、文字と数字およびアンダースコアのみを含むことができます。

他のアトリビュートはコンポーネントパラメータにバインドされます。 これらには公式と非公式のパラメータがあります。公式パラメータにはデフォルトのバインディングプリフィックスがあります(たいていは "prop:" です)。 非公式パラメータはリテラルと仮定されます(すなわち、"literal:" バインディングプリフィックスとなります)。

他のアトリビュートでは t: プリフィックスはオプションです。しかし、ビルドプロセスにTapestryテンプレートファイルの検証を組み込んでいるユーザーは ... 検証エラーとなるのを避けるために、DTDやXMLスキーマに定義されていないTapestry固有のアトリビュートをTapestry名前空間に入れる必要があります。

Tapestryのコンポーネント要素の開始タグと終了タグはコンポーネントの ボディ を定義します。 あるコンポーネントがそのボディー内に付加的なコンポーネントを囲んでいるというのはよくあることです。

<t:form>
  <t:errors/>
  <t:label for="userId"/>
  <t:textfield t:id="userId"/>
  <br/>
  <t:table for="password"/>
  <t:passwordfield t:id="password"/>
  <br/>
  <input type="submit" value="Login"/>
</t:form>

いくつかのケースで、コンポーネントには囲いが必要になります; 例えば、Formコンポーネントに囲まれていないフィールドコンポーネントはどれも実行時例外を発生します。

Tapestryコンポーネントはサブパッケージに配置することができます。例えば、org.example.myapp.components.ajax.Dialog というコンポーネントがあるとします。 このコンポーネントの正規の名前は "ajax/dialog" です(ajaxサブフォルダにあるためです)。 この名前には問題があり、<t:ajax/dialog> という要素名はXMLの要素としてとして正当ではありません。 そのため、スラッシュをピリオドに置き換えます: <t:ajax.dialog>

目立たない方法(Invisible Instrumentation)

任意のHTML要素をコンポーネントとしてマークできるという 目立たない方法(Invisible Instrumentation) がTapestry 4で好まれています。 目立たない方法(Invisible Instrumentation)はテンプレートをより簡潔にし、より読みやすくします。

Tapestry 5では、名前空間付きの idまたはtypeアトリビュートを使って任意の要素をコンポーネントとしてマークします。

例:

<p>
    Merry Christmas:
    <span t:type="Count" end="3">
        Ho!
    </span>
</p>

id、type、mixinsアトリビュートはTapestry名前空間内に置かなければなりません。 他のアトリビュートはTapestry名前空間内でもデフォルトの名前空間内でもどちらでも構いませんが、 その要素に定義されていないアトリビュートを使うときはTapestry名前空間が役立ちます。 (訳者注:どう役立つのか具体的に書かれていないが、前節の「検証エラーとなるのを避けるため...」と同様のことを指していると思われます)

テンプレート内でt:typeアトリビュートを使うか、Javaクラス内で Component アノテーションを使いコンポーネントを定義し(そしてテンプレート内の要素でt:idアトリビュートを使い)、コンポーネントの型を指定しなければ なりません

ほとんどの場合、通常の埋め込みコンポーネントと目立たない方法(Invisible Instrumentation)の埋め込みコンポーネントのどちらを選ぶかは、見た目の問題です。 しかし、Loopコンポーネントのようにいくつかの場合は違いがあります。 Loopコンポーネントは、目立たない方法(Invisible Instrumentation)で使用したとき、そのボディーを囲んでタグと非公式パラメータをレンダリングします。

例:

  <table>
    <tr t:type="loop" source="items" value="item" class="prop:rowClass">
      <td>${item.id}</td>
      <td>${item.name}</td>
      <td>${item.quantity}</td>
    </tr>
  </tabel>

この場合、Loopコンポーネントは<tr>要素の "中にマージされます"。itemsリスト内のitemオブジェクト毎に<tr>が出力されます。 各<tr>毎に動的にclassアトリビュートが出力されます。

テンプレート内の空白

Tapestryはテンプレートをパースする際に、余分な空白を取り除きます。 テキストブロック内の空白の繰り返しは単一の空白文字に縮められます。 ふたつのタグ間の空行や空白などのように、空白だけしかないテキストブロックは完全に削除されます。

レンダリングされた出力のソースを見ると、改行の無い1行の長い塊となっているでしょう。

これにより、サーバ側(ページをレンダリングする際の処理が少なくなる)とクライアント側(パースする文字数が少なくなる)双方でいくらか効率が良くなります。 FireBug のようなツールを使えば、綺麗に整形されたHTMLをクライアント側で見ることができます。

まれに、テンプレート内の空白が そこにあることに 重要な意味を持つことがあります。 <pre> (整形済みテキスト) のテキストブロックを生成する場合や、スタイルシートと連携して空白に何らかの効果が与えられる場合などです。

XML標準のアトリビュート xml:space を使って、 空白を圧縮する(xml:space="default")か元のままを維持する(xml:space="preserve")か指定することができます。 このアトリビュートはテンプレートパーサによって削除されます; レンダリングされた出力には現れません。

xml: 名前空間プリフィックスは全てのXMLドキュメントに組み込まれているので、(Tapestry名前空間のような)明示的な宣言は不要です。

例:

  <ul class="navmenu" xml:space="preserve">
    <li t:type="loop" t:source="pages" t:value="var:page">
      <t:pagelink page="var:page">${var:page}</t:pagelink>
    </li>
  </ul>

この例では、<ul> と <li> との間の空白、(レンダリングされた) <li> とその内側の <a> との間の空白が維持されます。 出力は次のようになるでしょう:

  <ul>
    <li>
      <a href="showcart>ShowCart</a>
    </li>
    <li>
      <a href="viewaccount">ViewAccount</a>
    </li>
  </ul>

通常通り空白を圧縮した場合のレンダリング結果は次のようになります:

  <ul><li><a href="showcart">ShowCart</a></li><li><a href="viewaccount">ViewAccount</li></ul>

内側の要素にも xml:space アトリビュートを指定して、どこの空白を維持しどこを圧縮するか微調整することができます。