Взаимодействие между Alpine-компонентами

Как мы знаем, все компоненты 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().

message

Ещё один вопрос, который стоит рассмотреть. Можем ли мы повлиять на принимаемый компонент — передать ему некие «начальные» данные. Скажем в примере с кнопками мы хотим передать другой класс и статус видимости.

Поскольку компоненты изолированные, то передачу можно сделать только через событие. В данном случае проще всего прицепиться к стандартному onload:

<div x-data @load.window="$dispatch('myevent1', {show: 'open', tclass: 't-green'})"></div>

То есть сразу после загрузки страницы этот код сгенерирует событие с нужными данными. Как ни странно, но такой подход заменяет довольно сложный js-код. :-)

Комментариев: 7 RSS

1Bugo22-02-2021 09:59

Вопрос по теме — как наладить взаимодействие между компонентами в этом кусочке кода, чтобы при клике на ссылку переключался статус только в отдельной строке таблицы, а не во всех сразу.

https://codepen.io/dragomano/pen/eYmXrrP

2MAX22-02-2021 10:06

У ссылки отправляете событие

$dispatch('myevent1'...

У принимающего SPAN ловите

@myevent1.window="...

События у каждой пары A/SPAN должны быть индивидуальные.

3Bugo22-02-2021 10:31

Это понятно, но если в таблице строк 10+, 100+ и так далее, а в названиях событий почему-то не поддерживаются цифры (то есть я не могу почему-то использовать названия типа "myevent" + id строки), неужели придется генерировать случайные названия для каждой строки?

4Bugo22-02-2021 10:37

Разобрался. Я называл события так: "my_event_1", а нужно так: "myevent1" :)

https://codepen.io/dragomano/pen/eYmXrrP

5MAX22-02-2021 10:39

Ну вы сами придумали такую схему. :-)

Архитектурно такой алгоритм неверный. Нужно всю таблицу сделать одним компонентом, а каждую строку TR выводить в цикле TEMPLATE, где есть свой счётчик к которому можно привязаться. Да и события в такой схеме просто не нужны.

6Bugo22-02-2021 10:45

Шаблон предоставляется CMS, я могу влиять только на содержимое ячеек (td), приходится изворачиваться :)

7MAX22-02-2021 10:49

В теории можно использовать для всех одно событие, но в свойствах события передавать ID. Но это всё равно нужно проставлять их уникальными. Иначе невозможно будет различать пары между собой.

Оставьте комментарий!

Комментарий будет опубликован после проверки. Вы соглашаетесь с правилами сайта.

(обязательно)