atomcoder/laravel-reorderable 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

atomcoder/laravel-reorderable

Composer 安装命令:

composer require atomcoder/laravel-reorderable

包简介

Drag and drop reorder functionality for Laravel Eloquent models.

README 文档

README

Laravel Reorderable

Drag-and-drop sorting for Laravel Eloquent models, with both Blade and Livewire UI support.

What This Package Does

This package lets you:

  • store a sortable position on any Eloquent model
  • render a drag-and-drop list in Blade or Livewire
  • persist the new order automatically through a package route
  • reorder items inside a group such as project_id, board_id, or category_id
  • reorder items programmatically in PHP
  • listen for an event when items are reordered

If you have records like tasks, posts, sections, menu items, images, or lessons, this package gives you a clean way to let users reorder them.

Requirements

  • PHP ^8.3
  • Laravel ^13.0
  • Livewire ^4.0

Installation

composer require atomcoder/laravel-reorderable
php artisan reorderable:install

The install command publishes:

  • config/reorderable.php
  • package views
  • the migration stub used by the generator command

After installing, the usual setup is:

  1. add a sort column to your table
  2. add the trait to your model
  3. add your model to allowed_models
  4. load records using the ordered() scope
  5. render the Blade include or Livewire component

Quick Start

1. Add a sort column to your table

By default, the package uses a column named sort_order.

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('tasks', function (Blueprint $table) {
    $table->unsignedInteger('sort_order')->default(0)->index();
});

If you want a different column name such as position, that is fine. You will just need to tell the model which column to use.

2. Make your model reorderable

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Atomcoder\LaravelReorderable\Contracts\ReorderableContract;
use Atomcoder\LaravelReorderable\Enums\SortDirection;
use Atomcoder\LaravelReorderable\Traits\HasSortOrder;

class Task extends Model implements ReorderableContract
{
    use HasSortOrder;

    protected $fillable = [
        'title',
        'project_id',
        'sort_order',
    ];

    protected string $sortColumn = 'sort_order';

    protected SortDirection|string $reorderSortDirection = SortDirection::Asc;

    public function getReorderLabel(): string
    {
        return $this->title;
    }
}

3. Allow the model in the package config

Edit config/reorderable.php:

'allowed_models' => [
    App\Models\Task::class,
],

If allowed_models is empty, the package accepts any Eloquent model that uses the trait. In most apps, whitelisting your models is safer and clearer.

4. Load the items in the correct order

$tasks = Task::query()
    ->where('project_id', $project->id)
    ->ordered()
    ->get();

Use ordered() whenever you fetch reorderable items for display.

To sort descending, pass a direction string or the enum:

use Atomcoder\LaravelReorderable\Enums\SortDirection;

$tasks = Task::query()
    ->where('project_id', $project->id)
    ->ordered(SortDirection::Desc)
    ->get();

SortDirection::fromValue() accepts SortDirection, asc or desc strings (case-insensitive), or null, and returns a SortDirection (defaults to Asc).

5. Render the UI

Blade:

@include('reorderable::components.list', [
    'items' => $tasks,
    'modelClass' => App\Models\Task::class,
    'groupColumn' => 'project_id',
    'groupValue' => $project->id,
    'title' => 'Reorder Tasks',
    'listId' => 'project-tasks-list',
])

Livewire:

<livewire:reorderable-list
    :items="$tasks"
    model-class="App\Models\Task"
    group-column="project_id"
    :group-value="$project->id"
    title="Reorder Tasks"
    list-id="project-tasks-list"
/>

That is all you need for a working reorderable list.

How It Works

When a user drags an item:

  1. the package reads the list order in the browser
  2. it sends the ordered item IDs to the package route
  3. the package updates the sort column in the database
  4. it dispatches an ItemsReordered event

For grouped lists, the reorder only applies inside the given group.

Model Setup Explained

Your model must:

  • extend Illuminate\Database\Eloquent\Model
  • implement Atomcoder\LaravelReorderable\Contracts\ReorderableContract
  • use Atomcoder\LaravelReorderable\Traits\HasSortOrder

HasSortOrder gives you

  • automatic sort assignment on create
  • an ordered() query scope
  • moveToPosition() for single-item moves
  • reorderFromArray() for bulk reordering
  • default implementations for getReorderKey() and getReorderLabel()

Model properties and methods

Item Required What it does
$sortColumn No Overrides the name of the database column that stores the order. If omitted, the package uses config('reorderable.sort_column').
$reorderSortDirection No Default direction for ordered() when no argument is provided. Accepts SortDirection or asc/desc strings (case-insensitive).
getReorderLabel() Usually yes Returns the text shown in the UI. The trait provides a fallback using name, title, label, or the primary key, but defining it explicitly is clearer.
getReorderKey() No Returns the identifier sent by the UI. The default is the model primary key. Keep this aligned with your actual primary key because reordering uses whereKey() internally.
ordered() No Query scope that sorts by the reorder column. Accepts SortDirection, asc/desc, or null (falls back to getDefaultSortDirection()).
getDefaultSortDirection() No Override to return a custom default direction as SortDirection (use SortDirection::fromValue() to normalize input).
moveToPosition() No Moves one model to a specific position and renumbers the rest.
reorderFromArray() No Reorders records based on an array of IDs. Used internally by the package.

Basic model example

class Post extends Model implements ReorderableContract
{
    use HasSortOrder;

    protected $fillable = ['title', 'sort_order'];

    public function getReorderLabel(): string
    {
        return $this->title;
    }
}

Custom sort column example

class MenuItem extends Model implements ReorderableContract
{
    use HasSortOrder;

    protected $fillable = ['label', 'position'];

    protected string $sortColumn = 'position';

    public function getReorderLabel(): string
    {
        return $this->label;
    }
}

Grouped Reordering

Grouped reordering is for cases where items should only reorder within a parent record.

Examples:

  • tasks inside a project
  • lessons inside a course
  • cards inside a board column
  • menu items inside a menu

Example: reorder tasks inside one project

$tasks = Task::query()
    ->where('project_id', $project->id)
    ->ordered()
    ->get();
@include('reorderable::components.list', [
    'items' => $tasks,
    'modelClass' => App\Models\Task::class,
    'groupColumn' => 'project_id',
    'groupValue' => $project->id,
    'title' => "Tasks for {$project->name}",
])

This means:

  • only tasks with project_id = $project->id are considered
  • dragging task A will not affect tasks in another project
  • the package renumbers only that project's tasks

Auto-assigning sort order within a group on create

When a new model is created, the trait automatically sets the next sort value if the sort column is blank.

If you want that auto-assignment to happen inside a group, add this method to the model:

public function getDefaultReorderGroupColumn(): ?string
{
    return 'project_id';
}

Now a newly created Task with project_id = 5 will get the next sort number inside project 5, not across all tasks.

Blade Usage

Render the Blade include anywhere you already have a collection of reorderable models.

@include('reorderable::components.list', [
    'items' => $tasks,
    'modelClass' => App\Models\Task::class,
    'groupColumn' => 'project_id',
    'groupValue' => $project->id,
    'title' => 'Reorder Tasks',
    'listId' => 'project-tasks-list',
])

Blade variables explained

Variable Required Type Meaning
items Yes iterable The models to display. These should already be loaded in the order you want shown, normally with ordered().
modelClass Yes string The fully qualified model class, for example App\Models\Task::class.
groupColumn No string or null The column used to limit reordering to a group, such as project_id.
groupValue No mixed The value of the group column, such as $project->id.
title No string Heading shown above the list. Default: Reorder Items.
listId No string DOM ID for the <ul>. Use a unique value if you render multiple lists on one page. Default: reorderable-list.

Blade layout requirements

The Blade view sends a fetch() request with a CSRF token and pushes its JavaScript into the scripts stack.

Your layout should include:

<head>
    <meta name="csrf-token" content="{{ csrf_token() }}">
</head>
<body>
    @yield('content')

    @stack('scripts')
</body>

If your app does not render @stack('scripts'), the drag-and-drop JavaScript will not load.

Livewire Usage

Use the Livewire component when the list already lives inside a Livewire-driven screen.

<livewire:reorderable-list
    :items="$tasks"
    model-class="App\Models\Task"
    group-column="project_id"
    :group-value="$project->id"
    title="Reorder Tasks"
    list-id="project-tasks-list"
/>

Livewire props explained

Prop Required Type Meaning
:items Yes iterable The initial items to display.
model-class Yes string The model class to reorder.
group-column No string Group column such as project_id.
:group-value No mixed Group value such as $project->id.
title No string The card heading. Default: Reorder Items.
list-id No string Unique HTML ID for the list. Default: reorderable-livewire-list.

Livewire requirements

Make sure your page layout includes the normal Livewire directives:

@livewireStyles
@livewireScripts

Complete Example

Controller

<?php

namespace App\Http\Controllers;

use App\Models\Project;
use Illuminate\View\View;

class ProjectTaskOrderController extends Controller
{
    public function __invoke(Project $project): View
    {
        $tasks = $project->tasks()
            ->ordered()
            ->get();

        return view('projects.task-order', [
            'project' => $project,
            'tasks' => $tasks,
        ]);
    }
}

Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Atomcoder\LaravelReorderable\Contracts\ReorderableContract;
use Atomcoder\LaravelReorderable\Traits\HasSortOrder;

class Task extends Model implements ReorderableContract
{
    use HasSortOrder;

    protected $fillable = [
        'project_id',
        'title',
        'sort_order',
    ];

    public function getReorderLabel(): string
    {
        return $this->title;
    }

    public function getDefaultReorderGroupColumn(): ?string
    {
        return 'project_id';
    }
}

Blade view

@extends('layouts.app')

@section('content')
    <div class="max-w-3xl mx-auto py-8">
        @include('reorderable::components.list', [
            'items' => $tasks,
            'modelClass' => App\Models\Task::class,
            'groupColumn' => 'project_id',
            'groupValue' => $project->id,
            'title' => "Reorder tasks for {$project->name}",
            'listId' => 'project-task-order-list',
        ])
    </div>
@endsection

Programmatic Reordering

You can reorder without the UI as well.

Move one item to a position

$task = Task::findOrFail(15);

$task->moveToPosition(1);

Move to a position inside a group:

$task = Task::findOrFail(15);

$task->moveToPosition(
    position: 2,
    groupColumn: 'project_id',
    groupValue: $task->project_id,
);

Reorder from an array of IDs

Task::reorderFromArray([8, 3, 5, 1]);

Grouped:

Task::reorderFromArray(
    orderedIds: [8, 3, 5, 1],
    groupColumn: 'project_id',
    groupValue: 12,
);

Request Payload Sent by the UI

The Blade and Livewire UIs ultimately reorder using this shape:

{
  "model": "App\\Models\\Task",
  "items": [5, 9, 2, 7],
  "group_column": "project_id",
  "group_value": 12
}

The package route is:

POST /reorderable/update

If you change route_prefix in config, the URL changes with it.

Configuration

Published config file:

return [
    'middleware' => ['web'],
    'route_prefix' => 'reorderable',
    'sort_column' => 'sort_order',
    'allowed_models' => [
        // App\Models\Task::class,
    ],
    'authorize' => null,
    'demo' => [
        'enabled' => false,
        'middleware' => ['web'],
    ],
];

Config values explained

Key Default What it does
middleware ['web'] Middleware used on the reorder update route.
route_prefix 'reorderable' Prefix for package routes such as /reorderable/update and /reorderable/demo.
sort_column 'sort_order' Default sort column name used when the model does not define $sortColumn.
allowed_models [] Optional whitelist of models that may be reordered.
authorize null Optional callback that receives the current request and model class. Return true to allow the reorder.
demo.enabled false Turns the demo route on or off.
demo.middleware ['web'] Middleware applied to the demo route.

Authorization example

'authorize' => function ($request, string $modelClass): bool {
    return $request->user()?->can('manage-content') ?? false;
},

If the callback does not return true, the package aborts with 403.

Events

The package dispatches this event after a reorder succeeds:

Atomcoder\LaravelReorderable\Events\ItemsReordered

Event properties:

Property Meaning
$modelClass The reordered model class.
$items The reordered IDs in their new order.
$groupColumn The group column used, if any.
$groupValue The group value used, if any.

Event listener example

use Atomcoder\LaravelReorderable\Events\ItemsReordered;
use Illuminate\Support\Facades\Event;

Event::listen(ItemsReordered::class, function (ItemsReordered $event) {
    logger()->info('Items reordered', [
        'model' => $event->modelClass,
        'items' => $event->items,
        'group_column' => $event->groupColumn,
        'group_value' => $event->groupValue,
    ]);
});

Generator Command

The package includes a helper command:

php artisan reorderable:make Task --table=tasks --label=title --column=sort_order

This command:

  • creates a migration to add the sort column
  • prints the model snippet you should add
  • prints a Blade example
  • prints a Livewire example

It does not edit your model or config automatically. You still need to update the model and allowed_models yourself.

Generator options explained

Option Default Meaning
{name} none The model name, for example Task.
--table plural snake case of the model Table to alter, for example tasks.
--label title Model attribute that should be returned from getReorderLabel().
--column sort_order Sort column to create in the migration.

Demo Page

Enable the demo in config/reorderable.php:

'demo' => [
    'enabled' => true,
    'middleware' => ['web'],
],

Then visit:

/reorderable/demo

If you changed route_prefix, the demo URL uses the new prefix.

Common Mistakes

  • forgetting to add the sort column to the database table
  • forgetting to add the model to allowed_models
  • forgetting to call ordered() when loading the list
  • using the Blade component without a CSRF meta tag
  • using the Blade component without rendering @stack('scripts')
  • using multiple lists on one page without giving each one a unique listId
  • returning a custom value from getReorderKey() that is not the model primary key

Testing

composer test

License

MIT

atomcoder/laravel-reorderable 适用场景与选型建议

atomcoder/laravel-reorderable 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 67 次下载、GitHub Stars 达 36, 最近一次更新时间为 2026 年 04 月 12 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「sortable」 「laravel」 「reorder」 「drag and drop」 「livewire」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 atomcoder/laravel-reorderable 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 atomcoder/laravel-reorderable 我们能提供哪些服务?
定制开发 / 二次开发

基于 atomcoder/laravel-reorderable 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 67
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 36
  • 点击次数: 37
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 36
  • Watchers: 1
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-04-12