Shred IT!!!!

IT全般について試したこと・勉強したことを綴ったり、趣味について語るブログ

Rails | Ajax で動的な検索・ソート機能付きページネーションを実装する(jQuery DataTablesプラグイン連動)

概要

Rails でページング処理といえば、kaminari だと思う。
github.com

kaminari で Ajax のページングもできるようだが、
AdventCalendar - kaminari徹底入門 - Qiita

jQuery の DataTables プラグインと連携して、ページング(一覧画面)を実装する。
(ページ送りは kaminari 使います・・・) github.com
この gem を利用することで jQuery の DataTables プラグインを簡単に導入できる。

DataTables プラグイン自体のサンプルは下記。
DataTables example - Ajax data source (arrays)

検索・ソート・件数指定など、javascript 側で実装してくれている。
件数が少ないようであれば、全件データを JSON形式で DataTables に渡してしまえば、 上記のサンプルのように軽快な動作で使い勝手が良い。

件数が多い場合はサーバへ Ajax 通信しながら表を作る。
DataTables example - Server-side processing

ソート条件や検索ワードなどがサーバ側へ送られて来るので、 それらの対応をして JSON 形式でデータを返してやればいい。

今回は Ajax 通信しながら動的に表を作る方の実装を行う。

準備

Gemfile に下記を追加。

gem 'kaminari'
gem 'jquery-datatables-rails'

インストールする。

$ bundle install
...
Installing kaminari 0.16.3
...
Installing jquery-datatables-rails 3.3.0
...

rweng/jquery-datatables-rails · GitHub
手順にある通り、generator からのインストール。

$ bundle exec rails generate jquery:datatables:install
      insert  app/assets/javascripts/application.js
      insert  app/assets/stylesheets/application.css

下記ファイルに下記内容が追加されていることを確認する。

$ less app/assets/javascripts/application.js
//= require dataTables/jquery.dataTables

$ less app/assets/stylesheets/application.css
*= require dataTables/jquery.dataTables

bootstrap 3 をインストール。

bundle exec rails generate jquery:datatables:install bootstrap3
      insert  app/assets/javascripts/application.js
      insert  app/assets/stylesheets/application.css

下記ファイルに下記内容が追加されていることを確認する。

$ less app/assets/javascripts/application.js
//= require dataTables/jquery.dataTables
//= require dataTables/bootstrap/3/jquery.dataTables.bootstrap

$ less app/assets/stylesheets/application.css
*= require dataTables/bootstrap/3/jquery.dataTables.bootstrap

本家の bootstrap 3 を配置する。

Bootstrap · The world's most popular mobile-first and responsive front-end framework.
上記から bootstrap 3 をダウンロード。

# 下記のようにファイルを配置する
app/assets/javascripts/bootstrap.min.js
app/assets/stylesheets/bootstrap.min.css

実装

実装に入るが一覧ページを新たに作るのもダルいので、 前にこのブログで作ったページを利用する。

Railsで階層化された複数モデルに対応するフォームの作り方 - Shred IT!!!!
Railsで階層化された複数モデルに対応するフォームの作り方【JavaScript/CoffeeScriptによる動的処理追加】 - Shred IT!!!!

ルーティング

index とは別に list というルーティングを新たに追加。

# config/routes.rb 一部抜粋
  resources :events do
    collection do #追加
      get :list #追加
    end #追加
  end

コントローラー

list メソッドを実装。
EventsDatatableはパラメーターを受け取って、それに従った SQL を実行し、JSON 形式に変換するクラス。

# app/controllers/events_controller.rb 一部抜粋
  def list
    respond_to do |format|
      format.html
      format.json {render json: EventsDatatable.new(params) }
    end
  end

ビュー

必要最低限の項目を作成。
あとは jQuery DataTables プラグインが色々な機能を足してくれる。

# app/views/events/list.html.erb を追加
<table id="events" class='table table-striped table-bordered'>
  <thead>
    <tr>
      <th>ID</th>
      <th>Name</th>
    </tr>
  </thead>
  <tbody>
  </tbody>
</table>

JSON変換クラス

一番大きい実装(モデルに実装できる内容だが、何も考えず切り分けた )。
受け取ったパラメーターから条件に合うデータを取得、結果を JSON 形式にする。

まずはAjax 通信で飛んでくるパラメーター。

{
  "draw" => "1", 
  # 今回の実装では columns 内 data  の項目しか使っていない
  # 項目毎に検索対象やソート対象にするか、とかのオプション指定ができると思われる
  "columns" => {
    "0" => {"data"=>"id", "name"=>"", "searchable"=>"true",
            "orderable"=>"true", "search"=>{"value"=>"", "regex"=>"false"}},
    "1" => {"data"=>"name", "name"=>"", "searchable"=>"true",
            "orderable"=>"true", "search"=>{"value"=>"", "regex"=>"false"}}
  },
  # どのカラムを昇順・降順にするか
  "order"=>{"0"=>{"column"=>"0", "dir"=>"asc"}},
  # ページ数と1ページに取得する件数
  "start"=>"0", "length"=>"10", 
  # 検索キーワード
  "search"=>{"value"=>"hoge", "regex"=>"false"}, "_"=>"14328624114557"}

パラメータを解釈して、データを取得するクラス。

# app/datatables/events_datatable.rb 追加
# -*- coding: utf-8 -*-
#
class EventsDatatable
  attr_accessor :params

  def initialize(params)
    @params = params
  end

  # jQuery DataTables へ渡すためのハッシュを作る
  # 補足:コントローラーの render json: で指定したオブジェクトに対して as_json が呼び出される
  def as_json(options = {})
    {
      recordsTotal: Event.count, # 取得件数
      recordsFiltered: events.total_count, # フィルター前の全件数
      data: events.as_json, # 表データ
    }
  end

  def events
    @events ||= fetch_events
  end

  # 検索条件や件数を指定してデータを取得
  def fetch_events
    Event.where(search_sql).order(order_sql).page(page).per(per)
  end

  # カラム情報を配列にする
  def columns
    return [] if params["columns"].blank?
    params["columns"].map{|_,v| v["data"]}
  end

  # 検索ワードが指定されたとき
  def search_sql
    return "" if params["search"]["value"].blank?
    search = params["search"]["value"]
    # name カラム固定の検索にしている
    # "name like '%hoge%'"のようにSQLの一部を作る
    "name like '%#{search}%'"
  end

  # ソート順
  def order_sql
    return "" if params["order"]["0"].blank?
    order_data = params["order"]["0"]
    order_column = columns[order_data["column"].to_i]
    # "id desc" のようにSQLの一部を作る
    "#{order_column} #{order_data["dir"]}"
  end

  # kaminari 向け、ページ数
  def page
    params["start"].to_i / per + 1
  end

  # kaminari 向け、1ページで取得する件数
  def per
    params["length"].to_i > 0 ? params["length"].to_i : 10
  end

end

coffeescript

下記を追加。

# app/assets/javascripts/events.js.coffee
jQuery ->
  $('#events').dataTable
    "processing": true, # 処理中の表示
    "serverSide": true, # サーバサイドへ Ajax するか
    "ajax": "list", # Ajax の通信先
    "columns": [ # 扱うカラムの指定
      { "data": "id" },
      { "data": "name" },
    ]

オートロード追加

追加した JSON 変換クラスをオートロードさせる。

# app/config/application.rb
...
  class Application < Rails::Application
    config.autoload_paths += %W(#{config.root}/app/datatables) # 追加
...

実装完了

これで実装は終わり。
見づらいが、動作している画面をキャプチャにする。
ページ送り・ソート・検索などで都度 Ajax 通信で JSON を取得している。

f:id:jetglass:20150526170200g:plain

まとめ

jQuery DataTables プラグインを使うことで検索・ソート・ページ送りなどの機能をビュー側で実装する必要がなくなるので、手っ取り早く一覧画面を作りたいときにおすすめ。

今回の実装はほぼ初期設定だが、それでも十分だと感じた。

編集ボタンや削除ボタンの HTML を表データとして JSON にして返却すれば、 ボタンの設置も可能。

jQuery DataTables プラグインは高機能だと思われるので、 必要に応じてリファレンス読み込んで機能追加するといい。
Reference

新規追加・編集・削除も Ajax で動的にできるようだ。
https://editor.datatables.net/examples/simple/simple