Alpine⚓︎
AlpineJS — это лёгкая JavaScript-библиотека, которая позволяет очень просто добавлять интерактивность на стороне клиента в ваши веб-страницы. Изначально она создавалась как дополнение к инструментам вроде Livewire, когда нужна более «чистая» JavaScript-утилита для точечного добавления интерактивности в приложение.
Livewire поставляется с уже встроенным Alpine, поэтому устанавливать его отдельно в проект не нужно.
Самое лучшее место, чтобы изучить использование AlpineJS — это официальная документация Alpine.
Простейший компонент на Alpine⚓︎
Чтобы заложить основу для дальнейшего материала, вот один из самых простых и показательных примеров компонента на Alpine. Небольшой «счётчик», который показывает число на странице и позволяет увеличивать его нажатием на кнопку:
<!-- Объявляем объект с данными JavaScript... -->
<div x-data="{ count: 0 }">
<!-- Выводим текущее значение count внутри элемента... -->
<h2 x-text="count"></h2>
<!-- При клике увеличиваем значение count на 1... -->
<button x-on:click="count++">+</button>
</div>
Компонент Alpine, приведённый выше, можно без проблем использовать внутри любого компонента Livewire в вашем приложении. Livewire самостоятельно заботится о сохранении состояния Alpine при обновлениях компонента Livewire. По сути, вы можете свободно использовать компоненты Alpine внутри Livewire точно так же, как если бы использовали Alpine в любом другом контексте, не связанном с Livewire.
Использование Alpine внутри Livewire⚓︎
Давайте рассмотрим более реалистичный пример использования компонента Alpine внутри компонента Livewire.
Ниже приведён простой компонент Livewire, который отображает детали модели поста из базы данных. По умолчанию показывается только заголовок поста:
<div>
<h1>{{ $post->title }}</h1>
<div x-data="{ expanded: false }">
<button type="button" x-on:click="expanded = ! expanded">
<span x-show="! expanded">Показать содержимое поста...</span>
<span x-show="expanded">Скрыть содержимое поста...</span>
</button>
<div x-show="expanded">
{{ $post->content }}
</div>
</div>
</div>
Используя Alpine, мы можем скрывать содержимое поста, пока пользователь не нажмёт кнопку «Показать содержимое поста...». В этот момент свойство expanded в Alpine будет установлено в true, и содержимое будет показано на странице, потому что x-show="expanded" используется для передачи Alpine контроля над видимостью содержимого поста.
Это пример того, где Alpine особенно хорош: добавление интерактивности в ваше приложение без лишних обращений на сервер Livewire.
Управление Livewire из Alpine с помощью $wire⚓︎
Одна из самых мощных возможностей, доступных вам как разработчику Livewire, — это $wire. Объект $wire — это магический объект, доступный во всех ваших Alpine-компонентах, которые используются внутри Livewire.
Можно думать о $wire как о шлюзе из JavaScript в PHP. Он позволяет вам получать доступ и изменять свойства компонента Livewire, вызывать методы компонента Livewire и делать гораздо больше — всё это изнутри AlpineJS.
Доступ к свойствам Livewire⚓︎
Вот пример простой утилиты «подсчёт символов» в форме создания поста. Она будет мгновенно показывать пользователю, сколько символов содержится в содержимом его поста, пока он печатает:
<form wire:submit="save">
<!-- ... -->
<input wire:model="content" type="text">
<small>
Количество символов: <span x-text="$wire.content.length"></span>
</small>
<button type="submit">Сохранить</button>
</form>
Как вы можете видеть, в приведённом выше примере x-text используется для того, чтобы позволить Alpine управлять текстовым содержимым элемента <span>. x-text принимает любое JavaScript-выражение внутри себя и автоматически реагирует на обновление любых зависимостей. Поскольку мы используем $wire.content для доступа к значению свойства $content, Alpine будет автоматически обновлять текстовое содержимое каждый раз, когда $wire.content обновляется со стороны Livewire — в данном случае благодаря wire:model="content".
Изменение свойств Livewire⚓︎
Вот пример использования $wire внутри Alpine для очистки поля «title» в форме создания поста.
<form wire:submit="save">
<input wire:model="title" type="text">
<button type="button" x-on:click="$wire.title = ''">Очистить</button>
<!-- ... -->
<button type="submit">Сохранить</button>
</form>
Пока пользователь заполняет приведённую выше форму Livewire, он может нажать «Очистить», и поле title будет очищено без отправки сетевого запроса от Livewire. Взаимодействие будет «мгновенным».
Вот краткое объяснение того, что происходит, чтобы это сработало:
x-on:clickуказывает Alpine слушать событие клика на элементе кнопки- При клике Alpine выполняет указанное JS-выражение:
$wire.title = '' - Поскольку
$wire— это магический объект, представляющий компонент Livewire, все свойства вашего компонента можно читать или изменять прямо из JavaScript $wire.title = ''устанавливает значение свойства$titleв вашем компоненте Livewire в пустую строку- Любые утилиты Livewire, такие как
wire:model, мгновенно отреагируют на это изменение — всё без лишних обращений к серверу - При следующем сетевом запросе Livewire свойство
$titleбудет обновлено до пустой строки уже на бэкенде
Вызов методов Livewire⚓︎
Alpine также может легко вызывать любые методы/действия Livewire, просто обращаясь к ним напрямую через $wire.
Вот пример использования Alpine для прослушивания события blur на поле ввода и запуска сохранения формы. Событие blur отправляется браузером, когда пользователь нажимает Tab, чтобы убрать фокус с текущего элемента и перевести его на следующий элемент на странице:
<form wire:submit="save">
<input wire:model="title" type="text" x-on:blur="$wire.save()">
<!-- ... -->
<button type="submit">Сохранить</button>
</form>
Обычно в такой ситуации вы бы просто использовали wire:model.live.blur="title", однако для демонстрационных целей полезно показать, как это можно реализовать с помощью Alpine.
Передача параметров⚓︎
Вы также можете передавать параметры в методы Livewire, просто передавая их в вызов метода через $wire.
Рассмотрим компонент с методом deletePost() примерно такого вида:
<?php
public function deletePost($postId)
{
$post = Post::find($postId);
// Проверяем, имеет ли пользователь право удалять...
auth()->user()->can('update', $post);
$post->delete();
}
Теперь вы можете передать параметр $postId в метод deletePost() из Alpine вот так:
В общем случае что-то вроде $postId обычно генерируется в Blade. Вот пример того, как с помощью Blade определить, какой именно $postId будет передан из Alpine в метод deletePost():
@foreach ($posts as $post)
<button type="button" wire:key="{{ $post->id }}" x-on:click="$wire.deletePost({{ $post->id }})">
Удалить "{{ $post->title }}"
</button>
@endforeach
Если на странице три поста, приведённый выше шаблон Blade отрендерится в браузере примерно так:
<button type="button" x-on:click="$wire.deletePost(1)">
Удалить «The power of walking»
</button>
<button type="button" x-on:click="$wire.deletePost(2)">
Удалить «How to record a song»
</button>
<button type="button" x-on:click="$wire.deletePost(3)">
Удалить «Teach what you learn»
</button>
Как вы можете видеть, мы использовали Blade, чтобы подставить разные ID постов в выражения Alpine x-on:click.
Подводные камни при передаче параметров из Blade⚓︎
Это чрезвычайно мощная техника, но она может запутывать при чтении ваших Blade-шаблонов. На первый взгляд бывает сложно понять, какая часть — это Blade, а какая — Alpine. Поэтому полезно проверять отрендеренный HTML на странице, чтобы убедиться, что всё подставилось именно так, как вы ожидали.
Вот пример, который часто сбивает с толку:
Допустим, вместо обычного ID ваша модель Post использует UUID в качестве первичного ключа (ID — это целые числа, а UUID — длинные строки символов).
Если мы отрендерим следующий код точно так же, как делали с ID, возникнет проблема:
<!-- Предупреждение: это пример проблемного кода... -->
<button
type="button"
x-on:click="$wire.deletePost({{ $post->uuid }})"
>
Приведённый выше шаблон Blade отрендерит в вашем HTML следующее:
<!-- Предупреждение: это пример проблемного кода... -->
<button
type="button"
x-on:click="$wire.deletePost(93c7b04c-c9a4-4524-aa7d-39196011b81a)"
>
Обратите внимание на отсутствие кавычек вокруг строки UUID? Когда Alpine попытается вычислить это выражение, JavaScript выдаст ошибку: "Uncaught SyntaxError: Invalid or unexpected token".
Чтобы исправить это, нужно добавить кавычки вокруг выражения Blade следующим образом:
Теперь приведённый выше шаблон отрендерится корректно, и всё будет работать как ожидается:
Обновление компонента⚓︎
Вы можете легко обновить компонент Livewire (запустить сетевой запрос на сервер для повторного рендеринга Blade-представления компонента) с помощью $wire.$refresh():
Совместное использование состояния через $wire.entangle⚓︎
Скорее всего, вам это не понадобится
Почти во всех случаях следует использовать $wire для прямого доступа к свойствам Livewire из Alpine вместо $wire.entangle(). Entangle создаёт дублированное состояние, что может приводить к проблемам с предсказуемостью и производительностью. Этот API поддерживается только ради обратной совместимости, но для нового кода его использование не рекомендуется.
Не используйте директиву Blade @entangle — она устарела и вызывает проблемы при удалении элементов DOM.
Для редких случаев, когда вам действительно нужна двунаправленная синхронизация состояния между Alpine и Livewire, можно использовать $wire.entangle():
<div x-data="{ open: $wire.entangle('showDropdown') }">
<button x-on:click="open = true">Показать ещё...</button>
<ul x-show="open">
<li><button wire:click="archive">Архивировать</button></li>
</ul>
</div>
По умолчанию изменения откладываются до следующего запроса Livewire. Используйте .live, чтобы синхронизировать сразу:
Использование директивы @js⚓︎
Если вам нужно вывести данные из PHP для прямого использования в Alpine, вы можете воспользоваться директивой @js.
Ручная сборка Alpine в вашем JavaScript-бандле⚓︎
По умолчанию JavaScript Livewire и Alpine автоматически подключается на каждую страницу с Livewire.
Это идеально для простых проектов, однако вы можете захотеть добавить собственные компоненты Alpine, хранилища (stores) и плагины в свой проект.
Подключить Livewire и Alpine через собственный JavaScript-бандл на странице довольно просто.
Сначала нужно добавить директиву @livewireScriptConfig в ваш файл макета (layout) следующим образом:
<html>
<head>
<!-- ... -->
@livewireStyles
@vite(['resources/js/app.js'])
</head>
<body>
{{ $slot }}
@livewireScriptConfig
</body>
</html>
Это позволяет Livewire предоставить вашему бандлу определённую конфигурацию, необходимую для корректной работы приложения.
Теперь вы можете импортировать Livewire и Alpine в ваш файл resources/js/app.js следующим образом:
import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';
// Здесь регистрируйте любые директивы, компоненты или плагины Alpine...
Livewire.start()
Вот пример регистрации пользовательской директивы Alpine под названием x-clipboard в вашем приложении:
import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';
Alpine.directive('clipboard', (el) => {
let text = el.textContent
el.addEventListener('click', () => {
navigator.clipboard.writeText(text)
})
})
Livewire.start()
Теперь директива x-clipboard будет доступна во всех ваших Alpine-компонентах в приложении Livewire.
Смотрите также⚓︎
- Свойства — Доступ к свойствам Livewire из Alpine через $wire
- Действия — Вызов действий Livewire из Alpine
- JavaScript — Выполнение пользовательского JavaScript в компонентах
- События — Отправка и прослушивание событий с помощью Alpine