在我的上一篇文章中,我们介绍了如何入门Laravel 中的 HTMX。我们探索了一些核心概念,包括使用 hx-get
和 hx-post
来添加异步功能。
在本篇博文中,我们将使用 HTMX 优化 Laravel Blade 模板,添加客户端排序和分页功能。
介绍
在上篇文章中,我们构建了一个非常简单的用于 contacts
CRUD 的数据表,现在让我们通过添加分页和排序功能,并使其成为一个可重用的组件来扩展它。
在 Laravel 中使用 HTMX 实现分页
虽然 Laravel 中的 {{ $contacts->links() }}
提供分页链接,但它缺少响应性。这就是 hx-boost
属性发挥作用的地方。
我们将 hx-boost
属性应用到容器中,确保容器内链接的后续请求都接受 hx-get
处理。
以下是我们的代码示例
<div id="table-container"
hx-get="{{ route('contacts.index') }}"
hx-trigger="loadContacts from:body">
<table id="contacts-table" class="table-auto w-full">
...
</table>
<div id="pagination-links" class="p-3"
hx-boost="true"
hx-target="#table-container">
{{ $contacts->links() }}
</div>
</div>
此外,我们将 hx-get
和 hx-trigger
属性从表格移动到父容器 div
中。
以下是更新的 ContactsController
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ContactRequest;
use App\Models\Contact;
use Illuminate\Http\Request;
class ContactController extends Controller
{
public function index(Request $request)
{
$searchTerm = $request->input('q');
$contacts = Contact::where('name', 'LIKE', "%$searchTerm%")
->paginate(10);
if ($request->header('hx-request')
&& $request->header('hx-target') == 'table-container') {
return view('contacts.partials.table', compact('contacts'));
}
return view('contacts.index', compact('contacts'));
}
...
...
}
现在,当点击分页链接时,HTMX 会发送异步请求,并在收到响应后交换内容。
在 Laravel 中使用 HTMX 实现排序
数据排序是另一个常见的需求。我们的目标是通过点击列标题来重新排列表格行。让我们启用 HTMX 的异步排序功能。
在表格标题中,我们包含用于交互式排序所需的 HTMX 属性
<div id="table-container"
hx-get="{{ route('contacts.index') }}"
hx-trigger="loadContacts from:body">
@php
$sortField = request('sort_field');
$sortDir = request('sort_dir', 'asc') === 'asc' ? 'desc' : 'asc';
$sortIcon = fn($field) =>
$sortField === $field ? ($sortDir === 'asc' ? '↑' : '↓') : '';
$hxGetUrl = fn($field) =>
request()->fullUrlWithQuery([
'sort_field' => $field,
'sort_dir' => $sortDir
]);
@endphp
<table id="contacts-table" class="table-auto w-full">
<thead>
<th class='px-4 py-2 border text-left cursor-pointer'
hx-get="{{ $hxGetUrl('name') }}"
hx-trigger='click'
hx-replace-url='true'
hx-swap='outerHTML'
hx-target='#table-container'>
Name
<span class="ml-1" role="img">{{ $sortIcon('name') }}</span>
</th>
<th class='px-4 py-2 border text-left cursor-pointer'
hx-get="{{ $hxGetUrl('email') }}"
hx-trigger='click'
hx-replace-url='true'
hx-swap='outerHTML'
hx-target='#table-container'>
Email
<span class="ml-1" role="img">{{ $sortIcon('email') }}</span>
</th>
<th class="px-4 py-2 border text-left">Phone</th>
<th class="px-4 py-2 border text-left">Address</th>
<th class="px-4 py-2 border text-left">Actions</th>
</thead>
<tbody id="contacts-table-body"
...
...
</tbody>
</table>
<div id="pagination-links" class="p-3"
hx-boost="true"
hx-target="#table-container">
{{ $contacts->links() }}
</div>
</div>
在@php
标签内,我们定义变量以处理排序功能。变量$sortField
和$sortDir
管理排序的字段和方向,而$sortIcon
函数用于生成表示排序方向的适当箭头图标。函数$hxGetUrl
有助于创建包含更新排序参数的HTMX请求的URL。
这是再次更新的ContactsController
:
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ContactRequest;
use App\Models\Contact;
use Illuminate\Http\Request;
class ContactController extends Controller
{
public function index(Request $request)
{
$searchTerm = $request->input('q');
$contacts = Contact::where('name', 'LIKE', "%$searchTerm%")
->when($request->has('sort_field'), function ($query) use ($request) {
$sortField = $request->input('sort_field');
$sortDir = $request->input('sort_dir', 'asc');
$query->orderBy($sortField, $sortDir);
})
->paginate(10);
if ($request->header('hx-request')
&& $request->header('hx-target') == 'table-container') {
return view('contacts.partials.table', compact('contacts'));
}
return view('contacts.index', compact('contacts'));
}
...
...
}
使用Blade组件重构
我们的表格代码正在变得越来越长且缺乏可重用性。为了解决这个问题,我们可以将每个表格元素分离成一个自己的组件。
让我们将其分解为可重用组件
components
├── table
│ ├── actions
│ │ ├── delete.blade.php
│ │ ├── edit.blade.php
│ │ └── view.blade.php
│ ├── tbody.blade.php
│ ├── td.blade.php
│ ├── th.blade.php
│ ├── thead.blade.php
│ └── tr.blade.php
└── table.blade.php
以下命令将帮助您创建这些组件
php artisan make:component table --view
php artisan make:component table.td --view
php artisan make:component table.th --view
php artisan make:component table.tr --view
php artisan make:component table.thead --view
php artisan make:component table.tbody --view
php artisan make:component table.actions.delete --view
php artisan make:component table.actions.edit --view
php artisan make:component table.actions.view --view
主要的表格组件x-table
如下所示
@props(['columns', 'records'])
<table {{ $attributes->merge(['id' => 'table','class' => 'table-auto w-full']) }}>
@if(isset($columns))
<x-table.thead :columns="$columns"/>
@if(isset($records))
<x-table.tbody :columns="$columns" :records="$records"/>
@endif
@endif
{{ $slot }}
</table>
此代码为标题和主体创建槽,传递例如列和记录的数据。
头部组件 x-table.thead
对列进行迭代
@props(['columns'])
<thead>
@if(isset($columns) && is_array($columns))
@foreach ($columns as $column)
<x-table.th field="{{ $column }}" />
@endforeach
@endif
{{ $slot }}
</thead>
头部单元格组件 x-table.th
生成排序URL并在需要时显示排序图标
@props(['field'])
@php
$sortField = request('sort_field');
$sortDir = request('sort_dir', 'asc') === 'asc' ? 'desc' : 'asc';
$sortIcon = fn($field) =>
$sortField === $field ? ($sortDir === 'asc' ? '↑' : '↓') : '';
$hxGetUrl = fn($field) =>
request()->fullUrlWithQuery([
'sort_field' => $field,
'sort_dir' => $sortDir
]);
@endphp
<th {{ $attributes->merge([
'class' => 'px-4 py-2 border text-left cursor-pointer',
'hx-get' => $hxGetUrl($field),
'hx-trigger' => 'click',
'hx-replace-url' => 'true',
'hx-swap' => 'outerHTML',
'hx-target' => '#table-container',
]) }}>
@if(isset($slot) && trim($slot) !== '')
{{ $slot }}
@else
<span>{{ Str::title($field) }}</span>
@endif
<span class="ml-1" role="img">{{ $sortIcon($field) }}</span>
</th>
而主体 x-table.tbody
对记录进行循环
@props(['columns', 'records'])
<tbody {{ $attributes->merge(['id' => 'table-body']) }}>
@if(isset($records))
@forelse ($records as $record)
<x-table.tr id="row-{{ $record->id }}">
@foreach($columns as $column)
<x-table.td>
@if($column === 'actions')
@if(isset($actions))
{{ $actions($record) }}
@else
<x-table.actions.view :record="$record"/>
<x-table.actions.edit :record="$record"/>
<x-table.actions.delete :record="$record"/>
@endif
@else
{{ $record->{$column} }}
@endif
</x-table.td>
@endforeach
</x-table.tr>
@empty
<x-table.tr>
<x-table.td colspan="100%">No record found.</x-table.td>
</x-table.tr>
@endforelse
@endif
{{ $slot }}
</tbody>
单独的单元格 x-table.td
负责显示数据
<td {{ $attributes->merge(['class' => 'px-4 py-2 border']) }}>
{{ $slot }}
</td>
而行 x-table.tr
提供包装
// Just for fun : time() - rand(100,2000)
<tr {{ $attributes->merge(['id' => "row-".(time() - rand(100,2000))]) }}>
{{ $slot }}
</tr>
同样,我们还可以集成如查看、编辑和删除的表格功能。务必探索全面指南以获取更多详细信息。
使用表格组件
现在我们已经将表格分解为可重用的Blade组件,让我们探索如何在我们的代码中应用它们。通过有效地利用这些组件,我们可以确保更加组织和流畅的方法。
让我们看看表格组件的实际应用
<div id="table-container"
hx-get="{{ route('contacts.index') }}"
hx-trigger="loadContacts from:body">
<x-table :records="$contacts"
:columns="['name', 'email', 'phone', 'address', 'actions']"/>
<div id="pagination-links" class="p-3"
hx-boost="true"
hx-target="#table-container">
{{ $contacts->links() }}
</div>
</div>
结束语
在将HTMX与Laravel结合的探索中,我们实现了动态表格排序和流畅的分页。通过创建可重用的Blade组件,我们简化了开发并改进了代码组织。
有关更详细信息,请查看我的博客文章,并在GitHub上找到此实现的源代码。
请确保关注我的Twitter以获取我发布更多内容的通知。
driesvints, spiritkiddie, mshaf liked this article