3
4

More than 3 years have passed since last update.

【スマホ対応】ドラッグ&ドロップで連番の並び替えをする。「acts_as_list」+「Sortable.js」

Last updated at Posted at 2021-02-10

ドラッグ&ドロップで自動連番の処理を実装する

イメージ

Image from Gyazo

実装経緯

  • 並び替え機能はメニュー表など管理画面で実装頻度が高かった。
  • 管理画面もスマホやタブレットに対応してほしいという声も多かった。

実装以前は、編集フォームに遷移して順番(position)カラムの整数値をセレクトフィールドで選択して更新していました。
また、自動連番処理はモデル側でしてました。

知識を整理するため簡単なアプリ上に作ってみようと思います。

使用するライブラリ

  • acts_as_list
    順番の並び替えの制御を担当する。
    同ポジションにパフォーマンスが上のranked-modelがある。
    今回acts_as_listを使用した経緯

    • レコード数が少ない
    • 今後もレコード数は多くはならない
    • 少ないレコード数なので順番の値を把握しておきたい。 ※レコード数が多く順番の値を気にしない場合はranked-modelを選択した方がよさげ
  • SortableJS
    ドラッグ&ドロップで要素の並び替えを担当する
    今回SortableJSを使用した経緯

    • jQueryUIを使わずに実装したかった。(jQuery UIのドラッグ操作がスマホ対応していないのが難点)
    • スマホ特有のドラッグ操作をhandleで制御出来る

実装

1. 順番(position)カラムの追加

今回はMenuモデルにpositionカラムを追加する形で作っていきます

class AddPositionToMenu < ActiveRecord::Migration[6.1]
   def change
     add_column :menus, :position, :integer, null: false, default: 0
     add_index :menus, :position # ここちょっと怖い
   end
 end

※ ここでindexにuniqueを設定するとacts_as_listの仕様で順番を更新できなくなります。

2. acts_as_listを追加
    gem "acts_as_list"

bundle installする

  • 対象のモデルにacts_as_listを記述する
class Menu < ApplicationRecord
   acts_as_list
   validates :title, presence: true, length: { maximum: 50 }           validates :title, presence: true, length: { maximum: 50 }
3. jqueryを追加
  • ajax処理が書きやすい(慣れている)ため導入します

yarn add jquery

  • jqueryの初期化 config/webpack/environment.js
const { environment } = require('@rails/webpacker')
// jqueryの設定
const webpack = require('webpack')
environment.plugins.prepend('Provide',
    new webpack.ProvidePlugin({
        $: 'jquery/src/jquery',
        jQuery: 'jquery/src/jquery'
    })
)
module.exports = environment

4. SortableJsの追加
  • 導入
    yarn add sortablejs

  • view設定
    id: js-sortable-menus内をソートの範囲
    class: i.handleをドラッグ&ドロップのトリガーにする
    app/views/menus/index.html.haml

  %tbody#js-sortable-menus
    - @menus.each do |menu|
      %tr
        %td
          %i.handle
           .fa.fa-list-ul
        %td= menu.title
  • sortablejsの設定 app/javascript/packs/views/menus/index.js
import Sortable from 'sortablejs';

$(function() {
    const el = document.getElementById('js-sortable-menus');
    new Sortable(el, {
        handle: "i.handle",
        axis: 'y',
        animation: 300,
        onUpdate: function (evt) {
            return $.ajax({
                url: `/api/menu/positions/${evt.oldIndex}`,
                type: 'patch',
                data: {
                    from: evt.oldIndex,
                    to: evt.newIndex
                }
            });
        }
    });
});

handle:でトリガーになる要素を設定
oldIndexは移動前の画面上の順番(0から始まる)
newIndexは移動後の画面上の順番(0から始まる)

  • api作成 まずnamespaceを使ってapiのroutesを記述する config/routes.rb
Rails.application.routes.draw do
  root to: "static_pages#index"
  resources :menus

  namespace :api do
    namespace :menu do
      resources :positions, only: [:update]
    end
  end
end
  • apiコントローラーの作成 app/controllers/api/menu/positions_controller.rb
class Api::Menu::PositionsController < ApplicationController
  skip_before_action :verify_authenticity_token

  def update
    menu = Menu.find_by!(position: params[:from].to_i + 1) # 0から始まるので+1する
    menu.update!(position: params[:to].to_i + 1) # 0から始まるので+1する
    head :ok
  end
end

skip_before_action :verify_authenticity_tokenCSRF トークンの検証をスキップする

5. 順番(position)カラム順に出力する

app/models/menu.rb

scope :sorted, -> { order(position: :asc) }

app/controllers/menus_controller.rb


  # GET /menus or /menus.json
  def index
    @menus = Menu.sorted
  end

app/views/menus/index.html.haml

順番を並び替えるviewにjsファイルを読み込ませる

= javascript_pack_tag "views/menus/index", "data-turbolinks-track": "reload"
%h1 Listing menus

%table
  %thead
    %tr
      %th
      %th Title
      %th Price
      %th
      %th
      %th

  %tbody#js-sortable-menus
    - @menus.each do |menu|
      %tr
        %td
          %i.handle
            .fa.fa-list-ul
        %td= menu.title
        %td= menu.price
        %td= link_to "Show", menu
        %td= link_to "Edit", edit_menu_path(menu)
        %td= link_to "Destroy", menu, method: :delete, data: { confirm: "Are you sure?" }

%br

= link_to "New Menu", new_menu_path

成果

Demo動作確認
※Herokuの無料プランなので立ち上がりに時間がかかる時があります。

Image from Gyazo
Github

参考にさせて頂いた記事

自社の公開記事から

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4