Content Index
- Creating the Native Datatable in Laravel Livewire
- Creating the Datatable in Laravel Livewire
- Define Model
- Define trait for search fields using queryString
- protected $queryString en Laravel Livewire
- What is it for?
- Process data for listing, render function
- Full project code
- View code
- Filters on a list or table in Laravel Livewire
- Filters in Laravel Livewire
- Paged listing with Volt
- Extra wire:sort in Livewire 4
- What is wire:sort?
- Basic usage example
- Advanced features of wire:sort
- Full Example: Sortable Datatable with Drag and Drop in Livewire 4
- 1. Data Preparation
- 2. Using Directives in the View
- 3. The Reordering Algorithm
- 4. Application in Tables (DataTables)
A table of type datatable, is the term that we use to indicate a table in basic HTML with the data with the CRUD type operations for each one of these data; this table; It is a table that allows us to define a set of useful options for our user such as pagination, search fields, column ordering, and of course, options to edit, delete, and all the others you need; In this entry we are only going to create the table, with the corresponding options for it, using tailwind.css for our interface, although you can use anything else like native CSS or even the tables we have for Bootstrap.
At this point, we already know how to use component properties in Livewire, which will be a key piece to convert our table or list into a datatable.
A DataTable can store data of any type, including text, numbers, dates, images, among other types. It is commonly used in web applications to display data in a table in an ordered and structured way. For example, if there is information from a list of publications, in this supposed list the options to delete, create, edit, see the detail, and filter the post may appear.
In the end, a Datatable is a table on steroids in which you define multiple management, filter, and sort options.
In Laravel Livewire, thanks to all the reactive behavior and communication with the server directly from the client, we can create these types of tables very easily natively.
Creating the Native Datatable in Laravel Livewire
Creating the Datatable in Laravel Livewire
As we mentioned before, there is some management component in the tables, therefore we have to define the model, the view and the component.
Define Model
First things first, and we need a data source, our user model, which is the one that comes free with Laravel Livewire, is quite convenient for us, but you can use any other; in our model of a lifetime for users with the traits that we have by default:
class User extends Authenticatable #implements MustVerifyEmail
{
use HasApiTokens;
use HasFactory;
use HasProfilePhoto;
use HasTeams;
use Notifiable;
use TwoFactorAuthenticatable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password'
];
}Define trait for search fields using queryString
Now we go to our component, for that we are going to use a Livewire component that we can create with:
php artisan make:livewire UserListAnd remember that with this, 3 elements are created, our Server Side Component and our view; now with the component created, we have to define some characteristics to be able to use the search; we use the property called queryString, to indicate which properties we are going to use for the search; This is useful because if our user refreshes the page, the data that the user is typing is saved in the application's URL and the search would be maintained:
use WithPagination;
protected $queryString = ['name'];
public $name = "";
public $sortColumn = "id";
public $sortDirection = "asc";
public $columns = [
'id',
'name',
'email'
];
public function sort($column)
{
$this->sortColumn = $column;
$this->sortDirection = $this->sortDirection == 'asc' ? 'desc' : 'asc';
}For the rest, we define some more properties to indicate the columns that we are going to use, their names and also a function and properties that will allow us to search by columns, and the type of ordering, ascending or descending.
protected $queryString en Laravel Livewire
protected $queryString is a fundamental property in the Laravel ecosystem, specifically when working with Livewire. Its main function is to synchronize the state of components with the browser URL.
Here's a basic explanation to understand how and why it's used:
What is it for?
Imagine you have a table with a search bar or filters. If the user filters for "Shoes" and then refreshes the page, the filter would normally be cleared.
By using $queryString, Livewire saves that value in the URL (example: yourwebsite.com/products?search=Shoes). This allows:
- The filter to persist upon refreshing.
- The user to copy and share the URL with the filter already applied.
- The browser's "Back" button to function correctly with the changes in state.
Process data for listing, render function
Now, the heart of our component, in which we build the query or the query to the database, as well as map the properties that we want to use to filter our data, which in this case is the name/name; otherwise we use pagination and return to the view we want to use:
public function render()
{
$users = User::orderBy($this->sortColumn, $this->sortDirection);
if ($this->name) {
$users->where('name', 'like', '%' . $this->name . '%')
->orWhere('email', 'like', '%' . $this->name . '%');
}
$users = $users->paginate(10);
return view('livewire.users-list', ['users' => $users]); //->layout('layouts.app');
}Full project code
Finally, here you can see the source code of the component used previously, in which we have the columns, as well as the columns to indicate the filter, ordering, in the render function, we indicate the view as well as apply the corresponding filters, remember that each of these properties used in the render function is used at the view level as models in the fields, thanks to the communication between the backend and the frontend, the communication is done directly and this is the secret and most important point of this project:
namespace App\Http\Livewire;
use App\Models\User;
//use Illuminate\View\Component;
use Livewire\Component;
use Livewire\WithPagination;
class UsersList extends Component
{
use WithPagination;
//public $users;
public $columns = [
'id',
'name',
'email'
];
public $name = "";
public $sortColumn = "id";
public $sortDirection = "asc";
protected $queryString = ['name'];
public function render()
{
$users = User::orderBy($this->sortColumn, $this->sortDirection);
if ($this->name) {
$users->where('name', 'like', '%' . $this->name . '%')
->orWhere('email', 'like', '%' . $this->name . '%');
}
$users = $users->paginate(10);
return view('livewire.users-list', ['users' => $users]); //->layout('layouts.app');
}
public function sort($column)
{
$this->sortColumn = $column;
$this->sortDirection = $this->sortDirection == 'asc' ? 'desc' : 'asc';
}
public function cleanFilter()
{
$this->name = "";
}
public function delete(User $user)
{
$user->delete();
}
}View code
The view, it's really simple; As we mentioned, we are using Tailwind, we have a block of inputs referencing the properties of the previous model through the wire:model, which is to define the fields of the form to filter and then we have a block for the table, which already has all the logic implemented in the component from Laravel, we are dedicated to painting the data and columns without any condition:
<div class="flex mt-1 ">
<x-jet-input wire:model="name" class="block w-full" />
<x-jet-secondary-button class="ml-2" wire:click="cleanFilter">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
</x-jet-secondary-button>
</div>
<table class="table-auto w-full">
<thead>
<tr>
@foreach ($columns as $c)
<th class="p-3" wire:click="sort('{{ $c }}')">
<button>
{{ $c }}
@if ($sortColumn == $c)
@if ($sortDirection == 'asc')
↑
@else
↓
@endif
@endif
</button>
</th>
@endforeach
<th class="p-3">Acciones</th>
</tr>
</thead>
<tbody>
@forelse ($users as $u)
<tr>
<td class="border p-3">{{ $u->id }}</td>
<td class="border p-3">{{ $u->name }}</td>
<td class="border p-3">{{ $u->email }}</td>
<td class="border p-3 flex justify-center">
<x-a class="p-1 bg-blue-600 mr-1" href="{{ route('user.edit', $u) }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
</x-a>
<x-jet-danger-button class="p-sm-button" wire:click="delete({{ $u->id }})">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</x-jet-danger-button>
</td>
</tr>
@empty
<tr>
<td colspan="3">
<p>No hay registros</p>
</td>
</tr>
@endforelse
</tbody>
</table>
<br>
{{ $users->links() }}Important points is to note the call to the functions through the click events that allow us to call functions of the component from the view, such as delete or order/sort to perform the ordering.
We are going to work on column ordering individually and ascending/descending for each column that we have in the list.
The scheme that we are going to follow, in order to be able to easily order all the columns of a list, is defining the columns that we are going to show in the list at the level of a property in the Livewire component; with this, we can easily indicate the click events based on the structure proposed in this book:
app/Http/Livewire/Dashboard/Post/Index.php
class Index extends Component
{
use WithPagination;
// ***
// order
public $sortColumn = 'id';
public $sortDirection = 'desc';
public $columns = [
'id' => "Id",
'title' => "Title",
'date' => "Date ",
'description' => "Description",
'posted' => "Posted",
'type' => "Type",
'category_id' => "Category",
];
public function render()
{
//$this->confirmingDeletePost = true;
$posts = Post::orderBy($this->sortColumn,$this->sortDirection);
if ($this->search)
// ***
}
// order
public function sort($column)
{
$this->sortColumn = $column;
$this->sortDirection = $this->sortDirection == 'asc' ? 'desc' : 'asc';
}Explanation of the above code
The column of $columns we define the columns that we are going to show in the list, like; for example:
'id' => "id",As the key of the array, we indicate the name of the column in the database (the name of the property in the model).
As value, it is the label or text that we are going to place as header in the table.
The $sortColumn and $sortDirection properties define the column that we are going to sort at a given time and the type (ascending or descending), respectively.
In the view:
resources/views/livewire/dashboard/post/index.blade.php
***
<table class="table w-full border">
<thead class="text-left bg-gray-100 ">
<tr class="border-b">
@foreach ($columns as $key => $c)
<th>
<button wire:click="sort('{{ $key }}')">
{{ $c }}
@if ($key == $sortColumn)
@if ($sortDirection == 'asc')
↑
@else
↓
@endif
@endif
</button>
</th>
@endforeach
<th class="p-2">
Actions
</th>
</tr>
</thead>
***Filters on a list or table in Laravel Livewire
Filters in web development are used to classify or limit data in a list, they are widely used in administrative modules to limit the data in a list. For example, a filter can be used to display only those elements of a data set that meet with certain criteria, such as a specific date or a particular category; we can actually place any type of field; in practice, filters are nothing more than HTML forms with their respective HTML fields; in which requests are usually sent via GET and not POST, since the aim is to classify data and not change any type of data. Let's see how to use them in Livewire.
Filters in Laravel Livewire
In Laravel Livewire, we can easily apply filters to search our lists, thanks to the fact that there is a kind of synchronization or link between the client side and the server, as far as its properties are concerned, this type of operation is very easy to carry out.
These lists can be anything like a table or list structured based on DIVs.
In this article, we are going to start with a model of publications like the following:
class Post extends Model
{
use HasFactory;
protected $fillable=['title', 'slug','date','image','text','description','posted','type', 'category_id'];
protected $casts =[
'date' => 'datetime'
];
public function category()
{
return $this->belongsTo(Category::class);
}
}And the typical listing using a pagination:
<table class="table w-full border">
<thead class="text-left bg-gray-100 ">
<tr class="border-b">
@foreach ($columns as $key => $c)
<th>
<button wire:click="sort('{{ $key }}')">
{{ $c }}
@if ($key == $sortColumn)
@if ($sortDirection == 'asc')
↑
@else
↓
@endif
@endif
</button>
</th>
@endforeach
<th class="p-2">
Acciones
</th>
</tr>
</thead>
<tbody>
@foreach ($posts as $p)
<tr class="border-b">
<td class="p-2">
{{ $p->id }}
</td>
<td class="p-2">
{{ str($p->title)->substr(0, 15) }}
</td>
<td class="p-2">
{{ $p->date }}
</td>
<td class="p-2">
<textarea class="w-48">
{{ $p->description }}
</textarea>
</td>
<td class="p-2">
{{ $p->posted }}
</td>
<td class="p-2">
{{ $p->type }}
</td>
<td class="p-2">
{{ $p->category->title }}
</td>
<td class="p-2">
<x-jet-nav-link href="{{ route('d-post-edit', $p) }}" class="mr-2">Editar
</x-jet-nav-link>
<x-jet-danger-button {{-- onclick="confirm('Seguro que deseas eliminar el registro seleccionado?') || event.stopImmediatePropagation()" --}}
wire:click="seletedPostToDelete({{ $p }})">
Eliminar
</x-jet-danger-button>
</td>
</tr>
@endforeach
</tbody>
</table>
<br>
{{ $posts->links() }}To filter data in the component class, we use where conditions, in this case on the type:
if ($this->type) {
$posts->where('type', $this->type);
}The category:
if ($this->category_id) {
$posts->where('category_id', $this->category_id);
}And if it is posted or not:
if ($this->posted) {
$posts->where('posted', $this->posted);
}All are existing model structures and of course, you can customize.
So, our component to get the filtered posts:
The component class:
//***
// filters
public $type;
public $category_id;
public $posted;
//***
public function render()
{
$posts = Post::where("id", ">=", 1);
if ($this->type) {
$posts->where('type', $this->type);
}
if ($this->category_id) {
$posts->where('category_id', $this->category_id);
}
if ($this->posted) {
$posts->where('posted', $this->posted);
}
$categories = Category::pluck("title", "id");
$posts = $posts->paginate(10);
return view('livewire.dashboard.post.index', compact('posts', 'categories'));
}
//***Important to note that:
- We have one property per filter.
- The query to the database to obtain the posts is composed in several steps and it is verified if the filter properties have a value set to apply in the query.
In the view, we place our filters based on the structure we have:
<div class="flex gap-2 mb-2">
<select class="block w-full" wire:model="posted">
<option value="">{{__("Posted")}}</option>
<option value="not">No</option>
<option value="yes">Si</option>
</select>
<select class="block w-full" wire:model="type">
<option value="">{{__("Type")}}</option>
<option value="adverd">adverd</option>
<option value="post">post</option>
<option value="course">course</option>
<option value="movie">movie</option>
</select>
<select class="block w-full" wire:model="category_id">
<option value="">{{__("Category")}}</option>
@foreach ($categories as $i => $c)
<option value="{{ $i }}">{{ $c }}</option>
@endforeach
</select>
</div>
<table class="table w-full border">
//***At this point, we would have a paginated list, it is interesting to realize that we can use the properties in Livewire in conjunction with the wire:model not only for record management, but also for other purposes as in this case, it would be to apply a filter.
In the end, we will have:

And with this, we have a simple filter, remember that you can place more fields following the same organization as the one presented in this post; filters are one of the many topics covered in the Laravel Livewire course and book.
Paged listing with Volt
It is very easy to convert a classic Livewire component, that is, a class and view, to a Volt component. We will start with:
resources/views/livewire/dashboard/category/index.blade.php
<div>
<x-action-message on="deleted">
<div class="box-action-message">
{{ __("Category delete success") }}
</div>
</x-action-message>
<flux:heading>{{ __('Category List') }}</flux:heading>
<flux:text class="mt-2">Lorem ipsum dolor sit amet consectetur adipisicing.</flux:text>
<div class="separation"></div>
<flux:button class="ml-1 mb-3" variant='primary' icon="plus" href="{{ route('d-category-create') }}">
{{ __('Create') }}
</flux:button>
<!-- <flux:modal.trigger name="delete-category">
<flux:button>Delete</flux:button>
</flux:modal.trigger> -->
<flux:modal name="delete-category">
<div class="m-1">
<flux:heading>{{ __('Delete Category') }}</flux:heading>
<flux:text class="mt-2">{{ __('Are you sure you want to delete this category?') }}</flux:text>
<div class="flex flex-row-reverse">
<flux:button class="mt-4" variant='danger' icon="trash" wire:click="delete()">
{{ __('Delete') }}
</flux:button>
</div>
</div>
</flux:modal>
<div class="overflow-x-auto shadow-md rounded-lg">
<table class="table w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead class="rounded-lg text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th>
Id
</th>
<th>
Title
</th>
<th>
Actions
</th>
</tr>
</thead>
<tbody>
@foreach ($categories as $c)
<tr
class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700 border-gray-200">
<td>
{{ $c->id }}
</td>
<td>
{{ $c->title }}
</td>
<td>
<a href="{{ route('d-category-edit', $c) }}">Edit</a>
<flux:modal.trigger wire:click="selectCategodyToDelete({{ $c }})" name="delete-category">
<flux:button class="ml-3" variant='danger' size="xs">{{ __('Delete') }}</flux:button>
</flux:modal.trigger>
<!-- <flux:button class="ml-3" variant='danger' size="xs" wire:click="delete({{ $c }})">
{{ __('Delete') }}
</flux:button> -->
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<br>
{{ $categories->links() }}
</div>app/Livewire/Dashboard/Category/Index.php
<?php
namespace App\Livewire\Dashboard\Category;
use Livewire\Component;
use Livewire\WithPagination;
use App\Models\Category;
use Flux\Flux;
class Index extends Component
{
use WithPagination;
public $categoryToDelete;
public function render()
{
$categories = Category::paginate(10);
return view('livewire.dashboard.category.index', compact('categories'));
}
function selectCategodyToDelete(Category $category){
$this->categoryToDelete = $category;
}
function delete(){
$this->dispatch("deleted");
Flux::modal("delete-category")->close();
$this->categoryToDelete->delete();
}
}And a Volt component is exactly the same:
$ php artisan make:volt Volt/Dashboard/Category/Index<?php
use Livewire\Volt\Component;
use Illuminate\Support\Facades\Storage;
use Livewire\WithFileUploads;
use App\Models\Category;
new class extends Component {
use WithFileUploads;
public $title;
public $slug;
public $text;
public $image;
protected $rules = [
'title' => "required|min:2|max:255",
'text' => "nullable",
'image' => "nullable|image|max:1024",
];
public $category;
public function mount(?int $id = null)
{
if ($id != null) {
$this->category = Category::findOrFail($id);
$this->title = $this->category->title;
$this->text = $this->category->text;
}
}
function submit()
{
// validate
$this->validate();
if ($this->category) {
$this->category->update([
'title' => $this->title,
'text' => $this->text,
]);
$this->dispatch("updated");
} else {
$this->category = Category::create([
'title' => $this->title,
'slug' => str($this->title)->slug(),
'text' => $this->text,
]);
$this->dispatch("created");
}
// upload
if ($this->image) {
// delete old img
if ($this->category->image) {
Storage::disk('public_upload')
->delete('images/category/' . $this->category->image);
}
$imageName = $this->category->slug . '.' . $this->image->getClientOriginalExtension();
$this->image->storeAs('images/category', $imageName, 'public_upload');
$this->category->update([
'image' => $imageName
]);
}
}
}; ?>
<div>
<x-action-message on="created">
<div class="box-action-message">
{{ __('Created category success') }}
</div>
</x-action-message>
<x-action-message on="updated">
<div class="box-action-message">
{{ __('Updated category success') }}
</div>
</x-action-message>
<flux:heading>
@if ($category)
{{ __('Category edit: ') }} <span class="font-bold">{{ $category->title }}</span>
@else
{{ __('Category create') }}
@endif
</flux:heading>
<flux:text class="mt-2">Lorem ipsum dolor sit amet consectetur adipisicing.</flux:text>
<div class="separation"></div>
<form wire:submit.prevent="submit" class="flex flex-col gap-4">
<!-- <input type="text" wire:model="title">
<textarea wire:model="text"></textarea>
<button type="submit" wire:click="submit">Send</button> -->
{{-- <input type="text" wire:model="title"> --}}
{{-- @error('title')
{{ $message }}
@enderror --}}
<flux:input wire:model="title" :label="__('Title')" />
<flux:textarea wire:model="text" :label="__('Text')" />
<flux:input wire:model="image" type='file' :label="__('Image')" />
{{-- @error('text')
{{ $message }}
@enderror --}}
<div>
<flux:button variant="primary" type="submit">
{{ __('Save') }}
</flux:button>
</div>
</form>
@if ($category && $category->image)
<img class="w-40 my-3" src="{{ $category->getImageUrl() }}" />
@endif
</div>We create the route:
// demo volt
Volt::route('volt', 'volt.dashboard.category.index')->name('volt-d-category-index');As you can see, we removed the render method and the pagination case is special, we must use the with() method and return the data to the view.
Extra wire:sort in Livewire 4
Unlike previous versions where you needed external plugins or Alpine.js, Livewire now has an official and simplified directive. Here is the basic explanation:
What is wire:sort?
It is a directive designed to make any list sortable via dragging, automatically handling animations and browser logic.
Basic usage example
In your Blade file, you wrap your list in a container with wire:sort and label each element with wire:sort:item:
<ul wire:sort="reorder">
@foreach ($tasks as $task)
<li wire:sort:item="{{ $task->id }}" wire:key="{{ $task->id }}">
{{ $task->title }}
</li>
@endforeach
</ul>In your PHP Component, you define the method that will receive the change:
public function reorder($id, $position)
{
// $id: The identifier of the item that moved.
// $position: The new position (starting from 0).
$task = Task::find($id);
$task->update(['position' => $position]);
// Optional: Reorder the rest of the collection in the DB
}Advanced features of wire:sort
wire:sort:handle: If you don't want the entire element to be "draggable", you can define a specific icon or button as a handle:
<li wire:sort:item="{{ $task->id }}">
<span wire:sort:handle>☰</span> {{ $task->title }}
</li>wire:sort:ignore: For elements within the list that should not trigger dragging (like delete buttons or inputs):
- <button wire:sort:ignore>Delete</button>
wire:sort:group: To move elements between different lists (Kanban style). - You use wire:sort:group="group_name" on the containers.
- The PHP method will receive a third parameter: $groupId.
Full Example: Sortable Datatable with Drag and Drop in Livewire 4
This is an implementation of wire:sort, which allows us to reorder elements using Drag and Drop, a new feature as of Livewire 4.
1. Data Preparation
We start with a very simple task list (Todo List). For it to work, we need a title and, mandatorily, a field for the order (call it position, sort, or whatever you prefer) to save the location in the database.
In the component, when mounting it (mount), we retrieve all tasks. It is very important that these come ordered by the corresponding field so that the initial view is correct.
app\Models\TodoList.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TodoList extends Model
{
use HasFactory;
protected $fillable = [
'title',
'order',
];
}2. Using Directives in the View
To use the new Wire Sort feature, we only need to define a couple of fields in our HTML:
- wire:sortable: This directive is placed on the main container and indicates the name of the method that will handle the reordering process.
- wire:sortable.item: Assigned to the element we are moving, usually passing the task ID.
- wire:sortable.handle: (Optional) If you want a specific icon for dragging.
The event fires when reordering finishes (on drag end), calling the defined method and passing the item ID along with its new position.
3. The Reordering Algorithm
Updating the position is not that trivial. If you simply assign the new position to a task, you could end up with two tasks having the same order number (for example, two tasks in position "2"). Therefore, you must update the others.
I propose the following algorithm to simplify the process:
- Filter: We get all tasks from the database except the one we just moved.
- Insert: We take the current task and manually insert it into the new position within the collection obtained in the previous step.
- Reindex: Using a foreach loop, we iterate through this new list and assign each task a position based simply on its index within the array.
- Persist: We save the changes to the database and reload the list so the interface updates.
resources\views\components\⚡mysort.blade.php
<?php
use Livewire\Component;
use App\Models\TodoList;
new class extends Component {
public $lists;
public function mount()
{
$this->lists = TodoList::orderBy('order')->get();
}
public function handleSort($id, $position)
{
// 1. Get all ordered items, excluding the one that moved
$items = TodoList::where('id', '!=', $id)->orderBy('order')->get();
// 2. Find the item that moved
$movedItem = TodoList::find($id);
// 3. Insert it into the new position within the collection (splice)
// Note: $position usually comes in base 0 from the frontend
$items->splice($position, 0, [$movedItem]);
// 4. Update the database
foreach ($items as $index => $item) {
$item->update(['order' => $index]);
}
// 5. Refresh the property so the view updates
$this->lists = TodoList::orderBy('order')->get();
}
};
?>
<div>
<ul wire:sort="handleSort">
@foreach ($lists as $l)
<li class="border p-4 mb-1" wire:key="{{ $l->id }}" wire:sort:item="{{ $l->id }}">
<span class="rounded-full px-2 py-1 bg-purple-500 text-sm">{{ $l->order }}</span> -
{{ $l->title }}
</li>
@endforeach
</ul>
</div>4. Application in Tables (DataTables)
If you are working with a table, the logic is the same. The wire:sort goes exactly above the @foreach (in the <tbody>) and the sortable.item at the same level as the <tr> tag. This is ideal for completing the Data Table we are building in this entry.
resources\views\components\⚡mysort.blade.php
<div>
<table class="table">
<thead>
<tr>
<th class="px-6 py-4 ">ID</th>
<th class="px-6 py-4 ">Title</th>
<th class="px-6 py-4 ">Order</th>
</tr>
</thead>
<tbody wire:sort="handleSort">
@foreach ($lists as $l)
<tr wire:key="{{ $l->id }}" wire:sort:item="{{ $l->id }}">
<td class="px-6 py-4 ">{{ $l->id }}</td>
<td class="px-6 py-4 ">{{ $l->title }}</td>
<td class="px-6 py-4 ">{{ $l->order }}</td>
</tr>
@endforeach
<tbody>
</table>
</div>
This way, you can easily adapt wire:sort to sort the rows of your datatable with ease.
The next step is to learn how to upload files or images using forms and drag and drop in Laravel Livewire.