Ymirではあるページの中に別のページ断片をインクルードして利用することができるようになっています。また、単にページ断片をインクルードするだけでなく、主となるページに関連付けられたPageクラスの処理時にページ断片に関連付けられたPageクラスの処理を併せて呼び出せるようになっています。このため、再利用可能な共通画面部品のようなものを作成することもできます。
以下、テンプレートエンジンとしてZPTエンジンを使う場合のインクルード機能の説明をします。
ページ断片のインクルード
インクルードされるページ断片は、通常のページと同様に作成することができます。ただし、残念ながら現在のところ自動生成機構による作成支援はありませんので、手動でPageクラスとZPTテンプレートを用意する必要があります。
例えばヘッダとして/header.htmlというZPTテンプレートを作成し、次のようにinclude式を使ってページの中にヘッダを挿入することができます。
<div tal:replace="structure include:/header.html">BODY</div>
include式のパラメータはインクルードするコンテンツのコンテキスト相対パスです。指定されたパスに対応するコンテンツのレンダリング結果がinclude式の評価結果となります。
デザインをWebブラウザで確認する都合上、header.htmlをHTML断片ではなく完全なHTML形式で書いておき、インクルードする時にはbodyタグの中身だけをインクルードしたいことがあります。その場合は次のようにinclude-body式を使うようにして下さい。
<div tal:replace="structure include-body:/header.html">BODY</div>
レンダリング時の「self」の内容
ページ断片に関連づけられたPageオブジェクトが存在する場合、インクルードされたページ断片のレンダリング処理を行なう間だけ、そのPageオブジェクトがrequestスコープに「self」という名前でバインドされます(インクルードされたページ断片のレンダリングが終了した時点でselfの内容は元に戻されます)。
ページ断片に関連付けられたPageオブジェクトが存在しない場合はYmirは何の処理も行ないません。selfもインクルード元のPageオブジェクトがバインドされた状態のままになります。
Pageクラスのマッピング
ページ断片には通常のページと同様にPageオブジェクトを関連付けることができます。Pageオブジェクトを関連付けたい場合は、ページ断片のパスから規約に従って導き出されるクラス名でクラスを作成しておくだけで良いです。例えば/header.htmlというパスから導き出されるクラス名がcom.example.web.HeaderPageだとすれば、このクラスを作成しておけばページ断片の処理時にこのクラスのオブジェクトが準備されて関連付けられます。
Pageオブジェクトの準備
JSFなどのフレームワークと違い、Ymirでは基本的にビュー部には関与せず、レンダリング処理はテンプレートエンジンに任せてしまうため、ページ内にどのページ断片がインクルードされているかをレンダリング前に知ることができません。
一方で、ページ内にインクルードされているページ断片に関連付けられているPageオブジェクトの準備は、主となるページに関連付けられているPageオブジェクトの準備と同じタイミングで一括して行なった方が都合が良いことが多いため、Ymirでは以下のようにアノテーションを使ってインクルード情報をPageクラスに指示するようにして、その情報を使ってレンダリング前に一括して全てのPageオブジェクトの準備を行なうようになっています。
@Include({ HeaderPage.class, FooterPage.class }) public class MainPage { ... }
具体的には、主となるページに関連付けられているPageオブジェクトを準備するタイミングでアノテーションを辿ってPageオブジェクトツリーを内部に構築し、ツリーを深さ優先で辿っていって各Pageオブジェクトについてリクエストパラメータやオブジェクトスコープからのオブジェクトの設定などの準備処理を行なっていきます。上記の例ではMainPageが親でHeaderPageとFooterPageが子であるようなツリーが構築され、MainPage→HeaderPage→FooterPageの順に処理が行なわれます。
準備処理は、主となるPageオブジェクトを処理する際に一括して行なわれます。例えば上記の例では、次のように処理が行なわれます。
- ...
- MainPageへの@Inアノテーションの処理
- HeaderPageへの@Inアノテーションの処理
- FooterPageへの@Inアノテーションの処理
- ...
- アクションメソッドの呼び出し(後述)
- ...
- MainPage#_prerender()の呼び出し
- HeaderPage#_prerender()の呼び出し
- FooterPage#_prerender()の呼び出し
- ...
インクルードされるPageオブジェクトには、主となるPageオブジェクトに設定されるものと同じリクエストパラメータやオブジェクトスコープのオブジェクトが設定されます。このためテンプレート中のインクルード指定で例えば以下のようにパスにリクエストパラメータを付与したとしても、インクルードされるPageコンポーネントにはそのパラメータは設定されません。
<div tal:replace="structure include:/header.html?param=value">BODY</div>
なお、ページ内にインクルードするページ断片にPageクラスが関連付けられている場合は忘れずにアノテーションでページ断片のPageクラスを指示するようにして下さい。そうしないとPageオブジェクトの準備が行なわれません。
アクションメソッドの呼び出し
リクエストに対応するアクションメソッドの探索はPageオブジェクトツリーのそれぞれのPageオブジェクトについて深さ優先で行なわれ、一番最初に見つかったアクションメソッドが実行されます。
アクションメソッドの種類には、
- ボタンに対応するアクションメソッド(例:_post_search)
- ボタンに対応するデフォルトアクションメソッド(例:_default_search)
- 通常のアクションメソッド(例:_post)
- デフォルトアクションメソッド(例:_default)
の4種類がありますが、探索はこれらの種類ごとに行なわれます。従って、例えば親Pageクラスに_postがあり、子Pageクラスに_post_searchがある場合は、子Pageクラスの_post_searchが実行されることになります。
なお、_validationFailedメソッドや_permissionDeniedメソッドの探索も同様に行なわれ、一番最初に見つかったメソッドが実行されます。
_prerenderメソッドは全てのPageオブジェクトについて呼び出されます。
注意事項
主となるページと同じディレクトリにページ断片を配置している場合、ページ断片を表すURLに直接アクセスされるとページ断片が表示されてしまいます。これを防ぐためには、org.seasar.ymir.impl.DeniedYmirPathMappingを使用します。
例えば「/included」ディレクトリ以下にページ断片を置く場合は、
<component class="org.seasar.ymir.impl.DeniedYmirPathMapping"> <arg>"/included/.*"</arg> </component>
のように指定することでページ断片への直接アクセスを禁止することができます。