Взаимодействие между Alpine-компонентами
06-01-2021Reading time ~ 4 min.Alpine.js 5372
Как мы знаем, все компоненты Alpine работают изолировано. Директива x-data
определяет сам компонент и всё «реактивное» взаимодействие происходит в пределах данного блока.
Но что делать, если нужно организовать связь между несколькими компонентами? Формально мы можем использовать сторонний js-код, в котором хранить общие переменные. Или даже использовать LocalStorage, чтобы сохранять состояние после закрытия страницы. Но всё это скорее обходные манёвры. Попробуем сделать это на нативном функционале Alpine.js.
Вообще зачем это нужно? Представим себе, что у нас есть страница, где несколько div-блоков, которые из соображений вёрстки нельзя разместить в одном родителе. Например будет ряд кнопок, нажав на которые будет выводиться блок сообщения. Причем пусть каждая кнопка должна ещё передавать дополнительные параметры, например css-класс.
Стандартно такой механизм в JavaScript называется пользовательские события (CustomEvent). То есть идея в том, что кнопки будут генерировать какое-то событие, а блок сообщения его перехватывать.
В Alpine для этого можно использовать магические свойства $dispatch
и $event
. Первое — это генерация события, второе — его получение.
Рассмотрим базовый пример. Вначале полный код:
<button x-data @click="$dispatch('myevent1', {show: 'open', tclass: 't-red'})">show red</button> <button x-data @click="$dispatch('myevent1', {show: 'open', tclass: 't-blue'})">show blue</button> <button x-data @click="$dispatch('myevent1', {show: 'hide', tclass: 'b-hide'})">hide</button> <div x-data="{show: '-', tclass: '-'}" @myevent1.window="show = $event.detail.show; tclass = $event.detail.tclass"> <div x-text="'Status: ' + show"></div> <div x-text="'Class: ' + tclass" :class="tclass"></div> </div>
Все блоки представляют собой независимые Alpine-компоненты. У кнопок мы цепляемся за событие onclick
. По клику будет выполняться магический метод/функция $dispatch()
. Первый параметр — это имя генерируемого события, второй — его произвольные данные.
Блок сообщения (DIV) будет принимать это событие. Код @myevent1.window
— это перехват события (сокращённая запись x-on:
), где .window
указывает на перехват «глобального» window-события.
То есть как только появится событие myevent1
, сработает данный код. В нём $event
это «стандартный» объект js-события, только через detail
возвращает переданные данные. В данном случае это show
и tclass
события, которые мы присваиваем текущему компоненту.
Дальше мы работаем с данными компонента как обычно.
Небольшое замечание по поводу передаваемых данных. Не нужно передавать булевые значения (true
иfalse
). В процессе передачи (в некоторых случаях - из полей форм) они преобразуются в строки'true'
и'false'
, а значит кодx-show="show"
всегда будет возвращать логическоеtrue
, посколькуBoolean('false')
равен true.
Рассмотрим ещё один пример того, как можно создавать события от полей ввода, где используется x-model
. Вначале полный код примера:
<div x-data="{show: ''}" @myevent2.window="show = $event.detail.show"> <div x-show="show == 'show'">message</div> </div> <div x-data="{s: 'hide'}" x-init="$watch('s', value => $dispatch('myevent2', {show: value}))"> <select x-model="s"> <option value="hide">hide</option> <option value="show">show</option> </select> </div>
Блок «message» ловит пользовательское событие myevent2
, по которому задаётся переменная show
. На неё завязано отображения блока с помощью x-show
.
Блок «select» более сложный. Вначале мы связываем поле ввода через переменную s
. Поскольку это «реактивность», то код очень простой. Для того, чтобы отправить событие через $dispatch
нам нужно прицепиться к какому-то событию, например onchange
для SELECT и сделать аналогично примеру с кнопками.
Такой вариант неплох для простого html-кода, если же у нас более сложная вёрстка, то по возможности лучше её упростить. К тому же у нас уже есть «реактивность» из «коробки» Alpine, поэтому оптимальным вариант — это генерировать событие, как только изменится переменная s
(по любому поводу).
Здесь Alpine предлагает ещё одно магическое свойство $watch
— функция, которая отслеживает изменение любой переменной компонента. То есть как только s
изменится (помним, что она «реактивная» за счёт x-model
), сработает $watch
, который выполнит код, где мы и пропишем генерацию события с помощью $dispatch()
.
Ещё один вопрос, который стоит рассмотреть. Можем ли мы повлиять на принимаемый компонент — передать ему некие «начальные» данные. Скажем в примере с кнопками мы хотим передать другой класс и статус видимости.
Поскольку компоненты изолированные, то передачу можно сделать только через событие. В данном случае проще всего прицепиться к стандартному onload
:
<div x-data @load.window="$dispatch('myevent1', {show: 'open', tclass: 't-green'})"></div>
То есть сразу после загрузки страницы этот код сгенерирует событие с нужными данными. Как ни странно, но такой подход заменяет довольно сложный js-код. :-)
Вопрос по теме — как наладить взаимодействие между компонентами в этом кусочке кода, чтобы при клике на ссылку переключался статус только в отдельной строке таблицы, а не во всех сразу.
https://codepen.io/dragomano/pen/eYmXrrP
У ссылки отправляете событие
У принимающего SPAN ловите
События у каждой пары A/SPAN должны быть индивидуальные.
Это понятно, но если в таблице строк 10+, 100+ и так далее, а в названиях событий почему-то не поддерживаются цифры (то есть я не могу почему-то использовать названия типа "myevent" + id строки), неужели придется генерировать случайные названия для каждой строки?
Разобрался. Я называл события так: "my_event_1", а нужно так: "myevent1" :)
https://codepen.io/dragomano/pen/eYmXrrP
Ну вы сами придумали такую схему. :-)
Архитектурно такой алгоритм неверный. Нужно всю таблицу сделать одним компонентом, а каждую строку TR выводить в цикле TEMPLATE, где есть свой счётчик к которому можно привязаться. Да и события в такой схеме просто не нужны.
Шаблон предоставляется CMS, я могу влиять только на содержимое ячеек (td), приходится изворачиваться :)
В теории можно использовать для всех одно событие, но в свойствах события передавать ID. Но это всё равно нужно проставлять их уникальными. Иначе невозможно будет различать пары между собой.