Velocityでテンプレートを継承する方法

VelocityでDjangoのテンプレートのように基底のテンプレートをextendするための方法。

継承ができると何がいいか?
なぜそれほどうらやましいのか?

1. 基本となるレイアウトを1つのファイルで記述し、
  変更が必要な部分のみ別のテンプレートに記述できる。
2. 基本レイアウトの変更可能部分についても、デフォルトの記述が可能。
3. 階層的に複数のレイアウトを継承できる(Aを継承したBを継承したCを・・・)
4. したがって、継承した側のテンプレートでは「本当に変更が必要な部分のみ」
  記述すれば良い。

重要なのは2と3。

デフォルト記述の例:

例えばWebサイトで他の基本的なレイアウトは同じだがメニューだけが違うページが複数あるような場合を考える。
VelocityLayoutServletであれば、メニューだけが違う基本レイアウトテンプレートをそれぞれ用意するか、もしくはマクロなどでメニューを動的に変更できるよう工夫する必要がある。



上記の継承が使えた場合は、よく使われるメニューをデフォルトとして記述した基本レイアウトを1つ用意し、異なるメニューが必要な場合のみ拡張先のテンプレートで記述すればよい。




階層的な継承の例:

上の例であるメニューの中に、さらに特定のレイアウトが共通する複数のページを作る場合を考える。
VelocityLayoutServletであれば、・・・もう考えるのいやだ・・・な・・・となる。
継承が使える場合、前の基本レイアウトを継承した基本レイアウトを作る。簡単!




Velocityでテンプレート継承
いくつか方法はあると思うが、ユーザディレクティブを利用する方法を採用した。
velocity toolsではなく、velocity自体に継承機能を追加するため、どこででも利用できるのが利点。

今回、ユーザディレクティブに追加したのは下記の2つ。

# extends('継承元ファイル')
# block('ブロック名')

手順1:
ディレクティブの実装クラスをダウンロードし、適切な場所に追加する。
velocity-template-extends.zip

手順2:
velocity.propertiesに下記のユーザディレクティブ設定を追加する。

#-------------------------------
# USER DIRECTIVE
#-------------------------------
userdirective=org.apache.velocity.runtime.directive.Extends
userdirective=org.apache.velocity.runtime.directive.ExtendBlock


以上、終了。


使用方法

extendsディレクティブ
extendsはparseディレクティブの変形で、extends〜endまでの部分をパースしてから継承元ファイルをパースするディレクティブ。

A.vm:

#extends('B.vm')
A特有の内容をここに(普通はblockディレクティブ以外を書くことはない)
#end

B.vm:

Bの内容

A.vmのパース結果:

A特有の内容をここに(普通はblockディレクティブ以外を書くことはない)
Bの内容


blockディレクティブ
blockはテンプレートの「部分」を定義するディレクティブで、extendsの範囲外にある場合のみパース結果を書き出す。
同名の「部分」が複数ある場合は、常に最初に定義された「部分」が後から出てきた「部分」を上書きする。
1.vm:

#block('X')
最初に定義したXブロック
#end
#block('X')
次に定義したXブロック
#end

1.vmのパース結果:

最初に定義したXブロック

2.vm:

#extends('3.vm')
#block('X')
最初に定義したXブロック
#end

3.vm:

基本テンプレートではXブロックを定義していない

2.vmのパース結果:

基本テンプレートではXブロックを定義していない


4.vm:

#extends('5.vm')
#block('X')
最初に定義したXブロック
#end

5.vm:

#block('X')
ここでXブロックを定義
#end

5.vmのパース結果:

最初に定義したXブロック

継承の例
basic_layout.vm:

基本レイアウトをここに。
#block('menu')
デフォルトのメニュー
#end
#block('contents')
デフォルトの内容があれば、ここに
#end

pageA.vm

#extends('basic_layout.vm')
#block('contents')
内容だけ変更
#end
#end

pageB.vm

#extends('basic_layout.vm')
#block('contents')
独自の内容
#end
#block('menu')
メニューも変える。後ろで定義しても、最終的にはextendsの外での並び順になる。
#end
#end

pageAのパース結果:

基本レイアウトをここに。
デフォルトのメニュー
内容だけ変更


pageBのパース結果:

基本レイアウトをここに。
メニューも変える。後ろで定義しても、最終的にはextendsの外での並び順になる。
独自の内容