#340 DataTables
- Download:
- source codeProject Files in Zip (84.3 KB)
- mp4Full Size H.264 Video (30.1 MB)
- m4vSmaller H.264 Video (14.9 MB)
- webmFull Size VP8 Video (13.7 MB)
- ogvFull Size Theora Video (37 MB)
DataTableを使えば通常のHTMLテーブルに、ページ区切り、ソート、検索の各機能を付加することができ、それらすべてをJavaScriptで実装します。今回のエピソードではその設定方法と、Railsアプリケーションからデータを供給する方法を紹介します。下に示したページにはProductのレコードで構成された通常のテーブルが表示されています。これにDataTableを追加して機能を改善します。
アプリケーションにDataTableを追加する
ソースコードをダウンロードしてRailsアプリケーションで使用することもできますが、画像ファイルへの相対パスによる参照がコードに含まれていて、asset pipelineに追加するためにはそれを修正する必要があり、これは少し難しい作業になります。幸いにもjquery-datatables-rails
というgemがあり、これを利用することでその作業をずっと簡単なものにしてくれます。gemfileでこれをassets
グループに追加します。このgemはかなり頻繁に更新されていて、なるべく最新版を使用したいので、github
オプションを使用してプロジェクトのパスを渡します。
group :assets do gem 'sass-rails', '~> 3.2.3' gem 'coffee-rails', '~> 3.2.1' # See https://github.com/sstephenson/execjs#readme for more supported runtimes # gem 'therubyracer', :platform => :ruby gem 'uglifier', '>= 1.0.3' gem 'jquery-datatables-rails', github: 'rweng/jquery-datatables-rails' end
github
オプションはBundler 1.1で導入された新機能です。このバージョンを持っていない場合は、代わりにgitのフルパスを渡す必要があります。いつもと同じようにgemを追加するときはbundle
コマンドを実行してインストールをおこないます。次にDataTableをasset pipelineと一緒に動作するように設定します。まずapplication.js
ファイルを開いて、manifestでdataTables/jquery.dataTables
の呼び出しを追加します。
//= require jquery //= require jquery_ujs //= require dataTables/jquery.dataTables //= require_tree .
インクルードするのためのCSSもついているので、application.css
にも何らか追加する必要があります。
/* * This is a manifest file that'll automatically include all the stylesheets available in this directory * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at * the top of the compiled file, but it's generally better to create a new file per style scope. *= require_self *= require dataTables/jquery.dataTables *= require_tree . */
テーブルにDataTableを追加する
アプリケーションにDataTableをインストールできたので、これを使って商品テーブルを改善します。このテーブル用のビューのコードを下に示しますが、中身はごく簡単です。おこなっていることは各商品の間をループしてテーブルに出力しているだけです。
<h1>Products</h1> <table> <tr> <th>Product Name</th> <th>Category</th> <th>Release Date</th> <th>Price</th> </tr> <% @products.each do |product| %> <tr> <td><%= link_to(product.name, product) %></td> <td><%= product.category %></td> <td><%= product.released_on.strftime("%B %e, %Y") %></td> <td><%= number_to_currency(product.price) %></td> </tr> <% end %> </table>
このテーブルでDataTableを利用できるようにするためには、ヘッダ行をthead
要素で、ボディセクションをtbody
要素でラップします。またJavaScriptからこのテーブルを参照できるようにid
を付けます。
<h1>Products</h1> <table id="products"> <thead> <tr> <th>Product Name</th> <th>Category</th> <th>Release Date</th> <th>Price</th> </tr> </thead> <tbody> <% @products.each do |product| %> <tr> <td><%= link_to(product.name, product) %></td> <td><%= product.category %></td> <td><%= product.released_on.strftime("%B %e, %Y") %></td> <td><%= number_to_currency(product.price) %></td> </tr> <% end %> </tbody> </table>
これでCoffeeScriptコードを使ってテーブルにDataTableを追加することができます。これをproducts.js
ファイルに記述します。必要な内容は、DOMがロードされたことを確認した後にテーブルに対してdataTable
を呼び出すことだけです。
jQuery -> $('#products').dataTable()
ページをリロードするとテーブルの外観はかなり変わりました。DataTableによって改ページ、ソート、検索の機能が付加されました。
DataTableのカスタマイズ
dataTableに対して指定できるオプションは多くの種類があり、それらを使ってテーブルの見え方や振る舞いを変更することができます。例えばsPaginationType
を設定すると改ページの振る舞いが変わります。これをfull_numbers
と指定すると各ページへのリンクが表示されます。DataTableのサイトの利用方法のセクションには、DataTableのカスタマイズに利用できる全オプションのリストがあります。
jQuery -> $('#products').dataTable sPaginationType: "full_numbers"
ページをリロードすると新規の改ページのスタイルが表示されます。
スタイルを改善する
テーブルのスタイルは完璧ではないですが、これに変更を加えることができます。DataTableのサイトにはきれいにスタイル設定されたサンプルのテーブルがあるので、これを今回のテーブルに使用します。サンプルテーブルはjQuery UIを使用していますが、これはgemfileに含まれているjquery-rails
gemに入っています。しかし我々のテーブルに必要なCSSスタイル設定は含まれていません。そこでjquery-ui-rails
というもう一つのgemを利用します。これを使えるようにするには、gemfileに追加して再度bundle
コマンドを実行します。
group :assets do gem 'sass-rails', '~> 3.2.3' gem 'coffee-rails', '~> 3.2.1' # See https://github.com/sstephenson/execjs#readme for more supported runtimes # gem 'therubyracer', :platform => :ruby gem 'uglifier', '>= 1.0.3' gem 'jquery-datatables-rails', github: 'rweng/jquery-datatables-rails' gem 'jquery-ui-rails' end
アプリケーションにCSSを追加するには、application.cssファイルにjquery.ui.core
とjquery.ui.theme
を追加します。これによってSmoothnessテーマがインクルードされます。また、DataTableのCSSファイルもjQuery UI用に設計されたものに置き換えます。src
ディレクトリの下にdemo_table_jui
というファイルが準備されています。jQuery.dataTables
のCSSをこのファイルで置き換えます。
/* * This is a manifest file that'll automatically include all the stylesheets available in this directory * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at * the top of the compiled file, but it's generally better to create a new file per style scope. *= require_self *= require jquery.ui.core *= require jquery.ui.theme *= require dataTables/src/demo_table_jui *= require_tree . */
dataTable
の呼び出しにbJQueryUI
オプションを追加します。
jQuery -> $('#products').dataTable sPaginationType: "full_numbers" bJQueryUI: true
最後にテーブルにdisplay
というclass
を設定します。
<table id="products" class="display">
ここでページをリロードすると、ずっときれいなテーブルが表示されました。
サーバからのデータでテーブルを更新する
テーブルに改ページ、ソート、検索の機能が付きましたが、これらは現状すべてクライアント側で発生しています。HTMLページにはデータベースにあるすべての商品が含まれており、DataTableがページ、ソート順、検索語に基づいて表示されるべきものを選択します。これはレコードが少ない場合(200件以下程度)はうまく動作しますが、もしレコードが千や万の単位だった場合にはすべてをクライアントに送信して処理させる方法はとりたくありません。このような場合には必要なレコードのみをクライアントに送信し、選択されたページやソートフィールドや検索語が変わったときに、DataTableにサーバと通信させるようにします。ドキュメントのサーバサイド処理に関するページを見ると、サーバに送信されるパラメータとJSONによるレスポンスについての説明があります。Railsアプリケーションをこれに対応した形にしてサーバ側で改ページ処理をしたいので、gemfileにwill_paginate
を追加し、再度bundleコマンドを実行してインストールします。
gem 'will_paginate'
サーバから必要な分だけを取得できるようになり、テンプレートですべての商品を表示する必要がなくなりました。関連する商品のデータセットを取得するためのURLを定義し、table
の開始タグのデータ属性にこれを置きます。products_url
を使用して、JSONフォーマットを使用するよう指定します。
<h1>Products</h1> <table id="products" class="display" data-source="<%= products_url(format: "json") %>"> <thead> <tr> <th>Product Name</th> <th>Category</th> <th>Release Date</th> <th>Price</th> </tr> </thead> <tbody> </tbody> </table>
CoffeescriptファイルでdataTableの呼び出しにいくつかのオプションを追加して、サーバからデータを取得するよう指示します。
jQuery -> $('#products').dataTable sPaginationType: "full_numbers" bJQueryUI: true bProcessing: true bServerSide: true sAjaxSource: $('#products').data('source')
bProcessing
オプションはサーバからデータを取得する間“Processing(処理中)”のメッセージを表示します。bServerSide
をtrueに設定することによって、データはサーバから取得されるようになります。最後にsAjaxSource
はデータを取得するためのURLを設定し、これをテーブル内で定義したdata-source
属性の値に設定します。DataTableがProductsController
のindex
アクションを起動し、JSONレスポンスを受け付けます。これをおこなうためにrespond_to
ブロックを使用します。難しいのは、どうやって正しい形式のJSONを返すかという部分です。一つの方法は、JbuilderやRABLなどを使用するやり方ですが、これらを使った場合、正しいレスポンスを生成するためには複雑なロジックが必要になるので、すぐにコードがわかりにくくなってしまいます。
もし、RailsのMVC構造には限界があってここでやろうとしていることには合わないと感じているのなら、コードをシンプルにするために新規にクラスを作ることも悪くありません。今回はこのアプローチをとることにします。JSONを生成するために新たにProductsDatatable
クラスを作成し、ヘルパーメソッドにアクセスできるようにコンストラクタにビューコンテキストを持たせます。これによって、JSONレスポンス用のミニプレゼンターの役目を果たします。このクラスをappディレクトリ下に新規に作成したdatatables
ディレクトリに作成します。コード量が多いのでここですべてを示すことはできませんが、Githubで全体を見ることができます。ここでは全体は簡単に流して重要な部分のみを見ていきます。
initializer
はビューコンテキストを引数にとり、それをインスタンス変数に割り当てます。ここから簡単に呼び出せるようにいくつかのヘルパーメソッドをこのビューに委譲します。
delegate :params, :h, :link_to, :number_to_currency, to: :@view def initialize(view) @view = view end
as_json
メソッドは、コントローラのrender_json
の呼び出しによって裏で起動されます。これによって、DataTableが必要とする、データベースからの関連する行を含んだすべてのデータが返されます。このデータはdata
メソッドから生成されますが、このメソッドは各商品の間をループしてテーブルの各セル用の情報を含んだ二次元の配列を返します。
def as_json(options = {}) { sEcho: params[:sEcho].to_i, iTotalRecords: Product.count, iTotalDisplayRecords: products.total_entries, aaData: data } end private def data products.map do |product| [ link_to(product.name, product), h(product.category), h(product.released_on.strftime("%B %e, %Y")), number_to_currency(product.price) ] end end
商品は、fetch_products
メソッドで取得されます。これはデータの正しいページを正しい並びで取得し、検索語がある場合にはそれによってフィルタがかけられます。検索クエリ自体は単純ですが、SphinxやSolrなどの全文検索エンジンを使うなどして機能を拡張することも可能です。
def fetch_products products = Product.order("#{sort_column} #{sort_direction}") products = products.page(page).per_page(per_page) if params[:sSearch].present? products = products.where("name like :search or category like :search", search: "%#{params[:sSearch]}%") end products end
クラスの定義の残りの部分は並び替えと改ページ用のヘルパーメソッドで構成されています。完成したこのクラスをコントローラアクションで利用してみます。
def index respond_to do |format| format.html format.json { render json: ProductsDatatable.new(view_context) } end end
サーバを再起動してページをリロードすると、前とまったく同じように動作します。改ページやソート、検索の振る舞いは前のままですが、データはクライアント側に一度で読み込んだものではなく、Railsアプリケーションから供給されています。
DataTableに関する今回のエピソードは以上です。サーバサイドでの処理が必要な場合は多少の手間がかかりますが、得られる成果を考えればその価値は十分あるでしょう。これに似たものをゼロから作成する別の方法に興味があれば、エピソード240を参照してください。