Пакет Laravel htmx

Источник: «Laravel Htmx»
Laravel htmx — это пакет Maurizio Bonani, позволяющий работать с библиотекой htmx. Эта библиотека позволяет получать доступ к современным функциям браузера непосредственно из HTML, а не с помощью JavaScript:

htmx предоставляет доступ к AJAX, CSS Transitions, WebSockets и Server Sent Events непосредственно в HTML, используя атрибуты, что позволяет создавать современные пользовательские интерфейсы с простотой и мощью гипертекста.

htmx имеет небольшой размер (~14k min.gz), не содержит зависимостей, расширяем, совместим с IE11 и сократил размер кодовой базы на 67% по сравнению с react

Рассмотрим, какие возможности пакета laravel-htmx описаны в его readme:

Htmx Request

Из контейнера можно разрешить экземпляр HtmxRequest, предоставляющий ярлыки для чтения заголовков запросов, специфичных для htmx.

use Mauricius\LaravelHtmx\Http\HtmxRequest;

Route::get('/', function (HtmxRequest $request)
{
// всегда true, если запрос выполняется с помощью Htmx
$request->isHtmxRequest();
// указывает на то, что запрос осуществляется через элемент, использующий hx-boost
$request->isBoosted();
// текущий URL-адрес браузера
$request->getCurrentUrl();
// true, если запрос направлен на восстановление истории после пропущенного события в локальном кэше истории
$request->isHistoryRestoreRequest()
// ответ пользователя на hx-prompt
$request->getPromptResponse();
// id целевого элемента, если он существует
$request->getTarget();
// имя сработавшего элемента, если он существует
$request->getTriggerName();
// id сработавшего элемента, если он существует
$request->getTriggerId();
});

Htmx Response

HtmxResponseClientRedirect

htmx может вызывать перенаправление на стороне клиента, когда получает ответ с заголовком HX-Redirect. HtmxResponseClientRedirect позволяет легко инициировать такое перенаправление.

use Mauricius\LaravelHtmx\Http\HtmxResponseClientRedirect;

Route::get('/', function (HtmxRequest $request)
{
return new HtmxResponseClientRedirect('/somewhere-else');
});

HtmxResponseClientRefresh

htmx запускает перезагрузку страницы, когда получает ответ с заголовком HX-Refresh. HtmxResponseClientRefresh — это пользовательский класс ответа, позволяющий отправить такой ответ. Он не принимает никаких аргументов, поскольку htmx игнорирует любое содержимое.

use Mauricius\LaravelHtmx\Http\HtmxResponseClientRefresh;

Route::get('/', function (HtmxRequest $request)
{
return new HtmxResponseClientRefresh();
});

HtmxResponseStopPolling

При использовании триггера опроса htmx прекращает опрос, когда встречает ответ со специальным кодом состояния HTTP 286. HtmxResponseStopPolling — это пользовательский класс ответа с таким кодом состояния.

use Mauricius\LaravelHtmx\Http\HtmxResponseStopPolling;

Route::get('/', function (HtmxRequest $request)
{
return new HtmxResponseStopPolling();
});

Для всех остальных доступных заголовков можно использовать класс HtmxResponse.

use Mauricius\LaravelHtmx\Http\HtmxResponse;

Route::get('/', function (HtmxRequest $request)
{
return with(new HtmxResponse())
->location($location) // Позволяет выполнять переадресацию на стороне клиента без полной перезагрузки страницы
->pushUrl($url) // вставляет новый url в стек истории
->replaceUrl($url) // заменяет текущий URL в строке адресации
->reswap($option) // Позволяет указать, как будет происходить замена ответа
->retarget($selector); // CSS-селектор, который изменяет цель обновления содержимого на другой элемент на странице
});

Кроме того, можно запускать события на стороне клиента с помощью метода addTrigger.

use Mauricius\LaravelHtmx\Http\HtmxResponse;

Route::get('/', function (HtmxRequest $request)
{
return with(new HtmxResponse())
->addTrigger($event)
->addTriggerAfterSettle($event)
->addTriggerAfterSwap($event);
});

Вы можете вызывать эти методы несколько раз, чтобы вызвать несколько событий.

Рендеринг Blade Fragments с помощью Htmx

Эта библиотека также предоставляет базовое Blade расширение для отрисовки фрагментов шаблонов.

Библиотека предоставляет две новые директивы Blade: @fragment и @endfragment. С помощью этих директив можно указать блок содержимого в шаблоне и вывести только этот блок. Например:

{{-- /contacts/detail.blade.php  --}}
<html>
<body>
<div hx-target="this">
@fragment("archive-ui")
@if($contact->archived)
<button hx-patch="/contacts/{{ $contact->id }}/unarchive">Unarchive</button>
@else
<button hx-delete="/contacts/{{ $contact->id }}">Archive</button>
@endif
@endfragment
</div>
<h3>Contact</h3>
<p>{{ $contact->email }}</p>
</body>
</html>

Определив этот фрагмент в шаблоне, мы можем отобразить его целиком:

Route::get('/', function ($id) {
$contact = Contact::find($id);

return View::make('contacts.detail', compact('contact'));
});

Или же мы можем отобразить только фрагмент шаблона archive-ui, используя макрос renderFragment, определённый в классе \Illuminate\View\View:

Route::patch('/contacts/{id}/unarchive', function ($id) {
$contact = Contact::find($id);

// Следующие подходы являются эквивалентными

// Использование Фасада View
return \Illuminate\Support\Facades\View::renderFragment('contacts.detail', 'archive-ui', compact('contact'));

// Использование хелпера view()
return view()->renderFragment('contacts.detail', 'archive-ui', compact('contact'));

// Использование Фасада HtmxResponse
return \Mauricius\LaravelHtmx\Facades\HtmxResponse::renderFragment('contacts.detail', 'archive-ui', compact('contact'));

// Использование класса HtmxResponse
return with(new \Mauricius\LaravelHtmx\Http\HtmxResponse())
->renderFragment('contacts.detail', 'archive-ui', compact('contact'));
});

Поддержка OOB Swap

htmx поддерживает обновление нескольких целей путём возврата нескольких частичных ответов с помощью hx-swap-oob. С помощью этой библиотеки вы можете возвращать несколько фрагментов, используя HtmxResponse в качестве возвращаемого типа.

Допустим, мы хотим пометить todo как завершённое с помощью PATCH-запроса к /todos/{id}. Этим же запросом мы хотим обновить в нижнем колонтитуле количество оставшихся todo:

{{-- /todos.blade.php  --}}
<html>
<body>
<main hx-target="this">
<section>
<ul class="todo-list">
@fragment("todo")
<li id="todo-{{ $todo->id }}" @class(['completed' => $todo->done])>
<input
type="checkbox"
class="toggle"
hx-patch="/todos/{{ $todo->id }}"
@checked($todo->done)
hx-target="#todo-{{ $todo->id }}"
hx-swap="outerHTML"
/>
{{ $todo->name }}
</li>
@endfragment
</ul>
</section>
<footer>
@fragment("todo-count")
<span id="todo-count" hx-swap-oob="true">
<strong>{{ $left }} items left</strong>
</span>
@endfragment
</footer>
</main>
</body>
</html>

Мы можем использовать HtmxResponse для возвращения нескольких фрагментов:

Route::patch('/todos/{id}', function ($id) {
$todo = Todo::find($id);
$todo->done = !$todo->done;
$todo->save();

$left = Todo::where('done', 0)->count();

return HtmxResponse::addFragment('todomvc', 'todo', compact('todo'))
->addFragment('todomvc', 'todo-count', compact('left'));
});

Подробнее о пакете laravel-htmx можно узнать на GitHub.

Дополнительные материалы

Предыдущая Статья

Контроллеры и их истинное предназначение

Следующая Статья

Новое в Symfony 6.4: Улучшения Serializer