レンダリングは再帰的な処理です。各コンポーネントが(IRenderインターフェースから継承した)render()メソッドを実装しています。 コンポーネントはテンプレート内に含まれている他のコンポーネントのrender()メソッドを実行します。
「もし目眩がするようなら、JSFのスタックトレースの縁に立つのはやめて下を見てごらん」と Bruce Tate が言いました。 同じことがTapestry 4にもあてはまります。コンポーネントを深くネストしたりループしたりすると、 スタックトレースはとても深くなります。
コンポーネントのレンダリングは、末尾再帰の代わりに ステートマシン と キュー に基づいています。 これはレンダリング処理を小さな断片に分割し、実装とオーバーライドを簡単にします。 心配しないでください。書かなければならないコード量はあっと言わせるほど少ないのです。
コンポーネント個々のレンダリングは多数のフェーズに分割されます。それを以下に示します。
(SetupRender, BeginRender, BeforeRenderBody 等の)オレンジ色のフェーズにはそれぞれ同名のアノテーションがあり、 そのアノテーションをあなたのクラスのひとつまたは複数のメソッドに置くことができます。 このアノテーションでTapestryに指示をすると、そのフェーズの一部としてあなたのメソッドが実行されるようになります。
これらのアノテーションが付けられたメソッドを レンダーフェーズメソッド と呼びます。
あなたのメソッドはvoidまたはbooleanを戻り値にすることができます。 値を返すことによりフェーズをスキップしたり再実行することが可能になります。図中の実線は通常の処理の経路を示しています。 破線は、レンダーフェーズメソッドがtrue(またはvoid)ではなくfalseを返した時に実行される別の経路です。
レンダーフェーズメソッドはパラメータ無し、もしくは MarkupWriter のひとつだけを受け取ることができます。 メソッドの可視性はどれでも好きなものにできますが ... パッケージプライベートにするのが一般的です。 それは、そのメソッドをコンポーネントの 公開 APIとすること無しに、(同じJavaパッケージ内の)ユニットテストコードからは利用できるようにするためです。
これらのメソッドは オプション です。デフォルトの動作は各フェーズ毎に決まっています。
多くのフェーズが、レンダーフェーズにも接続するコンポーネントMixinの使用を想定しています。 いくつかのフェーズは専らMixin用です。
通常、あなたのコードで使用するフェーズは SetupRender, BeginRender, AfterRender, CleanupRender で ... 大抵はこれらのうちのひとつかふたつです。
以下は繰り返し処理を行うコンポーネントのソースで、ふたつの値の間でカウントアップまたはカウントダウンを行い、 ボディを繰り返しレンダリングし、現在のインデックス値をパラメータに格納します。
package org.example.app.components; import org.apache.tapestry5.annotations.Parameter; import org.apache.tapestry5.annotations.AfterRender; import org.apache.tapestry5.annotations.SetupRender; public class Count { @Parameter private int start = 1; @Parameter(required = true) private int end; @Parameter private int value; private boolean increment; @SetupRender void initializeValue() { value = start; increment = start < end; } @AfterRender boolean next() { if (increment) { int newValue = value + 1; if (newValue <= end) { value = newValue; return false; } } else { int newValue = value - 1; if (newValue >= end) { value = newValue; return false; } } return true; } }
next()からfalseが返されると、TapestryはBeginRenderフェーズを再実行し、 コンポーネントのボディを再レンダリングします(このコンポーネントにはテンプレートがありません)。 trueが返されるとCleanupRenderフェーズに進みます。
アノテーションに従ってTapestryがあなたのメソッドに適応していることに注目してください。 メソッドのパラメータにも適応しています; この例ではアノテーションした2つのメソッドからは何も出力しないので、 それらのメソッドにMarkupWriterは必要ありません。
コンポーネントのテンプレートやボディが、しばしば他のコンポーネントを ... たくさんのコンポーネントを含むというのはとても興味深いことです。 それは、多数のコンポーネントが自身のステートマシンを持ち、それらがそれぞれ異なるフェーズにあるということを意味します。
レンダリング毎に一度だけ行う必要があるセットアップをコンポーネントに対して行うことができる場所です。 コンポーネントパラメータを参照し、それらを用いて一時的なインスタンス変数をセットするのに適しています。
タグをレンダリングするコンポーネントはここで開始タグをレンダリングすべきです (終了タグはAfterRenderフェーズ内でレンダリングすべきです)。 コンポーネントのテンプレートやボディがレンダリングされないようにするために、falseを返すこともできます。
テンプレートを持つコンポーネントとそうでないコンポーネントがあります。 テンプレートを持つコンポーネントでテンプレート内に<body>要素がある場合、 BeforeRenderBodyフェーズが実行されます(ボディをレンダリングするかどうかのオプションが与えられます)。
テンプレート内に<body>要素が無い場合はBeforeRenderBodyフェーズは実行されません。
テンプレートを持たないコンポーネントでも、ボディがある場合にはBeforeRenderBodyフェーズが実行されます。
BeginRenderでアノテーションされたメソッドが無い場合、このフェーズの間に特別な出力は行われず、 テンプレート(テンプレートがある場合)またはボディ(テンプレートはなく、しかしボディがある場合)がレンダリングされます。
このフェーズでは、コンポーネントが自身のテンプレートを(テンプレートが出力したマークアップを囲むマークアップを生成して)装飾することを可能にします。 テンプレートをスキップすることもできます。
コンポーネントのボディに関するフェーズです(コンポーネントのボディとは、コンテナのテンプレート内でコンポーネントが占めている部分のことです)。 BeforeRenderBodyフェーズは、コンポーネント自身のボディはスキップするがテンプレート(もしあれば)の残りの部分はレンダリングする、 ということを可能にします。
BeforeRenderBodyでアノテーションされたメソッドが無い場合、デフォルトではボディはレンダリングされます。 これはコンポーネントテンプレート内の<body>要素がある場所で行われ、 またコンポーネントにテンプレートが無い場合(しかし、ボディはある場合)は自動的に行われます。
ボディのレンダリング後に実行されるフェーズです; これはコンポーネントがボディを持っている場合にのみ行われます。
このフェーズはBeginRenderを補うもので、 BeginRenderフェーズ内でレンダリングした開始タグに対応する終了タグをレンダリングするのにしばしば使用されます。 AfterRenderフェーズはCleanupRenderに進むことも(上の例のCountコンポーネントのように)BeginRenderに戻ることもできます。
AfterRenderでアノテーションされたメソッドが無い場合、特別な出力は何も行われず、CleanupRenderフェーズに進みます。
SetupRenderと対になり、後始末を行うことができます。
あなたのメソッドにアノテーションを使うことを望まない場合、メソッドの命名規約に従うことでアノテーションを使わずに済ますことができます。 setupRender(), beginRender() といった具合にメソッド名をアノテーション名と同じにし、最初の文字を小文字にします。 アノテーションによるレンダーフェーズメソッドと同じように、可視性や戻り値、パラメータにはTapestryが適応します。
この方法を使うと、前の例は以下のように書き換えることができます:
package org.example.app.components; import org.apache.tapestry5.annotations.Parameter; public class Count { @Parameter private int start = 1; @Parameter(required = true) private int end; @Parameter private int value; private boolean increment; void setupRender() { value = start; increment = start < end; } boolean afterRender() { if (increment) { int newValue = value + 1; if (newValue <= end) { value = newValue; return false; } } else { int newValue = value - 1; if (newValue >= end) { value = newValue; return false; } } return true; } }
このやり方にはトレードオフがあります: 利点としては、コードがより簡単に短くなります。そしてあるクラスと別のクラスのメソッド名が一貫したものとなるでしょう。 欠点は、メソッド名がとても一般的であるため、場合によってはアノテーションしたメソッドを使用する場合よりもメソッドの内容を表すのに不十分となるかもしれません (initializeValue() や next() の方がメソッドの内容をよく表している考える人もいるでしょう)。
もちろん、あるメソッドはレンダーフェーズメソッドの命名規約に従い、別のメソッドはアノテーションを使用するというように併用することもできます。
レンダーフェーズメソッドから true または false を返す代わりに、コンポーネントを返すことができます。 そのコンポーネントは Component アノテーションでインジェクトしたものか、パラメータとして渡されたものです。
いずれにせよ、レンダーフェーズメソッドから返されたコンポーネントはキューに入り、現在のコンポーネントの 前に レンダリングされます。
レンダーフェーズメソッドから返されるコンポーネントは、アプリケーション内の全く別のページのものでも構いません。
コンポーネントの再帰的なレンダリングはできません。
この手法によりTapestryのページレンダリングは とても 動的になります。
booleanを返す場合はフェーズを中断しますがコンポーネントのインスタンスを返す場合は中断 しません。 複数のメソッドからコンポーネントを返すことができます(それを推奨はしません -- 狂気のさたです)。
同じレンダーフェーズに複数のメソッドをアノテーションすることができます。 これは同じクラス内のメソッドに対しても、別のクラスから継承したメソッドに対してもあてはまります。
コンポーネントがMixinを持っている場合、そのMixinのレンダーフェーズメソッドはコンポーネントのレンダーフェーズメソッドより 前に 実行されます。Mixinがベースクラスから派生している場合、Mixinの親クラスのレンダーフェーズメソッドがサブクラスのメソッドよりも前に実行されます。
Mixin同士の実行順序は今のところ未定義です。
例外: MixinAfter をアノテーションしたMixinクラスは、前ではなく 後に 実行されます。
常に親が先です。親クラスで定義されているメソッドは常に子クラスで定義されているメソッドよりも先に実行されます。
基底クラスのレンダーフェーズメソッドをサブクラスでオーバーライドした場合、そのメソッドは基底クラスの他のメソッドと共に一度だけ実行されます。 サブクラスは、基底クラスのメソッドをオーバーライドすることでその 実装 を変更することはできますが、メソッドが実行される タイミング を変更することはできません。 参照: TAPESTRY-2311
AfterXXX フェーズは BeginXXXフェーズや BeforeXXXフェーズと対になる存在です。 (レンダリング中のコンポーネントにボディやテンプレートがある場合には)たいてい、 BeforeXXX フェーズや BeforeXXX フェーズで開始した要素は対応する AfterXXX で終了します(閉じます)。
適切に、そして自然な順序で処理されることを保証するために、 AfterXXX と CleanupRender のレンダーフェーズメソッドは 逆順 に実行されます:
今のところメソッドはアルファベット順にソートされます。 同じ名前のメソッドはパラメータの数によってソートされます。しかし、これは良い方法ではありません ... ただひとつのメソッドを定義し、そのメソッドからあなたが望む順序で他のメソッドを呼び出してください。
メソッドが true または false を返す場合、そのフェーズの処理を中断します。 通常なら同じフェーズ内で実行されるはずの他のメソッドが実行されなくなります。
予期せぬ中断により同じフェーズ内の他のメソッドが実行されなくなるのを防ぐため、ほとんどのレンダーフェーズメソッドはvoidを返すべきです。