最近kaminariの実装を行ったので学んだことや得たkaminariの理解についてメモがてら書いてみようと思う。
やりたいこと、目的
kaminariでできる通常の機能に加えて
1. 自分の今いるページ(以下current_page
として省略)とページの総数(以下total_pages
として省略)に加えて二つの誘導リンクを設置したい。例えばcurrent_page < 10
のときに表示するページは20と40で10を超えると30と50。
手順
kaminariのsetupについて
kaminariの基本的な実装はネット上に山ほど落ちているので僕が説明するまでもないと思います。いかに参考リンクを貼っておきます。言うまでもないですが解説もわかりやすいのですが、わからないことがあったら随時ググっていったほうが構造理解には繋がると思います。
- kaminari徹底入門
- amatsuda/kaminari
- Railsでkaminariを使ってページネーションを実現する
- kaminariをBootstrap3、Rails4.2環境で使う![Ruby 2.3]
kaminariの基本的な設定
#config/initializers/kaminari_config.rb
Kaminari.configure do |config|
config.default_per_page = 100
# config.max_per_page = nil
# config.window = 4
# config.outer_window = 0
# config.left = 0
# config.right = 0
# config.page_method_name = :page
# config.param_name = :page
end
他にも方法はあるがここでpaginationの細かい設定を行ってる。ページリンク数を変えたり、リンクの位置を変えることができる。僕は「イヤマテヨ。ソモソモouter_window
ッテナニ??」ってなったので下記のリンクを参考にしながら作業してました。
kaminariの挙動を探ってみる
のリンクをイジってみたいとする。
prev kaminari-page-tip
というクラスの階層の下にあるので
# app/views/kaminari/_paginator.html.slim
/ The container tag
- available local variables
current_page : a page object for the currently displayed page
total_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
ruby:
one_digit = current_page.to_i % 10
page_20 = current_page + 20 - one_digit
page_40 = current_page + 40 - one_digit
if params[:available].present? || params[:price].present? || params[:usage_state].present?
url_20 = "#{request.path_info}?available=#{params[:available] if params[:available].present?}&page=#{page_20}&price=#{params[:price] if params[:price].present?}&usage_state=#{params[:usage_state] if params[:usage_state].present?}"
else
url_20 = "/items-page-#{page_20}"
end
== paginator.render do
.pagination
.pagination-middle
.pagination-prev
== prev_page_tag unless current_page.first?
/ == first_page_tag unless current_page.first? || [2,3,4].include?(current_page)
- each_page do |page|
- if page.left_outer? || page.right_outer? || page.inside_window?
== page_tag page
- elsif !page.was_truncated?
- unless (current_page >= 5 && page < current_page) || (total_pages - 4 == current_page && page == total_pages)
- if total_pages >= 20 && total_pages > current_page.number + 20
== gap_tag
span.kaminari-page-tip class="page#{' current' if page.current?}"
== link_to_unless current_page == page_20, page_20, url_20, {:remote => remote, :rel => page.next? ? 'next' : page.prev? ? 'prev' : nil}
== gap_tag
- else
== gap_tag
- if total_pages >= page_40 || total_pages < 20
== last_page_tag unless current_page == page_40 || current_page == page_20 || [1,2,3].map{|i| total_pages - i}.include?(current_page)
.pagination-next
== next_page_tag unless current_page.last?
== paginator.render do
の中の.paginator.html.slim
クラスの下に何やら沢山書かれている。prev_page_tag
というのは以下のファイルのこと。
# app/views/kaminari/_prev_page.html.slim
span.prev.kaminari-page-tip
== link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url.gsub("categories/","").gsub("%2F","\/"), :rel => 'prev', :remote => remote
'
つまりcurrent_page.first?
でfalse
判定が出た場合はprev_page
をレンダリングするように書かれている。多分だがprev
がcurrent_page
より先に降りてくるようなデータ構造になっている。
その他app/views/kaminari
の_last_page.html.slim
や_next_page.html.slim
、_gap.html.slim
、_page.html.slim
は全ては中枢である_paginator.html.slim
のファイルによって呼び出し管理を行っている。
each_pageの存在
ここではeach_page
とは
# File 'lib/kaminari/helpers/paginator.rb', line 49
def each_relevant_page
#enumerate each page providing PageProxy object as the block parameter Because of performance reason, this doesn't actually enumerate all pages but pages that are seemingly relevant to the paginator.
return to_enum(:each_relevant_page) unless block_given?
relevant_pages(@window_options).each do |page|
yield PageProxy.new(@window_options, page, @last)
end
end
- pages inside the left outer window plus one for showing the gap tag
- pages inside the inner window plus one on the left plus one on the right for showing the gap tags
- pages inside the right outer window plus one for showing the gap tag
と書かれている。これはつまり
こういうことではないのかと仮説を立てております。(本当かどうかわからないけど汗)
- each_page do |page|
- if page.left_outer? || page.right_outer? || page.inside_window?
== page_tag page
- elsif !page.was_truncated?
- unless (current_page >= 5 && page < current_page) || (total_pages - 4 == current_page && page == total_pages)
- if total_pages >= 20 && total_pages > current_page.number + 20
== gap_tag
span.kaminari-page-tip class="page#{' current' if page.current?}"
== link_to_unless current_page == page_20, page_20, url_20, {:remote => remote, :rel => page.next? ? 'next' : page.prev? ? 'prev' : nil}
== gap_tag
- else
== gap_tag
で単体のpage
に分けて一つ一つの(言うなれば)所属すべき場所に振り分けるための精査作業を行っているのではないかと。例えばlink_to_unless current_page == page_20
で条件にあえば上の方で定義したpage_20
へのレンダリングを行っているなどなど。
試しに
以下のようにテストしてみたところ
と出たのでどうやらeach_pageの中にcurrent_pageがあるのは事実みたいです。
蛇足
これはあくまで予想だがここに書いてあるviews.pagination.previous
は以下のファイルとつながっている。
«
と»
については« と » ってなんだろうの巻を参考に。
# config/locales/kaminari.ja.yml
ja:
views:
pagination:
first: "«"
last: "»"
previous: "前へ"
next: "次へ"
truncate: "..."
なぜ_paginator.html.slimがこんなに長いのか
ファイルの最初の方で
ruby:
one_digit = current_page.to_i % 10
page_20 = current_page + 20 - one_digit
page_40 = current_page + 40 - one_digit
if params[:available].present? || params[:price].present? || params[:usage_state].present?
url_20 = "#{request.path_info}?available=#{params[:available] if params[:available].present?}&page=#{page_20}&price=#{params[:price] if params[:price].present?}&usage_state=#{params[:usage_state] if params[:usage_state].present?}"
else
url_20 = "/items-page-#{page_20}"
end
page_20
とpage_40
を定義することで画像で言う
を作るために予め変数にリンク先の情報を入れているわけです。あとは上で書いたファイルの中のレンダリングで呼んであげるだけ。