Сайт вебмастера

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

06-01-2021Время чтения ~ 4 мин.Alpine.js 5198

Как мы знаем, все компоненты 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
1 Bugo 2021-02-22 09:59:54

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

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


2 Admin 2021-02-22 10:06:17 admin

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

$dispatch('myevent1'...

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

@myevent1.window="...

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


3 Bugo 2021-02-22 10:31:11

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


4 Bugo 2021-02-22 10:37:50

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

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


5 Admin 2021-02-22 10:39:48 admin

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

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


6 Bugo 2021-02-22 10:45:01

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


7 Admin 2021-02-22 10:49:30 admin

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