#254 Pagination with Kaminari
- Download:
- source codeProject Files in Zip (128 KB)
- mp4Full Size H.264 Video (16.4 MB)
- m4vSmaller H.264 Video (10.1 MB)
- webmFull Size VP8 Video (25 MB)
- ogvFull Size Theora Video (22.2 MB)
下の図は、長い一覧リストを表示するRails 3アプリケーションのスクリーンショットです。この一覧を、1ページのリストとしてではなく複数ページに分けて表示してみましょう。
Railsでのページ分割処理(pagination)でまず選択されるgemといえばwill_paginateです。しかし現行バージョンはRails 3をサポートしていません。Rails 3対応のプレリリース版がありますが、ここ数ヶ月更新されていません。will_paginateの開発が停止しているとしたら、他に使えるgemはないでしょうか?
代替案の一つがKaminariです。こちらのほうが、ページ分割処理をよりきれいに実装していていくつか改善された機能を備えているので、試してみることにしましょう。Kaminariは通常の方法でインストールします。アプリケーションのGemfile
に参照情報を追記し、bundleコマンドを実行し、システムにインストールされたことを確認します。
gem "kaminari"
Kaminariを使う
Kaminariでは、すべてのActiveRecordモデルに対してpageというスコープを適用できます。ProductsController
のindex
アクション内で表示したいページに連結して、商品リストをページ分割して表示できるようになります。検索文字列にページ引数を連結します。
def index @products = Product.order("name").page(params[:page]) end
商品リストを表示しているindex
テンプレートで、Kaminariが提供するpaginate
ヘルパーメソッドを呼び出し、ページ分割処理をしたいリストを渡します。
<% title "Products" %> <%= paginate @products %> <div id="products"> <%= render @products %> </div> <p><%= link_to "New Product", new_product_path %></p>
ここでページをリロードすると、ページの上部に各ページへのリンクが表示されます。
Kaminariは初期設定では1ページあたり25件を表示しますが、per
というスコープを呼び出すことで簡単に表示件数を変更できます。この機能を使って、1ページに表示する件数を5件に変えてみましょう。
def index @products = Product.order("name").page(params[:page]).per(5) end
これで、1ページに5件が表示されるようになりました。
この記法は、Rails 3の新しいActiveRecordの検索式によくなじみます。order
の呼び出しは、モデルに対してスコープを連結しているだけなので、検索式内のどこに記述しても同じように動作します。page
とper
の呼び出しを、モデルの他の名前付きスコープ内に移動しても、期待通りの動きをします。ただし一つだけ例外があって、per
は必ずpage
の後ろで呼び出されなくてはいけません。これは、page
がper
スコープを持つからです。もしper
をpage
の前に呼び出したら、まだ存在しないため、未定義メソッドのエラーが発生します。
外観を変更する
ページリンクの外観を変更する方法がいくつかあります。ページ数のリンクと一緒に表示される「prev」と「next」という表示と矢印を変更するために、ローカライゼーションファイルに行を追加します。この方法で簡単にこれらの項目を修正できます。複数言語サポートが必要な場合は特に便利です。追加したい項目をviews/pagination
の下に置き、previous
とnext
の各キーを設定することで、「前」と「次」にあたる文字列を変更できます。ページ数を省略して表示するときの文字列を変更する場合は、truncate
キーを設定します。ページが表示されるときにHTMLタグがエスケープされないので、ここでエスケープしておくことに注意してください。
en: hello: "Hello world" views: pagination: previous: "< Previous" next: "Next >" truncate: "…"
ページをリロードすると変更された文字列を確認できます。
ナビゲーションの他の部分で利用されるクラスにCSSを追加することでさらに変更を加えることができます。ナビゲーション部分がHTML 5のnav
要素を使用していることに注目してください。
<nav class='pagination'> <span class="prev"> <a href="/products" class="prev" rel="prev">< Previous</a> </span> <span class="page first"> <a href="/products">1</a> </span> <span class="page current">2</span> ...
CSSを用いればナビゲーションの外観を大きく変えることができますが、変更できない部分もあります。例えば、1ページ目の「Previous」のリンクは初期設定では非表示になっていますが、これをクリックできないリンクとして薄い色で表示するということはできません。
KaminariはRailsのエンジンというしくみで実装されていて、複数のビューファイルを持っており、それらを編集することでアプリケーションをカスタマイズできます。これを簡単に行うために、Kaminari で提供されるジェネレータを利用できます。このジェネレータに、テーマ名を引数として渡します。defaultを指定すると標準のテーマが適用されます。
$ rails g kaminari:views default create app/views/kaminari/_current_page.html.erb create app/views/kaminari/_first_page_link.html.erb create app/views/kaminari/_last_page_link.html.erb create app/views/kaminari/_next_link.html.erb create app/views/kaminari/_next_span.html.erb create app/views/kaminari/_page_link.html.erb create app/views/kaminari/_paginator.html.erb create app/views/kaminari/_prev_link.html.erb create app/views/kaminari/_prev_span.html.erb create app/views/kaminari/_truncated_span.html.erb
GitHubのKaminari Themes projectで他のテーマも見ることができます。この記事の執筆時点ではまだ少ししかありませんが、ユーザが自分のテーマを作れる材料は揃っているので、近いうちに間違いなく数は増えていくでしょう。
ジェネレータがapp/views
の下に新たにkaminari
ディレクトリを生成し、いくつかのファイルを配置します。これらのファイルを修正することでページ分割の動作をカスタマイズできます。ベースとなるファイルは_paginator.html.erb
で、一見複雑に見えますが簡単に理解して修正できるようになるでしょう。
/app/views/kaminari/_paginator.html.erb
<%# The container tag
- available local variables
current_page: the page number of currently displayed page
num_pages: total number of pages
per_page: number of items to fetch per page
remote: data-remote
paginator: the paginator that renders the pagination tags inside
-%>
<%= paginator.render do -%>
<nav class='pagination'>
<%= current_page > 1 ?prev_link_tag : prev_span_tag %>
<% each_page do |page| -%>
<% if page.current?-%>
<%= current_page_tag %>
<% elsif page.left_outer?|| page.right_outer?|| page.inside_window?-%>
<% if page.first?-%>
<%= first_page_link_tag %>
<% elsif page.last?-%>
<%= last_page_link_tag %>
<% else -%>
<%= page_link_tag %>
<% end -%>
<% elsif !page.was_truncated?-%>
<%= truncated_span_tag %>
<% end -%>
<% end -%>
<%=num_pages > current_page ?next_link_tag : next_span_tag %>
</nav>
<% end -%>
nav
要素と、そのすぐ下の「previous」リンクを表示するためのコードを見てみましょう。このコードは、表示中のページが第1ページかどうかによって、prev_link_tag
かprev_span_tag
のいずれかを呼び出します。それぞれのメソッドが、別のファイルを呼び出します。第1ページの動作を変えたいので、_prev_span.html.erb
を見てみます。
<%# "Previous" without link - available local variables current_page: the page number of currently displayed page num_pages: total number of pages per_page: number of items to fetch per page remote: data-remote -%> <span class="prev"></span>
span
要素内に少しコードを追加します。_prev_link.html.erb
の中を見ると、リンクを生成しているのがわかります。このリンクのための文字列を生成している部分のコードをコピーして、span
要素内にペーストします。
<%# "Previous" without link - available local variables current_page: the page number of currently displayed page num_pages: total number of pages per_page: number of items to fetch per page remote: data-remote -%> <span class="prev disabled"> <%= raw(t 'views.pagination.previous') %> </span>
文字列を表示するコードがt
メソッドを使用して、前に修正したローカライゼーションファイルの翻訳された文字列を取得し、エスケープされないようraw
に渡されます。さらにその部分にdisabled
クラスを追加し、以下のCSSを記述して、薄い色で表示されるようにしています。
.disabled { color: #999; }
商品リストの第1ページを読み込むと、薄い色で表示された文字列を見ることができます。
当然、最終ページの「next」リンクも同じように処理したいのですが、ここでは省略します。
Kaminari gemの紹介は以上です。Rails 3のアプリケーションでページ分割処理が必要な場合は、試してみてください。今回は紹介できませんでしたが、paginateヘルパーメソッドに渡すことができるオプションは多岐に渡ります。例えば、ページリンクのURLに引数を追加したり、リンクをAJAXで動作させて、ページのリストの表示方法を変えることなども可能です。