Mojoliciousテンプレートのヘルパーは自作サブルーチンを書いて積極的に使うべき
MojoliciousのテンプレートエンジンのEP(Embedded Perl)は、HTMLの中にPerlの制御構文や出力を埋め込めるタイプです。
僕は今までHTML::Templateというテンプレート内では表現をほとんど弄れないものを使っていたので、MVCモデルでいうところのController部分でテンプレート差し込み用の文字列の表現を作っていましたが、EPでは、それをView側に持って来れるということです。
例えば、ディレクトリリストを表示するとして、ファイルのサイズをbyte〜KB〜MB〜GBと様々な単位で表現したい時、
- HTML::Templateでは、テンプレート側では数値や単位を変えられないので、テンプレートに渡す前に数値と単位を変えてあげる
- EPでは、テンプレート側で数値や単位を変えるロジックを組めるので、テンプレートに渡す数値は意識することがない
という違いになると思います。
とはいえ、テンプレート内だけでロジックを組もうとすると、どつぼにはまるので、ロジックを組み込みするための補助としてヘルパーを使うことを提案します。
ヘルパーは、「誰かが便利なヘルパープラグインつくってくれないかなー」という待ちの姿勢ではなくて、「テンプレート内で使う俺様定義のサブルーチン集」として積極的に使うべきだというお話。
例えば、ディレクトリリスト表示Mojolicious::Liteアプリ
設置したディレクトリの、ディレクトリリストをtable要素の表組で表示するMojolicious::Liteアプリ。
これは、まだ時刻がUnix epoch表記だし、ファイルサイズがByte表記なので、人間が読みやすい形式にしたいなーと思っている、という想定です。
#!/usr/bin/perl use 5.010; # Mojolicious 1.9.8以降はPerl 5.10対応 use Mojolicious::Lite; use Mojo::Util qw(decode); use File::Basename; use File::Spec; my $file_dir = app->home->rel_dir('./'); get '/' => sub { my $self = shift; my @filelist = glob( File::Spec->catfile( $file_dir, '*' ) ); my $files = []; for my $filename ( @filelist ) { my @stat = stat $filename; push @$files, { filename => decode('utf8', File::Basename::basename($filename) ), size => $stat[7], mtime => $stat[9], }; } $self->stash( files => $files ); } => 'index'; app->start; __DATA__ @@ index.html.ep <!DOCTYPE html> <style> table { border-collapse: collapse; } th, td { border: 1px solid; padding: 6px; } </style> <table> <thead><tr><th>filename</th><th>size</th><th>datetime</th></tr></thead> <tbody> % for my $item (@$files) { <tr> <td> %= $item->{filename} </td> <td title="<%= $item->{size} %>"> %= $item->{size} </td> <td> <time datetime="<%= $item->{mtime} %>"> %= $item->{mtime} </time> </td> </tr> % } </tbody> </table>
あまりよろしくない例:テンプレートブロックで表記
以下は、HTML表記の外側で表示の仕方を変えるのに、テンプレートブロックを使った例。
あまりよろしくないっていうのは、他のテンプレートで再利用しにくいってこと。
#!/usr/bin/perl use 5.010; # Mojolicious 1.9.8以降はPerl 5.10対応 use Mojolicious::Lite; use Mojo::Util qw(decode); use File::Basename; use File::Spec; my $file_dir = app->home->rel_dir('./'); get '/' => sub { my $self = shift; my @filelist = glob( File::Spec->catfile( $file_dir, '*' ) ); my $files = []; for my $filename ( @filelist ) { my @stat = stat $filename; push @$files, { filename => decode('utf8', File::Basename::basename($filename) ), size => $stat[7], mtime => $stat[9], }; } $self->stash( files => $files ); } => 'index'; app->start; __DATA__ @@ index.html.ep % my $humanreadable_filesize = begin % my $size = shift; % my $unit = 'byte'; % if ( $size > 1024 ) { $size /= 1024; $unit = 'KB'; }; % if ( $size > 1024 ) { $size /= 1024; $unit = 'MB'; }; % if ( $size > 1024 ) { $size /= 1024; $unit = 'GB'; }; %= sprintf '%.1f %s', $size, $unit; % end % my $humanreadable_datetime = begin % my $mtime = shift; % my ($second, $minute, $hour, $mday, $month, $year) = localtime $mtime; % $year += 1900; % $month ++; % my $datetime = sprintf '%4d-%02d-%02dT%02d:%02d:%02d', % $year, $month, $mday, $hour, $minute, $second; %= $datetime; % end <!DOCTYPE html> <style> table { border-collapse: collapse; } th, td { border: 1px solid; padding: 6px; } </style> <table> <thead><tr><th>filename</th><th>size</th><th>datetime</th></tr></thead> <tbody> % for my $item (@$files) { <tr> <td> %= $item->{filename} </td> <td title="<%= $item->{size} %>"> %= $humanreadable_filesize->( $item->{size} ) </td> <td> <time datetime="<%= $humanreadable_datetime->( $item->{mtime} ) %>"> %= $humanreadable_datetime->( $item->{mtime} ) </time> </td> </tr> % } </tbody> </table>
ヘルパーを使った例
Mojoliciousでは、ヘルパーという自分で定義できるテンプレートマクロがあるので、こっちを使うとよい。
#!/usr/bin/perl use 5.010; # Mojolicious 1.9.8以降はPerl 5.10対応 use Mojolicious::Lite; use Mojo::Util qw(decode); use File::Basename; use File::Spec; my $file_dir = app->home->rel_dir('./'); get '/' => sub { my $self = shift; my @filelist = glob( File::Spec->catfile( $file_dir, '*' ) ); my $files = []; for my $filename ( @filelist ) { my @stat = stat $filename; push @$files, { filename => decode('utf8', File::Basename::basename($filename) ), size => $stat[7], mtime => $stat[9], }; } $self->stash( files => $files ); } => 'index'; # Mojolicious Helpers # ファイルサイズのバイト数を、人間が読めるように桁繰上げする helper humanreadable_filesize => sub { my ($self, $string) = @_; my $size = $string; my $unit = 'byte'; if ( $size > 1024 ) { $size /= 1024; $unit = 'KB'; }; if ( $size > 1024 ) { $size /= 1024; $unit = 'MB'; }; if ( $size > 1024 ) { $size /= 1024; $unit = 'GB'; }; $string = sprintf '%.1f %s', $size, $unit; }; # Unix epoch時刻を、人間が読めるように整形する helper humanreadable_datetime => sub { my ($self, $string) = @_; my $mtime = $string; my ($second, $minute, $hour, $mday, $month, $year) = localtime $mtime; $year += 1900; $month ++; my $datetime = sprintf '%4d-%02d-%02dT%02d:%02d:%02d', $year, $month, $mday, $hour, $minute, $second; $string = $datetime; }; app->start; __DATA__ @@ index.html.ep <!DOCTYPE html> <style> table { border-collapse: collapse; } th, td { border: 1px solid; padding: 6px; } </style> <table> <thead><tr><th>filename</th><th>size</th><th>datetime</th></tr></thead> <tbody> % for my $item (@$files) { <tr> <td> %= $item->{filename} </td> <td title="<%= $item->{size} %>"> %= humanreadable_filesize( $item->{size} ) </td> <td> <time datetime="<%= humanreadable_datetime( $item->{mtime} ) %>"> %= humanreadable_datetime( $item->{mtime} ) </time> </td> </tr> % } </tbody> </table>