1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

kaminariで"ページネーションのhtml要素"を非同期に出す

Last updated at Posted at 2020-10-28

「kaminari 非同期」で検索すると、ページネーションの"リンク先のページ"を非同期で表示する方法が出てきます。kaminariのREADMEで紹介しているのもコレです。
https://github.com/kaminari/kaminari

そうではなくて、"ページネーションのhtml要素"を非同期で後から出す方法の紹介です。
検索してもあまり出てこなかったんですよね。
image.png
(↑ページネーションのhtml要素)

Gemのバージョンはkaminari (1.2.1), rails (6.0.3.4)です。

なぜ非同期に?

普通にkaminariでページネーションの要素を出すときは、ビューで<%= paginate(@users) %>のように書きます。
でもこれ、最後のページが何ページ目なのか求めるために、「レコードの全件数を取得するクエリ」が流れるんです。

SELECT COUNT(*) FROM users;

この場合は軽そうですが、色々と検索条件を付けていくと重いクエリになりそうです。なのでページネーションの要素は非同期で表示させたい、というのが動機です。

実装

最初に全体像を載せます。Railsで、Userモデルのindexページ、という想定です。

routes.rb
resources :users
users_controller.rb
def index
  users = User.all.page(params[:page]).per(3)
  respond_to do |format|
    format.html { @users = users.without_count }
    format.json { render :json => view_context.paginate(users).gsub('.json', '') }
  end
end
users/index.html.erb
<div id="paginator"></div>
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const path = location.pathname + '.json' + location.search;
    const paginator = document.querySelector('#paginator');
    fetch(path).then(response => response.text()).then(html => {
      paginator.insertAdjacentHTML('afterbegin', html);
    })
  })
</script>

解説

処理の流れを順に説明します。最初、usersコントローラーのindexアクションにhtmlのリクエストが来たら、モデルに.without_countメソッドを付けてからviewに渡します。

format.html { @users = users.without_count }

このメソッドをつけると、先程載せた「レコードの全件数を取得するクエリ」が発行されなくなります。
ページネーションの要素が付いてないhtmlが返され、同期処理は終わりです。

ここからが非同期です。返したhtml(内のjavascript)では、ページネーションの要素を非同期で要求します。
このときURLをlocation.pathname + '.json' + location.search;として、
「URLのパスの末尾をjsonに変えただけのURL」を送ります。元のパスが/users?age=10なら送るパスは/users.json?age=10です。

送られてきたリクエストは、同じくusersコントローラーのindexアクションに送られます。
URLに.jsonが付いているので、respond_toformat.jsonの方が呼ばれます。

format.json { render :json => view_context.paginate(users).gsub('.json', '') }

view_contextはビューのインスタンスを返すメソッドです( https://apidock.com/rails/ActionView/Rendering/view_context )。 これを使うとビューのメソッドをコントローラー等から使うことができて、paginate(user)が呼べます。
ここで使う変数userにはwithout_countは付けていませんので、「レコードの全件数を取得するクエリ」が発行されます
paginateメソッドで作られたページネーション要素ですが、formatがjsonだと(html以外だと)、リンクの末尾が.jsonとなってしまいます。ですのでgsubで消します。
出来上がったhtml文字列をjsonで包んで返します。

受け取ったjavascript側では、それを適当な要素にくっつければ完了です。

fetch(path).then(response => response.text()).then(html => {
  paginator.insertAdjacentHTML('afterbegin', html);
})

非同期でページネーション要素を出せました。
(↓の例ではsleepを入れています)

a.gif

いいですね、非同期。
この記事が誰かの役に立てればです。

1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?