この記事の内容
TODOリストを共有できるアプリを作っていて、自分のTODO一覧をドラッグ&ドロップで並び替えができる機能を実装しました。
(コンセプトは「人生でやりたいこと100のリストの共有」なので、todoをdreamという言葉を使って表現しています。)
基本的にはこちら【Rails 4で作るドラッグアンドドロップで表示順を変更できるサンプルアプリ(スクリーンキャスト付き)】の記事にしたがって実装していきました。
上記記事はRails4なので、Rails5で変わったところや、自分なりに理解に時間がかかったところなどに適宜説明を入れながら書いてみようと思います。
前提
Rails 5.2.3
jQuery 3.4.1
構成
userの詳細ページ(show)にdreamの一覧が表示されています。
viewの構成としては、
/views/users/show.html.erb
内で同じ階層の_dream.html.erb
が部分テンプレートとして呼ばれ、userのdreamを繰り返し表示しています。
CSSフレームワークはMaterializeを使用しています。
アソシエーション(今回関連するもののみ):
users - has_many :dreams
dreams - belongs_to :user
流れ
- ranked-modelの導入
- jquery-ui-railsの導入
- ルーティング
- Controller
- Viewにカスタムデータ属性を追加
- jQuery
1. ranked-modelの導入
ranked-modelは、row_order
というカラムでレコードをソートし、並び替えた時の全体の再配置やリバランスを自動的に行ってくれるgemです。
gem 'ranked-model'
bundle install
も忘れずに。
次に、migrationファイルを作成して、
dreamsテーブルにrow_order
カラムをinteger型で追加します。
ここに並び順を表す数字が入ります。
そしてmodelです。
include RankedModel
ranks :row_order , with_same: :user_id
これによりrankメソッドが使えるようになります。
今回はuserごとにグルーピングして並び順をつけたいので、with_same: :user_id
を追加します。
これでControllerに以下のように書くことでrow_order順に一覧表示されます。
def show
@dreams = @user.dreams.rank(:row_order)
end
2. jquery-ui-railsの導入
そもそもjQueryUIとは、jQueryをベースにしたJavaScriptのライブラリです。
jquery-ui-railsはそれをrailsに導入するためのgemです。
gem 'jquery-ui-rails'
bundle install
も忘れずに。
jQueryUIのうち今回使うものをapplication.jsに読み込みます。
//= require jquery3
//= require rails-ujs
//= require jquery-ui/widgets/sortable
//= require jquery-ui/effects/effect-highlight
//= require jquery-ui
とすると全てを読み込めますが、重くなってしまうので必要なものだけがいいと思います。
この記述の仕方についてはGitHubに載っています。
3. ルーティング
resources :dreams do
put :sort
end
これにより、PUT /dreams/:dream_id/sort
でdreams#sort
が呼ばれます。
4. Controller
並べ替えが行われた時に実行されるsortアクションを定義します。
def sort
dream = Dream.find(params[:dream_id])
dream.update(dream_params)
render body: nil
end
private
def dream_params
params.require(:dream).permit(:content, :opened, :status, :row_order_position)
end
render body: nil
はアクション実行後にViewをレンダリングしたくない時に使います。Rails5からそれまでより少し記述が変わったようです。
dream_params
の中身のrow_order_position
について、カラム名はrow_order
なんですが、_position
をつけないといけません。これはgemの仕様みたいです。
5. Viewにカスタムデータ属性を追加
jQueryでajaxを行う際に必要になるので、カスタムデータ属性を設定しておきます。
<%= content_tag "tr", id: "dream-#{dream.id}", class: "item", data: { model_name: dream.class.name.underscore, update_url: dream_sort_path(dream)} do %>
(当初は普通に<tr>タグの中に変数を<%= %>で埋め込んで実行してみたのですが、どこかで間違えたのかエラーになってしまったので、content_tag
を使っています。)
このdata-model_name
とdata-update_url
が次の項目でmodelName
、updateUrl
となって出てきます。
dream.class.name.underscore
ですが、
class
とname
はrubyのメソッドで、それぞれオブジェクトのクラスとその名前を取得します。
underscore
はrailsのメソッドで、クラス名をファイル名に変換します。
(例:「Product.underscore」→「product」、「AdminUser.underscore」→「admin_user」)
6. jQuery
並べ替えの実装をするtable_sort.jsを作成します。
$(function(){
$('#dreams_list').sortable({ ★1
update: function(e, ui){ ★2
let item = ui.item; ★3
let item_data = item.data(); ★4
let params = {_method: 'put'}; ★5
params[item_data.modelName] = { row_order_position: item.index() } ★6
$.ajax({
type: 'POST',
url: item_data.updateUrl, ★7
dataType: 'json',
data: params ★8
});
},
stop: function(e, ui){ ★9
ui.item.children('td').not('.item__status').effect('highlight', { color: "#FFFFCC" }, 500)
}
});
});
★1
jQueryUIのsortableを使います。
sortableメソッドは、デフォルトで「ターゲット要素直下の子要素全て」を並べ替えの対象とするので、今回は並べ替え対象となるlistの親要素となっている#dreams_list
を選択しました。
もし「子要素全て」以外を並べ替え対象とするなら、updateの前にitems: ''
でその要素を指定します。
★2
updateパラメーターにはドラッグで並び順が変更されたタイミングで呼び出されるイベントを指定します。
第1引数はイベントオブジェクト、第2引数には、以下のプロパティを持ったオブジェクトです。
★3
上記uiで取得したデータのうちのitemを変数itemに代入します。
★4
上記で定義したitemのdata属性をitem_dataに代入します。
itemの中にもたくさんのデータが入っています。
その中のdatasetというところに入っているようです。
console.log(item)
で確認したところdatasetの内容は以下のようになっていました。
dataset: DOMStringMap
modelName: "dream"
updateUrl: "/dreams/58/sort"
ここで、Viewで設定したdata属性が返ってきているのがわかります。
★5
後ほどajaxでdataとして送るparamsを定義します。
まずは_method
を使い、PUTだよという情報を入れてあげます。
(指定しないとGETかPOSTしか送れないようです。まだきちんと調べられていません…)
★6
先ほどのparamsに追加で情報を入れます。
params[item_data.modelName]
は、★4で確認したようにdream
が入ります。
{row_order_position: item.index()}
はindex()
メソッドを使って、その要素がjQueryオブジェクトの何番目か(0から数えて)を取得します。
つまり、paramsにdream: {row_order_position: 0(例)}
というようなデータが渡されます。
★7
★4で確認したように、/dreams/1(例)/sort
のような形になります。
★8
結果としてparamsの中身は{_method: ‘put’, dream: {row_order_position: 0(例)}}
となります。
★9
stopパラメーターは並び替えが終了したときに呼び出されます。
今回はitem__status
クラス以外にハイライトが当たるようにしています。
補足
当初ドラッグ&ドロップはできるが、その結果が保存されずリロードすると元に戻るという状態でした。
要因は、rails-ujsを読み込んでいなかったことです。
//= require rails-ujs
についてはこちらの記事を参考にしました。
読み込む順番に注意。
//= require jquery3
//= require rails-ujs
//= require jquery-ui/widgets/sortable
//= require jquery-ui/effects/effect-highlight
//= require_tree .
完成!
並び替えが実装できました!
完成版のコードはこちらをご参照ください。
分かりにくい点や間違い等ありましたらご指摘お願いいたします。
参考
https://qiita.com/jnchito/items/391fb16d3f69fda9bdae
http://stacktrace.jp/jquery/ui/interaction/sortable.html
https://qiita.com/ichikawa-hiroki/items/f5df892ff85afea51b8c