LoginSignup
2
1

More than 5 years have passed since last update.

rails: kaminariをつかってちょっとだけ付加機能をつけたページネーションの実装

Posted at

最近kaminariの実装を行ったので学んだことや得たkaminariの理解についてメモがてら書いてみようと思う。

やりたいこと、目的

Screen Shot 2016-11-16 at 12.06.35 AM.png

kaminariでできる通常の機能に加えて
1. 自分の今いるページ(以下current_pageとして省略)とページの総数(以下total_pagesとして省略)に加えて二つの誘導リンクを設置したい。例えばcurrent_page < 10のときに表示するページは20と40で10を超えると30と50。

手順

kaminariのsetupについて

kaminariの基本的な実装はネット上に山ほど落ちているので僕が説明するまでもないと思います。いかに参考リンクを貼っておきます。言うまでもないですが解説もわかりやすいのですが、わからないことがあったら随時ググっていったほうが構造理解には繋がると思います。

kaminariの基本的な設定

kaminari_config.rb
#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の挙動を探ってみる

例えば
kaminari_finish copy.png

のリンクをイジってみたいとする。

Screen Shot 2016-11-15 at 11.13.36 PM.png

prev kaminari-page-tipというクラスの階層の下にあるので

_paginator.html.slim

# 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というのは以下のファイルのこと。

_prev_page.html.slim
# 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をレンダリングするように書かれている。多分だがprevcurrent_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とは

paginator.rb
# 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

と書かれている。これはつまり

kaminari_each_page.png

こういうことではないのかと仮説を立てております。(本当かどうかわからないけど汗)

_paginator.html.slim
- 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へのレンダリングを行っているなどなど。

試しに

Screen Shot 2016-11-16 at 12.18.32 AM.png

以下のようにテストしてみたところ

Screen Shot 2016-11-16 at 12.17.31 AM.png

と出たのでどうやらeach_pageの中にcurrent_pageがあるのは事実みたいです。

蛇足

これはあくまで予想だがここに書いてあるviews.pagination.previousは以下のファイルとつながっている。

&laquo&raquoについては&laquo と &raquo ってなんだろうの巻を参考に。

kaminari.ja.yml
# config/locales/kaminari.ja.yml

ja:
  views:
    pagination:
      first: "&laquo;"
      last: "&raquo;"
      previous: "前へ"
      next: "次へ"
      truncate: "..."

なぜ_paginator.html.slimがこんなに長いのか

ファイルの最初の方で

_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_20page_40を定義することで画像で言う

Screen Shot 2016-11-16 at 12.06.35 AM.png

を作るために予め変数にリンク先の情報を入れているわけです。あとは上で書いたファイルの中のレンダリングで呼んであげるだけ。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1