Add a sort order (sort_order) column to the database, and integrate SortableJS with htmx (hx-post) to save the sorted results.
1. Creating a migration file
Add a sort_order column (integer type) to the existing table to preserve the sort order.
Execute command:
Bash
|
1 |
php artisan make:migration add_sort_order_to_test_tutorial_posts_table |

Pausing work to ask about a suspicious command: an explanation of [php artisan make:migration add_sort_order_to_test_tutorial_posts_table]
Please explain the relationship between ‘add_sort_order_to_’ and ‘test_tutorial_posts_table’ in the command ‘php artisan make:migration add_sort_order_to_test_tutorial_posts_table’.
This is an explanation of the structure and relationships within the filename string used in the command php artisan make:migration add_sort_order_to_test_tutorial_posts_table.
How the ‘snake case’ naming convention works
Laravel parses the string entered in the command line using snake case (a format where words are joined by underscores _) and automatically determines the internal processing.
-
add_sort_order_to_ (Prefixes indicating actions and content)
- Role: This section describes the ‘intent (behaviour)’ of the migration process.
-
Meaning: This follows the rule ‘add_[name of the column to be added]_to_’, indicating the intention to ‘add the sort_order column’.
- Role: This section describes the ‘intent (behaviour)’ of the migration process.
-
test_tutorial_posts (Name of the relevant table)
-
Role: The name of the table in the database on which operations are to be performed.
-
Relationship: Laravel automatically recognises the section between the ‘to_’ at the start and the ‘_table’ at the end as the ‘table name to which the changes should be applied’.
-
-
_table (Suffix indicating the type of migration)
-
Role: A keyword that explicitly indicates that this is a migration file relating to table operations.
-
Code automatically generated by Laravel as a result of this naming convention
If you run the command in accordance with the naming conventions outlined above, Laravel will interpret this as ‘modifying an existing table’ rather than ‘creating a new table’, and will generate the initial code for the migration file as follows.
PHP
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Laravel automatically recognises the `test_tutorial_posts` table and prepares the code return new class extends Migration { public function up(): void { Schema::table('test_tutorial_posts', function (Blueprint $table) { // You can write the code you wish to add here }); } public function down(): void { Schema::table('test_tutorial_posts', function (Blueprint $table) { // }); } }; |
If you run a command using a name that does not conform to the rules (e.g. change_something), Schema::table(...) will not be generated automatically, and you will need to specify the target table name manually. The combination of add_~_to_ and ~_table is the definitive syntax for correctly informing Laravel of the target table.
I reckon it would be fine to just prepare a template and then copy, paste and rename.
Resumption of work
Edit file: database/migrations/xxxx_xx_xx_xxxxxx_add_sort_order_to_test_tutorial_posts_table.php
PHP
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::table('test_tutorial_posts', function (Blueprint $table) { $table->integer('sort_order')->default(0)->after('content'); }); } public function down(): void { Schema::table('test_tutorial_posts', function (Blueprint $table) { $table->dropColumn('sort_order'); }); } }; |
Running the migration:
Bash
|
1 |
php artisan migrate |


2. Routing settings (routes/web.php)
Add the following to the ‘use’ section at the start of the file to configure the routes for display and for sorting and saving.
PHP
|
1 2 3 4 |
use App\Http\Controllers\TestTutorialPostController; Route::get('/test-tutorial/posts/reorder', [TestTutorialPostController::class, 'reorderIndex']); Route::post('/test-tutorial/posts/reorder', [TestTutorialPostController::class, 'reorderUpdate']); |
3. Implementing the controller (app/Http/Controllers/TestTutorialPostController.php)
Retrieve the data in ascending order (sort_order) and update it using the POST data received.
PHP
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\TestTutorialPost; class TestTutorialPostController extends Controller { // Initial display of the reordering page public function reorderIndex() { $posts = TestTutorialPost::orderBy('sort_order', 'asc')->get(); return view('test_tutorial_post.reorder_index', ['posts' => $posts]); } // Process for saving the sort order (POST from HTML form) public function reorderUpdate(Request $request) { $ids = $request->input('ids', []); foreach ($ids as $index => $id) { TestTutorialPost::where('id', $id)->update([ 'sort_order' => $index + 1 ]); } return response('<div style="color: green; margin-top: 10px;">Sort order saved. </div>'); } } |
4. Implementing the Blade view
Full-screen view: resources/views/test_tutorial_post/reorder_index.blade.php
Use @extends or @section in the outer block to loop through and display lists using standard PHP syntax.
HTML
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<?php $__env->startSection('content'); ?> <!-- Loading the SortableJS library --> <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script> <h1>[Test Tutorial] Drag-and-drop reordering</h1> <p>When you drag a card to reorder it, the new order is automatically saved to the database.</p> <form id="sortable-list" hx-post="<?php echo e(url('/test-tutorial/posts/reorder')); ?>" hx-trigger="end" hx-target="#reorder-status"> <?php echo csrf_field(); ?> <?php foreach ($posts as $post): ?> <div class="test-tutorial-sortable-card" style="border: 1px solid #ccc; margin: 10px 0; padding: 15px; background: #fff; cursor: move; user-select: none;"> <input type="hidden" name="ids[]" value="<?php echo e($post->id); ?>"> <strong>☰ <?php echo e($post->title); ?></strong> (Sort Order: <?php echo e($post->sort_order); ?>) <p style="margin: 5px 0 0 0; colour: #666;"><?php echo e($post->content); ?></p> </div> <?php endforeach; ?> </form> <div id="reorder-status"></div> <script> htmx.onLoad(function(content) { var sortableEl = content.querySelector('#sortable-list'); if (sortableEl) { new Sortable(sortableEl, { animation: 150, onEnd: function (evt) { htmx.trigger(sortableEl, 'end'); } }); } }); </script> <?php $__env->stopSection(); ?> <?php echo $__env->make('layouts.app', \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->render(); ?> |
Procedure for Verifying Operation
-
Open your browser and go to /test-tutorial/posts/reorder (e.g. http://localhost/mutunet/public/test-tutorial/posts/reorder).
-
Drag and drop the cards with your mouse to reorder them.
-
The moment the item is dropped,
hx-trigger="end"is triggered, and the message ‘Sort order saved’ appears in green text at the bottom of the screen. -
Reload the page (F5) and check that the sort order has been retained.