ページネーションの実装
一覧ページ・検索結果ページにページネーションを導入しましょう。
ページネーションとは「次のページ」「前のページ」「先頭のページ」「最後のページ」、「ページナビゲーション 1, 2, 3 ...」などの機能のことです。
結果が、30件~50件を超えてくると、ページネーションを導入したくなりますね。
ページネーションのためのモジュールのインストール
Perlには、ページネーションを支援してくれるモジュールがあります。
ページネーションの基本モジュールとして「Data::Page」をインストールします。
Data::Page::Navigationは、Data::Pageにページナビゲーション機能を追加してくれるモジュールです。こちらもインストールしましょう。
モジュールのインストールはcpanmを使って行います。
cpanm Data::Page cpanm Data::Page::Navigation
ページネーションの基礎
テーブルに本のデータが1000件存在すると仮定しましょう。
id => 1, name => 'Book1', price => 1200 id => 2, name => 'Book2', price => 2000 ... id => 1000, name => 'Book1000', price => 450
Data::Pageモジュールは、ページネーションに必要な情報を自動的に計算してくれますが、そのために必要な情報を知っておく必要があります。
全体の件数
まず最初に必要な情報は、全体の件数です。DBIx::Customのselectメソッドと、mysqlのcount関数を使って、件数を取得してみましょう。
# 全体の件数 my $total = $dbi->model('book')->select('count(*)')->value;
一ページに表示する個数
次に必要なのは、一ページに表示する個数です。これは、30~50くらいの値を設定します。
my $count_per_page = 30;
現在のページ
最後は、現在のページです。これは、パラメーターから受け取り、長さがない場合、または、1より小さい場合は、1を指定しましょう。
my $page = param('page'); $page = 1 if !length $page || $page < 1;
オフセットの計算
ひとつのページに表示するデータを取り出すには、、mysqlのlimit句を使って、どの位置からデータを取得するかを指定する必要があります。
このために、オフセットを計算します。
my $offset = $count_per_page * ($page - 1);
データの取得
mysqlのbookテーブルから、指定したページに表示するデータを取得します。DBIx::Customのselectメソッドと、mysqlのlimit句を使って、件数を取得してみましょう。appendはorder by句や、limit句など自由に追加するためのオプションです。
my $books = $dbi->model('book')->select(['id', 'name'], append => "limit $offset, $count_per_page")->all;
Data::Pageオブジェクトの生成
これで、Data::Pageオブジェクトを生成する準備が整いました。
# Data::Pageオブジェクトの生成 my $pager = Data::Page->new($total, $count_per_page, $page);
前のページを取得
前のページを取得するには、previous_pageメソッドを使用します。前のページがない場合は、undefが返ってきます。
my $previous_page = $pager->previous_page;
次のページを取得
次のページを取得するには、next_pageメソッドを使用します。次のページがない場合は、undefが返ってきます。
my $next_page = $pager->next_page;
最初のページ番号
最初のページ番号を取得するにはfirst_pageメソッドを使用します。常に1を返します。
my $first_page = $pager->first_page;
最後のページ番号
最後のページ番号を取得するにはlast_pageメソッドを使用します。これは総ページ数と同じ値になります。
my $last_page = $pager->last_page;
ページナビゲーション機能
1, 2, 3, 4などのページナビゲーション機能を追加してみましょう。たとえば15ページ目にいるときは、15ページ目の周辺のページを合わせて、ページ番号を十個取得できます。
# ページナビゲーションの個数を設定 $pager->pages_per_navigation(10); # 10 11 12 13 14 15 16 17 18 19 my @nav_pages = $pager->pages_in_navigation;
ページネーションのサンプルコード
ページネーションのサンプルコードです。最初のページ、前のページ、次のページ、最後のページのサンプルです。ページが複数あるときだけ、ページネーションを表示するようにしています。
アプリケーションクラス
use Data::Page; use Data::Page::Navigation;
テンプレート
<% # 現在のページ my $page = param('page'); $page = 1 if !length $page || $page < 1; # DBI my $dbi = $self->app->dbi; # 1ページに表示する個数 my $count_per_page = 30; # オフセット my $offset = $count_per_page * ($page - 1); # 本の一覧 my $books = $dbi->model('book')->select( ['id', 'name'], append => "limit $offset, $count_per_page" )->all; # 本の総数 my $total = $dbi->model('book')->select('count(*)')->value; # ページネーションオブジェクト my $pager = Data::Page->new($total, $count_per_page, $page); %> % for my $book (@$books) { <div> <div>本のID <%= $book->{id} %></div> <div>本のタイトル <%= $book->{name} %></div> </div> % } <% # ページナビゲーションの個数を設定 $pager->pages_per_navigation(10); # 前のページ my $previous_page = $pager->previous_page; # 次のページ my $next_page = $pager->next_page; # 最初のページ my $first_page = $pager->first_page; # 最後のページ my $last_page = $pager->last_page; # 周辺のページ番号を取得 my @nav_pages = $pager->pages_in_navigation; %> % if ($first_page != $last_page) { <div class="pagenation"> <a href="<%= url_with->query({page => undef}) %>">最初へ</a> % if ($previous_page) { <a href="<%= url_with->query({page => $previous_page}) %>">前へ</a> % } % for my $nav_page (@nav_pages) { % if ($nav_page eq $page) { <span><%= $nav_page %></a> % } else { <a href="<%= url_with->query({page => $nav_page}) %>"><%= $nav_page %></a> % } % } % if ($next_page) { <a href="<%= url_with->query({page => $next_page}) %>">次へ</a> % } <a href="<%= url_with->query({page => $last_page}) %>">最後へ</a> </div> % }
$selfはMojolicious::Controllerです。appでMojoliciousを継承しているアプリケーションオブジェクトを取得します。dbiで、DBIx::Customオブジェクトを取得しています。
url_withとqueryへハッシュリファレンスを渡す方法を使用すると、現在のURLのクエリを維持しながら、ページ番号だけ置換することができます。
よくある質問
where句と組み合わせることはできますか?
はい、where句を組み合わせることができます。本のタイトルに、Perlを含むの例です。
my $where = [ 'title like :title', # where句 {title => '%Perl%'}, # パラメーター ]; my $books = $dbi->model('book')->select( ['id', 'name'], append => "limit $offset, $count_per_page", where => $where )->all; my $total = $dbi->model('book')->select('count(*)', where => $where)->value;
URLの他のクエリ文字列をそのままにして、ページ番号だけを変えることはできますか?
ここで解説したサンプルは、ページ番号だけの変更に対応しています。
url_forではなくurl_withを使うこと。queryメソッドの引数にハッシュリファレンス「{page => $page}」を渡すことがポイントです。
ページネーションを部品化することはできますか?
はい、ページネーションは、上下に表示したり、他のページと共有したい場合がありますね。
そのような場合は、include機能を使うことができます。
テンプレートファイル「templates/include/pagenation.html.ep」を作成します。
stashを使ってpagerを受け取ります。
<% my $pager = stash('pager'); # 現在のページ my $page = $pager->current_page; # ページナビゲーションの個数を設定 $pager->pages_per_navigation(10); # 前のページ my $previous_page = $pager->previous_page; # 次のページ my $next_page = $pager->next_page; # 最初のページ my $first_page = $pager->first_page; # 最後のページ my $last_page = $pager->last_page; # 周辺のページ番号を取得 my @nav_pages = $pager->pages_in_navigation; %> % if ($first_page != $last_page) { <div class="pagenation"> <a href="<%= url_with->query({page => undef}) %>">最初へ</a> % if ($previous_page) { <a href="<%= url_with->query({page => $previous_page}) %>">前へ</a> % } % for my $nav_page (@nav_pages) { % if ($nav_page eq $page) { <span><%= $nav_page %></a> % } else { <a href="<%= url_with->query({page => $nav_page}) %>"><%= $nav_page %></a> % } % } % if ($next_page) { <a href="<%= url_with->query({page => $next_page}) %>">次へ</a> % } <a href="<%= url_with->query({page => $last_page}) %>">最後へ</a> </div> % }
使う側は、以下のようにします。
%= include 'include/pagenation', pager => $pager;