M.C.P.C. (Mamesibori Creation Plus Communication)

印刷屋から五反田のWeb屋に転職したCLのブログです。

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>