inertia.js + reactでdraggable (react-beautiful-dnd)

react-beautiful-dnd を使う例

事前準備


artisan make:model Draggable -mrsf

とりあえずtitleだけあればいいとしよう

        Schema::create('draggables', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->integer('sort_order')->default(0);
            $table->timestamps();
        });

ただし↑のようにsort_orderで並び順を定義する

class DraggableFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'title' => fake()->company,
        ];
    }
}
        \App\Models\Draggable::factory(5)->create();

これで

= Illuminate\Database\Eloquent\Collection {#7618
    all: [
      App\Models\Draggable {#7620
        id: 1,
        title: "有限会社 工藤",
        sort_order: 0,
        created_at: "2023-12-31 02:07:56",
        updated_at: "2023-12-31 02:07:56",
      },
      App\Models\Draggable {#7621
        id: 2,
        title: "株式会社 山本",
        sort_order: 0,
        created_at: "2023-12-31 02:07:56",
        updated_at: "2023-12-31 02:07:56",
      },
      App\Models\Draggable {#7622
        id: 3,
        title: "有限会社 木村",
        sort_order: 0,
        created_at: "2023-12-31 02:07:56",
        updated_at: "2023-12-31 02:07:56",
      },
      App\Models\Draggable {#7623
        id: 4,
        title: "株式会社 加藤",
        sort_order: 0,
        created_at: "2023-12-31 02:07:56",
        updated_at: "2023-12-31 02:07:56",
      },
      App\Models\Draggable {#7624
        id: 5,
        title: "有限会社 山口",
        sort_order: 0,
        created_at: "2023-12-31 02:07:56",
        updated_at: "2023-12-31 02:07:56",
      },
    ]

まあこんな具合に5つエントリが出来た。

routes/web.phpには適当にresouceを振っとく

    Route::resource('draggables', DraggableController::class);

draggable.index


    public function index()
    {
        $draggables = Draggable::all();
        return Inertia::render('Draggables/Index', [
            'draggables' => $draggables,
        ]);
    }

resources/js/Pages/Draggables/Index.jsx 

import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import { Head } from '@inertiajs/react'

export default function DraggableIndex({ auth, draggables }) {
  return (
    <AuthenticatedLayout
      user={auth.user}
      header={
        <h2 className="font-semibold text-xl text-gray-800 leading-tight">
          Draggable
        </h2>
      }
    >
      <Head title="Draggable" />

      <div className="py-12">
        <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
          <div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
            <div className="p-6 text-gray-900">
              <ul className="space-y-2">
                {draggables.map((draggable, index) => (
                  <li
                  key={index}
                  className="px-4 py-2 border rounded-md hover:bg-gray-100"
                >
                    {draggable.title}
                </li>
                ))}
              </ul>
            </div>
          </div>
        </div>
      </div>
    </AuthenticatedLayout>
  )
}

これを動かしていく

install

npm install react-beautiful-dnd

したら

import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import { Head } from '@inertiajs/react'
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

こんな感じでimportできる。

コード

詳しい説明は一気に割愛するが、とりあえず動くものを貼り付けておく。これは今後もう少しこれを使って何か作る事があった場合は逐次解説する

import React, { useState } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head } from '@inertiajs/react';
import { router } from '@inertiajs/react';

export default function DraggableIndex({ auth, draggables }) {
  // ドラッグアンドドロップの状態を管理するためのローカルステート
  const [items, setItems] = useState(draggables);

  // ドラッグが終了したときのハンドラー
  const onDragEnd = (result) => {
    if (!result.destination) return;

    const newItems = Array.from(items);
    const [reorderedItem] = newItems.splice(result.source.index, 1);
    newItems.splice(result.destination.index, 0, reorderedItem);

    setItems(newItems);
    router.post(route('draggables.store'), { items: newItems })
  };

  return (
    <AuthenticatedLayout
      user={auth.user}
      header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Draggable</h2>}
    >
      <Head title="Draggable" />

      <div className="py-12">
        <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
          <div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
            <div className="p-6 bg-white border-b border-gray-200">
              <DragDropContext onDragEnd={onDragEnd}>
                <Droppable droppableId="droppable">
                  {(provided) => (
                    <ul className="space-y-2" {...provided.droppableProps} ref={provided.innerRef}>
                      {items.map((item, index) => (
                        <Draggable key={item.id} draggableId={item.id.toString()} index={index}>
                          {(provided) => (
                            <li
                              ref={provided.innerRef}
                              {...provided.draggableProps}
                              {...provided.dragHandleProps}
                              className="px-4 py-2 border rounded-md hover:bg-gray-100"
                            >
                              {item.title}
                            </li>
                          )}
                        </Draggable>
                      ))}
                      {provided.placeholder}
                    </ul>
                  )}
                </Droppable>
              </DragDropContext>
            </div>
          </div>
        </div>
      </div>
    </AuthenticatedLayout>
  );
}

backendの処理

そうすると

    router.post(route('draggables.store'), { items: newItems })

により、storeに転送される。まあstoreっていうのは流石にちょっとアレな名前なので本来はreorderとかそういう名前にしておいた方がいいんだろうけど、これをdumpすると

    public function store(Request $request)
    {
        dd($request->all());
    }


山本を一番↑にD&Dした例

このように番号が振り直されているものが来るので、あとはbackendでsort_orderをupdateしてあげればよい。

class Draggable extends Model
{
    use HasFactory;

    protected $fillable = [
        'sort_order'
    ];
}
    public function store(Request $request): RedirectResponse
    {
        $draggables = $request->items;
        foreach ($draggables as $index => $item) {
            Draggable::where('id', $item['id'])
                ->update(['sort_order' => $index]);
        }
        return redirect(route('draggables.index'));
    }

とか

最終的に

indexもsort_orderの値を見る事

    public function index()
    {
        // $draggables = Draggable::all();
        $draggables = Draggable::orderBy('sort_order')->get();
        return Inertia::render('Draggables/Index', [
            'draggables' => $draggables,
        ]);
    }

この記事が気に入ったらサポートをしてみませんか?