ページネーションの実装
一覧ページ・検索結果ページにページネーションを導入しましょう。
ページネーションとは「次のページ」「前のページ」「先頭のページ」「最後のページ」、「ページナビゲーション 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;
Perl Webアプリ開発入門