Mojoliciousの「テンプレート」の使い方
テンプレートについて解説したいと思います。今までは、簡単なテキストを出力してきただけでしたが、Webアプリケーションなので、HTMLを出力したいですね。Mojoliciousのテンプレートという機能を使うと、HTMLを出力することができます。
use Mojolicious::Lite; get '/' => sub { my $self = shift; # テンプレートの指定 $self->render('index'); }; get '/company/info' => sub { my $self = shift; # テンプレートの指定 $self->render('company/info'); }; app->start; __DATA__ @@ index.html.ep <html> <head> <title>Index</title> </head> <body> <h1>Index</h1> </body> </html> @@ company/info.html.ep <html> <head> <title>Company Information</title> </head> <body> Company Information </body> </html>
アプリケーションを起動して「/」と「/company/info」にアクセスしてみてください。それぞれ「Index」「Company Informatiomn」が太字で表示されます。
テンプレートの指定方法
まずテンプレートの指定部分についてみてみましょう。テンプレートはrenderメソッドで指定することができます。
# テンプレートの指定 $self->render('index'); $self->render('company/info');
二番目の例のように「/」を使ってディレクトリ階層を表現することも可能です。
テンプレートの記述方法
次にテンプレートの記述方法を見てみましょう。
__DATA__ @@ index.html.ep <html> <head> <title>Index</title> </head> <body> <h1>Index</h1> </body> </html> @@ company/info.html.ep <html> <head> <title>Company Information</title> </head> <body> <h1>Company Information</h1> </body> </html>
テンプレートは
次にテンプレート名は次のような表現で記述します。
@@ テンプレート名.html.ep
「@@」の後ろに半角の空白が必要なことに注意してください。Mojoliciousのテンプレート名の拡張子は、特別なことを行わない限りは「html.ep」になります。
今回のサンプルでは、テンプレートはふたつありますが、それぞれ次のような記述になっています。
@@ index.html.ep @@ company/info.html.ep
そしてテンプレート名の後ろにHTMLを記述します。
@@ index.html.ep <html> <head> <title>Index</title> </head> <body> <h1>Index</h1> </body> </html>
これが表示される内容になります。
テンプレート内にはPerlが書ける
MojoliciousのテンプレートにはPerlを記述することができます。少しだけ記法を覚えてしまえば、特別な文法を覚える必要がないので便利です。
Perlを記述するには先頭に「%」を記述します。
% my $name = 'kimoto';
また複数にわたってPerlを記述するときは「<% %>」という記法を使うことができます。
<% my $title = 'kimoto'; my $age = 19; %>
値の埋め込み
また「%=」あるいは「<%= %>」を使って、直接、値をHTMLに埋め込むことができます。
%= $title; <%= $title %>
Perlの記述を使ったサンプル
では、実際にテンプレート内でPerlを使ったサンプルを書いてみましょう。サンプルの中では、if文やfor文を使っています。
use Mojolicious::Lite; get '/' => sub { my $self = shift; $self->render('index'); }; app->start; __DATA__ @@ index.html.ep <% my $name = 'kimoto'; my $age = 19; my @nums = (1, 2, 3); %> <html> <head> <title>Index</title> </head> <body> <h1>Index</h1> % if ($name eq 'kimoto') { Kimoto % } else { Other % } <br> He is <%= $age %> years old.<br> % for my $num (@nums) { <%= $num %><br> % } </body> </html>
こんな風にテンプレートの中にPerlを記述することができるので便利です。
値は自動的にHTMLエスケープされる
%= 値 <%= 値 %>
上記の記述で埋め込んだ値は、自動的にXMLエスケープされます。XMLエスケープは、HTMLの中で危険な文字列を安全な文字に置き換える処理のことです。以下の変換が行われます。
& => & < => < > => > " => " ' => '
もし「<」などが、置き換えられないとすれば「」のような文字列を、ユーザーから送り込まれた場合に、javascriptが実行される危険性を持つことになります。ですので、Mojoliciousでは、このような危険性を下げるために、デフォルトでXMLエスケープ処理を行うようになっています。
もしXMLエスケープを行いたくない場合は「<%==」あるいは「<%== %>」という記法を使います。
%== 値 <%== 値 %>
データの受け渡しはスタッシュと呼ばれる変数を使う
コントローラーとテンプレートではスタッシュと呼ばれる変数を共有しています。データをテンプレート側に渡したい場合はstashメソッドを使って、値を設定することができます。
# コントローラーでのスタッシュの値の設定 $c->stash('name' => 'Kimoto'); # コントローラーでのスタッシュの値の取得 my $name = $c->stash('name')
またテンプレートの中ではstashヘルパーを使って、値の設定と取得を行うことができます。テンプレートの中で利用できる関数のことをヘルパーと呼びますので、覚えておいてください。
# テンプレートでのスタッシュの値の設定 stash('name' => 'Kimoto'); # テンプレートでのスタッシュの値の取得 my $name = stash('name');
またrenderメソッドの第二引数以降を利用すると、スタッシュの値を設定できるので、便利です。短く書けるので、これを利用しましょう。
# renderメソッド $c->render($template, 'name' => 'Kimoto', 'age' => 19); # これと同じ $c->stash('name' => 'Kimoto'); $c->stash('age' => 19); $c->render($template);
スタッシュの予約語
スタッシュのキーとしては、次のものは予約語になっているので、値を保存したい場合には、使わないでください。もし使えば、予測外の挙動を起こして、正しい処理が行われなくなるので、気をつけてください。
action app cb controller data extends format handler json layout namespace partial path status template text variant
スタッシュを使ったサンプル
スタッシュを使ったサンプルを書いてみます。
use Mojolicious::Lite; # コントローラー get '/' => sub { my $self = shift; # スタッシュに値を設定してindexを描画 $self->render('index', 'name' => 'Kimoto', age => 19); }; app->start; __DATA__ @@ index.html.ep <% # スタッシュから値を取得 my $name = stash('name'); my $age = stash('age'); %> <html> <head> <title>Index</title> </head> <body> <h1><%= $name %>:<%= $age %></h1> </body> </html>
コントローラー内でrenderメソッドを使ってスタッシュを設定しています。
# スタッシュに値を設定してindexを描画 $self->render('index', 'name' => 'Kimoto', age => 19);
テンプレート内でstashヘルパーを使ってスタッシュの値を取得しています。
レイアウト
次にレイアウトという機能を解説します。もう一度最初のサンプルを見てみましょう。
@@ index.html.ep <html> <head> <title>Index</title> </head> <body> <h1>Index</h1> </body> </html> @@ company/info.html.ep <html> <head> <title>Company Information</title> </head> <body> Company Information </body> </html>
htmlタグやbodyタグというのは、二つのテンプレートで共通のものとなっていますね。bodyの中身は異なりますが、骨格となっているhtmlタグやbodyタグは同じです。こういう場合は、レイアウトという機能を使えば、記述の繰り返しを避けることができます。
最初のサンプルをレイアウトを使って書き直してみましょう。
@@ layouts/common.html.ep <html> <head> <title><%= stash('title') %></title> </head> <body> %= content; </body> </html> @@ index.html.ep % layout 'common', title => 'Index'; <h1>Index</h1> @@ company/info.html.ep % layout 'common', title => 'Company Information'; <h1>Company Information</h1>
レイアウト名
まずレイアウトを定義してみましょう。まずテンプレート名を見てください。
layouts/common.html.ep
「layouts/」で始まっています。これはレイアウトを作成するときの規則です。その後ろにレイアウト名「common」が続きます。そして、拡張子は、テンプレートと同じ「.html.ep」です。
レイアウトの内容
次にレイアウトの内容を見てみましょう。
<html> <head> <title><%= stash('title') %></title> </head> <body> %= content; </body> </html>
すべてのテンプレートで共有する部分をレイアウトには記述します。htmlタグとbodyタグを書いています。レイアウトに埋め込む内容は、contentヘルパーで取得することができます。contentヘルパーの部分が、実際の内容に置き換わります。
レイアウトの中でも、stashを利用することができますので、テンプレートからレイアウトにstashを通してデータを渡すことができます。これを利用すれば、タイトルだけを変更するということも簡単です。
レイアウトの呼び出し
次に、実際のテンプレートでレイアウトを呼び出している記述を見てみましょう。
@@ index.html.ep % layout 'common', title => 'Index'; <h1>Index</h1>
layoutヘルパーを使えば、レイアウトを呼び出すことができます。第一引数にはレイアウト名を指定します。第二引数以降の値は、stashの値として設定されます。
実際にこれは以下のようなHTMLに展開されます。
<html> <head> <title>Index</title> </head> <body> <h1>Index</h1> </body> </html>
「<h1>Index</h1>」が、呼び出したレイアウトに埋め込まれていることを確認してください。レイアウトは必須で使う機能になると思います。ぜひ覚えてください。
他のテンプレートをインクルードする
次に他のテンプレートを「インクルード」できる機能を紹介します。「ヘッダ」や「フッタ」などの共通部品を作成して読み込むことができます。
他のテンプレートをインクルードするにはinclude関数を使用します。複数の部分で利用するコンポートネントを共通の部品として定義しておいて読み込むのに利用できます。
インクルードされるテンプレートの記述
インクルードされるテンプレートを記述します。名前はcomponent.html.epとします。
コンポーネント %= $name
$nameという部分に注目してみてください。このようにしておくと、テンプレートをインクルードするときに、一部分だけ内容を変更することができます。
テンプレートのインクルード
テンプレートをインクルードするには、include関数を使用します。
%= include 'component', name => 'Ken';
第一引数はインクルードしたいテンプレート名です。「.html.ep」は省略することができます。第二引数以降はインクルードされるテンプレートに渡したいデータをハッシュで指定することができます。
テンプレートの外部化
Mojolicious::Liteのサンプルでは、テンプレートは、ファイルの中に記述しましたが、「ファイル」として配置することもできます。規模が大きくなってきた場合は、ファイルとして外部に保存すると、見通しがよくなります。
「templates」ディレクトリを作成して、その中に配置すると、Mojoliciousはそれを読み込んでくれます。
templates / index.html.ep / list.html.ep
テンプレートで使うテクニック
テンプレートでよく使うテクニックを紹介します。
テキストをHTMLエスケープから保護する
テキストをHTMLエスケープから保護するには、文字列をMojo::ByteStreamオブジェクトに変換してからテンプレートに渡します。
<%= Mojo::ByteStream->new('<p>test</p>') %>
タグなどをHTMLエスケープしたくない場合は、Mojo::ByteStreamオブジェクトに変換して、テンプレートに渡します。
またbというヘルパーもあり、こちらを使うと短くかけます。
<%= b('<p>test</p>') %>
コントローラーオブジェクトの取得
テンプレートの中でコントローラーオブジェクを取得するには$selfという変数にアクセスします。
% my $controller = $self;
$selfはテンプレートの中で暗黙的に宣言される変数です。これがMojolicious::Controllerオブジェクトになっています。
このオブジェクトからコントローラクラスのメソッドを呼び出すことができます。
# コントローラークラスのメソッドの呼び出し my $req = $self->req;
テンプレートブロックを作成する
テンプレートの中で「テンプレートブロック」を作成することができます。テンプレートブロックは再利用可能なテンプレートの部品として利用することができます。
# Template block <% my $div = begin %> % my ($id, $text) = @_; <div id="<%= $id %>" > %= $text </div> <% end %>
テンプレートブロックは実質的にはサブルーチンのリファレンスですが、HTMLの内容をテンプレートにおけるのと同じように記述できるのがポイントです。テンプレートブロックはbeginで始まりendで終わります。
テンプレートブロックはPerlのサブルーチンのリファレンスとして利用することができます。
<%= $div->('foo', 'Hello') %>
以下はMojolicious::Liteでのサンプルです。
use Mojolicious::Lite; get '/' => 'index'; app->start; __DATA__ @@ index.html.ep <% my $div = begin %> % my ($id, $text) = @_; <div id="<%= $id %>" > %= $text </div> <% end %> <%= $div->('foo', 'Hello') %>
タグの前後の空白を取り除く
Mojoliciousのテンプレートを使っているときに、タグの前後の空白を取り除きたい場合があります。前後の空白を取り除きたい場合は次のような特別なタグを利用します。
<%= 内容 =%>
通常のタグの場合はは「<%= 内容 %>」です。後ろの%の前に=がついているところがポイントです。
テンプレートでは関数の名前空間が共有される
Mojoliciousのテンプレートの中でPerlのコードを自由に書くことができますが、ひとつだけ注意するべきことがあります。それは、すべてのテンプレートで、関数の名前空間が共有されるということです。
# テンプレートA use Encode 'encode';
# テンプレートB encode('UTF-8', $str);
テンプレートBではencode関数をインポートしていないのですが、テンプレートAでインポートしているのでテンプレートBで利用することができます。これはMojoliciousがすべてのテンプレートをひとつの名前空間の中に作成しているからです。
ですので、混乱したくないのであれば、テンプレートの中では関数のインポートを行わないで、完全修飾名で利用するのが安全でしょう。
use Encode (); Encode::encode('UTF-8', $str);
まとめ
MojoliciousのテンプレートはPerlが使えるのがとてもよい! 「if」も「for」もテンプレート用の構文をいちいち覚える必要がないしね。「レイアウト」と「インクルード」の記述を使えば、ヘッダやフッタの記述を共有化できる。コピペしまくらなくっていいから、便利だ。