• このエントリーをはてなブックマークに追加

ss-1366457267

はじめに

最近ようやく Mouse な実装を始めたところで(Moose もほとんどやったことない),これがなかなか楽しいので,ちょうどお仕事で必要だった「パジネーション」なクラスを書いてみたのでそのへんを晒してみます.

個人的に使いたいようにしか実装していませんw

こんな感じで使いたい

「モデル」という定義にそぐわないような気もしますが,まぁあまり気にしないでください><

コントローラ内

コード中に出てくる $c->model(...) については,次をご参照ください:

sub foobar {
    my ($class, $c) = @_;
    my $req = $c->req;
 
    my $page  = $req->param('page') // 1;
    my $num   = $req->param('num')  // 50;
    my $count = ...;
 
    my @nanika_no_list = ...;
 
    my %pg_params = (
        page    => $page,
        entries => $num,
        count   => $count,
    );
    my $pg = $c->model('Pagination' => \%pg_params);  # ココ!
 
    return $c->render('foobar.tx', {
        nanika_no_list => \@nanika_no_list,
        pg             => $pg,
    })
}

ビュー foobar.tx

...
 
<div id="nanika_no_list">
:include inc::pg
 
  <ol>
:for $nanika_no_list -> $i {
    <li><: $i.foobar :></li>
:}
  </ol>
 
:include inc::pg
</div><!-- /#nanika_no_list -->
 
...

パジネーション部分のテンプレート inc/pg.tx

:if defined $pg {
<div class="row-fluid">
  <ul class="pager span1">
:  if $pg.has_prev {
    <li class="previous">
      <a href="?page=<: $pg.prev :>">&laquo;</a>
    </li>
:  } else {
    <li class="previous disabled">
      <a>&lt;&lt;</a>
    </li>
:  }
  </ul>
 
  <div class="pagination pagination-centered span10">
    <ul>
:  for $pg.list_page( range => 3, first => 1, last => 1 ) -> $p {
:    if defined $p {
      <li <:if $p == $pg.page {:>class="active"<:}:>>
        <a href="?page=<: $p :>"><: $p :></a>
      </li>
:    } else {
      <li class="disabled"><a>...</a></li>
:    }
:  }
    </ul>
  </div>
 
  <ul class="pager span1">
:  if $pg.has_next {
    <li class="next">
      <a href="?page=<: $pg.next :>">&raquo;</a>
    </li>
:  } else {
    <li class="next disabled">
      <a>&gt;&gt;</a>
    </li>
:  }
  </ul>
</div><!-- /.row-fluid -->
:}

パジネーションなクラス

上記の要件の下,以下のように書いてみました.

コード中の $c->validator(...) については,次をご参照ください:

また,テストについては,だいたい次のような感じで書いてます:

さて,Mouse 的にはこんな感じでいいんですかね?

package MyApp::Model::Pagination;
use 5.16.0;
use warnings;
use utf8;
use Mouse;
use MouseX::Types::Mouse qw/Bool Int/;
use POSIX qw/ceil/;
 
my %ro = ( is => 'ro' );
my %ro_Int = ( %ro, isa => Int );
 
has 'c'            => ( %ro, isa => 'MyApp' );
has 'count'        => ( %ro_Int );
has 'entries'      => ( %ro_Int );
has 'page'         => ( %ro_Int );
has 'pages'        => ( %ro_Int );
has 'entries_page' => ( %ro_Int );
has 'next'         => ( %ro_Int, lazy_build => 1 );
has 'prev'         => ( %ro_Int, lazy_build => 1 );
has 'first'        => ( %ro_Int );
has 'last'         => ( %ro_Int );
 
sub BUILDARGS {
    my ($class, %params) = @_;
    my $v = $params{c}->validator(
        page    => { isa => Int, default =>1 },
        count   => { isa => Int },
        entries => { isa => Int },
    )->with('AllowExtra');
    my ($p, %ex) = $v->validate( \%params );
 
    my ($count,$entries, $page) = @$p{qw/count entries page/};
    my $pages = ceil( $count / $entries );
 
    %ex = (
        %ex,
        pages        => $pages,
        entries_page => 0,
        first        => 1,
        last         => $pages,
    );
    if ( $page < $pages ) {
        $ex{next} = $page < 1 ? 1 : $page + 1;
    }
    if ( $page > 1 ) {
        $ex{prev} = $page <= $pages ? $page - 1 : $pages;
    }
    if ( 0 < $page  &&  $page <= $pages ) {
        $ex{entries_page} = $ex{next} ? $entries : ($count % $entries) || $entries;
    }
 
    return +{ %$p, %ex };
}
 
sub list_page {
    my ($self, %params) = @_;
    my $v = $self->c->validator(
        range => { isa => Int, optional => 1 },
        first => { isa => Bool, default => 0 },
        last  => { isa => Bool, default => 0 },
    );
    my $p = $v->validate( \%params );
    my $page  = $self->page;
    my $first = $self->first;
    my $last  = $self->last;
    my $range = exists $p->{range} ? $p->{range} : undef;
 
    my @p;
    my ($from, $to);
 
    if ( defined $range ) {
        ($from, $to) = ( $page - $range, $page + $range );
        $from = $first  if $from < 1;
        $to   = $last   if $to > $last;
        if ( $p->{first} ) {
            unshift @p, undef   if $from > $first + 1;
            unshift @p, $first  if $from > $first;
        }
        else {
            unshift @p, undef  if $from > 1;
        }
    }
    else {
        ($from, $to) = ( $first, $last );
    }
 
    for my $p ( $from .. $to ) {
        push @p, $p;
    }
 
    if ( defined $range ) {
        if ( $p->{last} ) {
            push @p, undef  if $to < $last - 1;
            push @p, $last  if $to < $last;
        }
        else {
            push @p, undef  if $to < $last;
        }
    }
 
    return wantarray ? @p : \@p;
}
 
__PACKAGE__->meta->make_immutable();
1;

list_page メソッド

パラメータでページ一覧の表示方法を調整できます.

先の inc/pg.tx において,パラメータを次のように変更すると:

:  for $pg.list_page( range => 1, first => 1 ) -> $p {
...
:  }

次のような表示になります:

ss-1366461028

おわりに

以上,ちょっとお仕事で必要になった「パジネーション」を実装するにあたり,せっかくなので Mouse ベースなクラスとして実装してみたお話でした.