LoginSignup
3
1

More than 1 year has passed since last update.

初めに

フロントはVue.jsで開発し、サーバーサイドはRailsを使用しているアプリに
ドラッグ&ドロップ機能を追加するため、Vue.Draggableを導入した時のお話です。

コードはかなり端折っていますが、実装の流れを備忘録で残します。
Draggablegithubこちらにあります。

環境

ruby 3.1.0
Rails 7.0.4
vue@3.2.41

インストール

著者はimportmapを使用しているので以下のコマンドでpinします。
そうすると、importmap.jsに自動書き込みが走ります。

./bin/importmap pin vuedraggable@next --download

使い方

その後、draggableを使用可能にするために読み込み、
コンポーネントとして使用できるようにします。

import Draggable from "vuedraggable";

// 略
 components: {
    Draggable,
  },

使い方は簡単で以下のように、ドラッグさせたいコンポーネントを <draggable>
挟むことで簡単にドラッグ&ドロップが実装できます。

vue3系からは <template #item>draggableタグの中に仕込まないと
エラーが発生するので注意が必要です。

// 略
<draggable>
  <template #item="{ element }">
    <!-- ★ここにドラッグ&ドロップさせたいコンポーネントを挿入します -->
  </template>
</draggable>

<script>
import Draggable from "vuedraggable";

components: {
    draggable,
  },
}

今回は以下のように v-for 構文でCardsを1個ずつ表示している状態で、
card同士を自由にドラッグできるようにすることをゴールにします。

       
<template v-for="card in Cards">
  <template #item="{ element }">
    <div class="card">
      <p class="title">{{ card.title }}</p>
    </div>
  </template>
</template>

以下のように追記します。
Cards:listに渡すことで、draggableの内部で自動でループ処理が行われます。
<template #item>タグ内のelementの中に、配列の個別データが格納されます。

また、item-keyにループ要素のkeyになる値を設定します。
これがないと、warningが出ます。

<draggable :list="Cards" item-key="id"> // 追記
  <template #item="{ element }">  // 追記
    <div class="card">
      <p class="title">{{ element.title }}</p>
    </div>
  </template>
</draggable>

この段階でもそれっぽい動きが可能でドラッグ&ドロップができます。

しかし、並び替えて新しくなった順番は保存できていないため、
ドラッグ後、リロードすると元の順番に戻ってしまいます。

cardを並び替えた状態で保存できた方が便利ですね。

cardを並び替えた状態で保存するためにまず
①ドラッグの完了を補足 → ②DBに順番を保存する 必要があります。

①ドラッグの完了を補足

まずは①について見ていきます。
Vue.Draggableはありがたいことに用意されているイベントが多く、その中のendを使います。
これは、ドラッグ&ドロップが終わったタイミングで発火します。

ちなみにイベントはこちらのページで綺麗にまとめられていました。
ありがてぇ

<draggable :list="Cards"
           @end="saveCardmIndex()"> // ここを修正
  <template #item="{ element }">
    <div class="card">
      <p class="title">{{ element.title }}</p>
    </div>
  </template>
</draggable>

②DBに順番を保存する

次に②について見ていきます。
結論、順番のデータを保持するためのカラムを用意しました。

Cardテーブルに新しくsortカラムを追加します。

rails g migration AddSortToCard sort:integer

マイグレーションファイルは以下のような感じ

class AddSortToCard < ActiveRecord::Migration[7.0]
  def change
    add_column :card, :sort, :integer
  end
end

その後、rails db:migrateコマンドでDBに反映させます。

では実際の実装イメージを書いていきます。

ドラッグ&ドロップする前が以下のような並びになっていると仮定します。

array = {
  0: {id: 1, title: '111', sort: 0}
  1: {id: 2, title: '222', sort: 1}
  2: {id: 3, title: '333', sort: 2}
}

ドラッグ&ドロップすると以下のようにデータが入れ替わると仮定します。

array = {
  0: {id: 1, title: '111', sort: 0}
  1: {id: 3, title: '333', sort: 2}
  2: {id: 2, title: '222', sort: 1}
}

上記のように入れ替わった後、その時のindex番号でsortカラムを書き換えようという作戦です。

以下にざっくりした設計を記載します。

ドラッグが完了

ドラッグの並びになったデータすべて(配列)をpost送信する

controllerで配列を受け取りeach_with_indexsortカラムを更新

リロード時にsortカラムの順番に整形して画面に返却する

では順番に書いていきます。

ドラッグの並びになったデータすべて(配列)をpost送信する

上記にも書いた通りで、ドラッグ&ドロップ終了時に、全てのcard情報が詰まった配列をpost送信します。

<draggable :list="Cards"
           @end="saveCardIndex()">
  <template #item="{ element }">
    <div class="card">
      <p class="title">{{ element.title }}</p>
    </div>
  </template>
</draggable>

<script>
  methods: {
    saveCardIndex() {
      Axios.post("update_card_index", cards: this.Cards)
    },

routes.rbの変更もお忘れなく

controller側で、配列を受け取り each_with_indexsortカラムを更新

post送信されたcard配列を受け取って、each_with_indexによってcardごとにsort情報を更新します。これで、画面の状態をDBに保存することができました。

# 略
  def update_sort
    # 全てのcardのsortを更新
    card_array_params.each_with_index do |card,i|
      card = Card.find(card.id)
      card.sort = i
      card.save!
    end
    
    render json: {result: "ok"}
  end

  private

  # オブジェクトの配列で送信されるため、一個ずつ中身の検査が必要
  def card_array_params
    params.require(:_json).map do |param|
      param.permit(:id)
    end
  end 

リロード時に、sortカラムの順番に整形して画面に返却する

.order(sort: :asc)を追加することで、画面にgetアクセスした時や、リロードした時にsortカラムの順番で画面に表示されるようになります。

cards_controller.rb
#省略
  def index
    cards = Card.all.order(sort: :asc) # ここを修正
    render json: cards, methods: :language
  end
#省略

これで、ドラッグ&ドロップした情報がDBに保存され、リロードにも対応できるようになりました。
ドラッグ中のアニメーションなど置いといて、最低限の機能が実装できました。

最後に

ここまで読んでくださりありがとうございました。
実装方法はかなり簡単で、イベントも豊富なのでかなり気合いの入ったプラグインだと感じました。
サクッとドラッグ&ドロップを作りたいときに便利ですね。

参考

▼アニメーションをつけるなら
ドラッグ時の掴める箇所やアニメーションを設定

3
1
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
1