#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を参照してください。


