Kaminariをjsonでajax化する
Kaminariは便利だがネット上のソースを見るとjs.erbで対応しているものが多い。
(というかKaminariに限らずajaxはjs.erbばかりだった)ので、備忘録も兼ねて。
手順
はじめに
まずは普通に実装。Kaminariを使えば超手軽にページネーションが実装できる。 このあたりの説明は割愛。
コントローラー。
# app/controllers/books_controller.rb def index @books = Book.page(params[:page]).per(10) end
次にビュー。パーシャル化しておく。
# app/views/books/index.html.erb <div id="paginator"> <%= render 'paginator' %> </div> <div id="books"> <%= render 'book' %> </div>
# app/views/books/_paginator.html.erb <%= paginate @books %>
# app/views/books/_book.html.erb <% @books.each do |book| %> <%= book.title %> <% end %>
ajax化
ここからjsonで動くようにする。
# app/views/books/_paginator.html.erb <%= paginate @books, remote: true %>
remote: true
でリクエストがajaxで処理されるようになる。
コントローラーではajaxの場合のみpartialを返すようにする。
# app/controllers/books_controller.rb def index @books = Book.page(params[:page]).per(10) # view_contextでpaginateメソッドを使いパーシャルの中身と同じものを生成 paginator = view_context.paginate( @books, remote: true ) # render_to_stringでパーシャルの中身を生成 books = render_to_string( partial: 'book', locals: { books: @books } ) #リクエストを確認しajaxだった場合にはjsonで各パーシャルをクライアントへ返す if request.xhr? render json: { paginator: paginator, books: books } end end
最後にcoffee(js)側でサーバから受け取った値を画面に表示する。
// app/assets/javascripts/books.js.coffee $ -> // paginationのリンクからajax:successが発火したら処理を開始 $('#paginator').on 'ajax:success', 'li a', (e, result, status, xhr) -> // controllerでresultの中に入れたpaginatorとbooksをそれぞれ表示する $('#paginator').html result.paginator $('#books').html result.books
これでjs.erbを書くことなくajaxでKaminariが動く。
リファクタリング(おまけ)
とりあえず動くが同じアクション内でリクエストにより挙動が変わるのは望ましくない。
また、コントローラーとビューで記述がやや重複しておりDRYではないのでこれも変更。
まずはビューを変更する。パーシャルの中身はjsonで受け取るため不要。
# app/views/books/index.html.erb <div id="paginator"> </div> <div id="books"> </div>
ページネーターはajaxで生成するので削除。
$ rm app/views/books/_paginator.html.erb
次にコントローラーのアクションを分ける。
# app/controllers/books_controller.rb before_action :set_books, only: [:index, :ajax] def index end def ajax # view_contextでpaginateメソッドを使いパーシャルの中身と同じものを生成 paginator = view_context.paginate( @books, remote: true, url: books_ajax_url # Kaminariのリンク先を変える ) # render_to_stringでパーシャルの中身を生成 books = render_to_string( partial: 'book', locals: { books: @books } ) #リクエストを確認しajaxだった場合にはjsonで各パーシャルをクライアントへ返す if request.xhr? render json: { paginator: paginator, books: books, success: true # クライアント(js)側へsuccessを伝えるために付加 } end end private def set_books @books = Book.page(params[:page]).per(10) end end
あとはcoffee(js)に処理を追加する。
最初に表示した段階から読み込みをしなければならず、表示されないと発火トリガーとなるページネーターも生成されないので注意が必要。
// app/assets/javascripts/books.js.coffee $ -> // paginationからajax:successが発火したら処理を開始 $('#paginator').on 'ajax:success', (e, result, status, xhr) -> // controllerでresultの中に入れたpaginatorとbooksをそれぞれ表示する $('#paginator').html result.paginator $('#books').html result.books // 読み込みした後にajaxでサーバへリクエストを投げる $.ajax( url: "/books_ajax", // ajaxアクションを明示 success: (data, status, xhr) -> // ajaxの処理が成功した場合 if data.success $("paginator").trigger('ajax:success', [ data, status, xhr]) // "#paginator"の中で"ajax:success"を発火させる end )
最後のtriggerの部分とかajax:successを渡す部分でかなり躓いた。