JQuery UI Sortable を使ってドラックアンドドッロプでRuby on Railsの一覧表示の順を設定する

お仕事で、データの並び順を簡単に設定出来る方法を考えていたら、JQuery UI のSortable が使えるのでは? と思い試してみました。


下の画像は、 Ruby on Railsの Scaffold で作ったアプリの一覧画面に Sortable を組込、一覧の行をドラックし適当な場所にドロップするところです。


コード

jQuery UI

jQUeryと JQueryUI (最低限 UI Core, Draggable, Sortable) をダウンロドし public/javascripts に追加します。
これらの Javascriptを使えるようにレイアウトに追加

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">
   ・・・・ 省略 ・・・・
  <%= javascript_include_tag 'jquery-1.3.2.min' %>
  <%= javascript_include_tag 'jquery-ui-1.7.2.custom.min' %>
</head>
index.html.erb

一覧表示のテンプレートには Sortableの対象になる tbodyタグを置き id を付け、 $("#対象のid").sortable(); で対象をSotableにするだけです。超簡単!
ただし、並び順の変更結果が判るようにドラック対象になる trタグには "row_" + レコードのID番号 の id を付加しています。

<h1>Listing todos</h1>

<table>
  <thead>
  <tr>
    <th>Due</th>
    <th>Task</th>
  </tr>
  </thead>
  <tbody id="sortable">
<% @todos.each do |todo| %>
  <tr id="row_<%= todo.id%>">
    <td><%=h todo.due %></td>
    <td><%=h todo.task %></td>
    <td><%= link_to 'Show', todo %></td>
    <td><%= link_to 'Edit', edit_todo_path(todo) %></td>
    <td><%= link_to 'Destroy', todo, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
  </tbody>
</table>

<br />

<%= link_to 'New todo', new_todo_path %>

<script type="text/javascript">
$(function() {
	$("#sortable").sortable({
	   cursor: 'move',
	   start: function(event, ui) {ui.item.css("background-color", "orange");},
	   stop: function(event, ui) {
	     ui.item.css("background-color", "white");
	     jQuery.post('<%=reorder_todos_path%>', $(this).sortable( 'serialize'), null, 'text');
	   }
	});
	$("#sortable").disableSelection();
});
</script>

ただし、ここでは

1. ドラック中は行の背景色をオレンジに変える為に、 startイベント、stopイベントで background-color を変えています。

2. ドロップした時点での並び順をサーバー側に送っています。 $(this).sortable( 'serialize') の値は "row=4&row=2&row=3&row=1" のような文字列で Rails で受け取ると "row"=>["4", "2", "3", "1"] のようにレコードのID番号の配列になります。

コントロラー
class TodosController < ApplicationController
  def index
    @todos = Todo.all(:order => 'seq')
  end

  def reorder
    params[:row].each_with_index {|row, i| Todo.update(row, {:seq => i})}
    render :text => "OK"
  end

コントロラーの reorderアクションでは params[:row] で受け取ったID番号の配列を使い、各レコードの seqの値を更新します。

注意

  • テーブルには表示順のカラム seq:integer を追加して下さい。
  • このコードはCSRF対策を考慮してないので application_controller.rb の protect_from_forgery をコメントアウトして下さい。実アプリで使うさいにはここはちゃんとしないとダメです!
  • config/route.rb に reorder アクションを追加して下さい
  map.resources :todos, :collection => {:reorder => :post}