0
0

More than 3 years have passed since last update.

Kaminariを使用した非同期ページネーションの作り方

Posted at

この記事で説明する事

  1. SJRを使用して非同期ページネーション実装する。
  2. ページネーション自体はgemのkaminariを使用する。

簡単にアプリの作りを説明

インスタのクローンアプリで、ユーザーがいて、画像を投稿する事が出来る様な仕組みになっています。
user has many postsの形になっていて、今回はPostsに対してページネーションを付け様と思います。参考までにテーブル構成乗っけてます。

usersテーブル

  create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "email", null: false
    t.string "crypted_password"
    t.string "salt"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "username", null: false
    t.string "avatar"
    t.index ["email"], name: "index_users_on_email", unique: true
  end

postsテーブル

  create_table "posts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "images", null: false
    t.text "body", null: false
    t.bigint "user_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["user_id"], name: "index_posts_on_user_id"
  end

何はともあれkaminariを導入する。

Gemfile

gem 'kaminari'

いつも通りbundle install

今回はユーザーが投稿したpostsにページネーションを付けるのでposts_controllerの記述を変更する。

posts_controller

def index
  @posts = Post.page(params[:page]).includes(:user).order(created_at: :desc)
end

ここまではテーブル構成が同じであれば、非同期でも同期でも同じになリます。
これでページネーションのボタンを押せば自動的に対応するpostsを取ってきてくれるようになります。
通常なら後はviewにpaginate @postsと記述するだけで普通のページネーションが実装できます。

ただ今回は、非同期ページネーションなのでここからが少し違う実装になります。

※そもそも非同期ってなんぞや!って人はこの記事がめちゃわかりやすいので貼っておきます。
初心者目線でAjaxの説明

実装のイメージ

今回の実装イメージを説明しておきます。

上の記事を読んでいただいた方は、分かると思いますが非同期処理はものすごく雑に言うと
ページ全体ではなく、必要な部分だけ画面に反映させると言えます。

なので今回の要件に当てはめると

①反映させる画面をまず決める。今回はposts/index.html.slim
②①で決めたファイルの中で。ポスト一覧部分だけを特定する(今回はidを付与して特定する。)
posts/index.html.slimから先ほど特定したpost一覧部分だけを、スバっと入れ替える。(この部分だけレスポンスを返してあげる)

このようなイメージで実装していきたいと思います!!

実装

では先ほどのイメージ通りに行きたいと思います。

①反映させる画面をまず決める

今回はpostの一覧画面に反映させるのでposts/index.html.slimになります。

ページ全体の画像

Image from Gyazo

コード

.container
  .row
    #posts.col-md-8.col-12
      = render @posts
      = paginate @posts
    .col-md-4.col-12
      - if logged_in?
        .profile-box.mb-3
          = image_tag 'profile-placeholder.png', size: '50x50', class: 'rounded-circle mr-1'
          = current_user.username
      .users-box
        .card
          .card-header
            | ユーザー
          .card-body
            - @dummy_names.each do |name|
              .user.mb-3
                = image_tag 'profile-placeholder.png', size: '40x40', class: 'rounded-circle mr-1'
                = name
          .card-footer
            = link_to 'すべて見る', '#'

このコードが①で説明した部分的に入れ替えられる元になるコードです。
ではこの中から更新したい部分がどこか調べていきましょう。

②①の画像の中でポスト一覧だけを特定する

コードを良く読んでみるとpost一覧はそっくりそのままパーシャルに切り出されているのが分かりました!(4行目)
5行目にはページネーション の記述を予め追加しています。

posts/index.html.slim

.container
  .row
    .col-md-8.col-12
      = render @posts  <= この部分がpost一覧
      = paginate @posts <= ページネーション

つまりこのパーシャルとページネーションの部分を非同期通信でスバっと入れ替えればいい事が分かります。
ではどうやってこの2つの部分を特定するのか?

このような場合はidclassを付与して、入れ替える場所の目安を作ってあげましょう。
今回は#postsというidを親要素に付与します。
※後々にJSでQuerySelectorgetElementbyIdメソッドを使用して場所が特定できます。

.container
  .row
    #posts.col-md-8.col-12
      = render @posts  <= この部分がpost一覧
      = paginate @posts <= これはページネーション

ちょうどこの部分です。

Image from Gyazo

#postsがついているのが分かりますね!これで非同期処理で入れ替える場所の特定が出来ました。

posts/index.html.slimから先ほど特定したポスト一覧部分をズバっと差し替える。

ここまでで差し替える行われるページページ内の差し替える場所の特定ができました。
なので非同期処理を実装していきましょう。

kaminariで非同期処理を行うのは、非常に簡単です。おなじみのremote: trueを付けるだけでjs形式のリクエストを送信する事が出来ます。話が少しそれますが、この実装が終わった後、開発者ツールのNetworkタグのContentTypeを見てみましょう。remote: trueにするとこの項目がhtmlからjavascriptに変更されます。

remote: trueなし
Content-Type: text/html; charset=utf-8

remote: true あり
Content-Type: text/javascript; charset=utf-8

すこし寄り道しましたが、下記の様にremote:trueを付けます

posts/index.html.slim

.container
  .row    
    #posts.col-md-8.col-1
      = render @posts
      = paginate @posts, remote: true

これでJS形式のリクエストを送信する事が出来る様になりました。
またページネーションのボタンを押した時に自動的に呼び出されるテンプレートファイルもアクション名.js.erbが呼び出されるようになります。

このままではどこまで入れ替えるのかちょっと分かり辛いのでこの2つをパーシャルにまとめてしまいます。

posts/_posts_paginate.html.slim

= render posts
= paginate posts, remote: true

posts/index.html.slim


.container
  .row
    #posts.col-md-8.col-12
      = render 'posts_paginate', posts: @posts

これで_posts_paginate.html.slimを差し替える処理をindex.js.erbに書けばいいという事になります。

posts/index.js.erb

var postsPaginate = document.querySelector('#posts'); //ここで設定したidを特定。constでも動くかも
postsPaginate.innerHTML = "<%= j render 'posts_paginate', posts: @posts%> "//innerHTMLで中身を置き換える。
history.replaceState( "", "" ,"?page=<%= @page %>");//リロードできる様にURLを書き換えている

1行目で場所の特定をしています。つまり#postsの場所を指定しています。
2行目でinnerHTMLメソッドを使用して、子要素を入れ替えています。今回子要素は

    #posts.col-md-8.col-12
      = render 'posts_paginate', posts: @posts

パーシャル= render 'posts_paginate', posts: @postsだけになります。
つまり2行目では同じ内容で@postsの中身だけkaminariによって置き換えられているという事です。
3行目では、リロードの処理をするために、replaceStateというメソッドを使用してURLを書き換えています。ちなみに@pageの中身は

def index
 # kaminariの処理
 @page = params[:page]
end

です。ここは本筋とちょっと外れるので説明は割愛して、リンクだけ貼っておきます。
JavaScriptでURLを操作するメモ

ここまで実装出来たら非同期でページネーションが動くようになっているはずです。

非同期処理と言うと、最初はハマりがちなところなのですが
idで置き換える場所を特定する。
②置き換えたい場所をできればパーシャルにしてしまう。(した方がjsのメソッドで置き換えが簡単)
js.erbの中でパーシャル部分をそのまま置き換える。

この考え方で実装するようになってから僕はRailsのSJRに関しては簡単に実装できる様になりました。
最後に参考資料も貼っておくので良かったら読んでみてください。

参考資料

差し替える場所を特定するには

querySelectorメソッドの使い方
【JavaScript入門】getElementByIdを完全理解する3つのコツ!

差し替える為のメソッド

【JavaScript入門】innerHTMLでdivタグ内の要素を取得、設定する方法

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