Дата публикации: 19.07.2024 в 23:59

Кейс по динамическому обновлению данных в MoonShine с помощью Alpine JS

Евгений ЮрченкоЕвгений Юрченко
1 комментария

Привет, коллеги! 👋

Используя админ-панель MoonShine все основные CRUD операции закрываются очень просто и быстро, основная проблема с которой сталкиваешься - это динамическое обновление данных, например в форме редактирования сущности. Например, у нас есть интернет-магазин, есть модель товара и ProductResource для MoonShine. У товара есть поле "тип товара" и в зависимости от него нам нужно отображать разные поля в форме. Где-то полгода назад я встретился с похожей проблемой - обратился в ТГ чатик муншайна и к сожалению не нашёл ответа, также позднее, спустя несколько месяцев, также обращался уже по другому проекту и так не получил ответа. Я не работал ранее с библиотекой Alpine JS поэтому по старинке просто создал JS файл, подключил его к ресурсу и написал супер "костыльную" логику, потому что мне нужно было чтобы это просто как-то работало. Я слушал изменение select'a и просто отправлял форму, страница перезагружалась и уже отрабатывала логика рендеринга на PHP, тем самым отрисовывая нужные поля. Выглядело это конечно ужасно)

Alpine JS нас спасёт

Не так давно я столкнулся с похожей проблемой, но тут решение "просто чтобы работало" или "как угодно, главное работает" уже не подходит, так как заказчик более привередлив и проект делается так сказать "с душой".

Окинув взглядом документацию Alpine JS повторно, я задумался, а что нам мешает на поля накидывать x-show, x-model, x-data и реализовывать нашу динамическую логику, MoonShine предоставляет нам ->customAttributes() функцию, которая позволяет накидывать любые атрибуты на HTML элементы наших полей и также ->customWrapperAttributes() если требуется атрибут накинуть не на сам (select,input,checkbox) например, а на весь блок поля (далее покажу пример когда нам это может потребоваться).

Перейдём к примерам:

Для того чтобы где-то хранить наши переменные Alpine JS предоставляет аттрибут x-data, он объявляется на корневом элементе и все его потомки имеют доступ к этим переменным.

Делаем, например, декоративную обёртку Block для всех наших полей чтобы где-то хранить наши переменные. Добавляем наш аттрибут x-data где будем хранить переменные нашей формы.

Block::make([
   // наши поля будут тут
])->customAttributes([
   'x-data' => "{
      product_type: ". ($this->item?->product_type ?? 1) ."
   }"
]),


Теперь добавляем наш селект с типом товара (в моём случае я сделаю Enum поле) и связываем это поле с переменной product_type с помощью аттрибута x-model. Кто работал с vue js узнают похожую логику, там это v-model. Это аналогичный аттрибут, он связывает нашу переменную product_type в x-data со значением в этом поле.

Enum::make('Тип товара', 'product_type')
   ->attach(ProductType::class)
   ->customAttributes([
       'x-model' => 'product_type',
   ]),

Теперь имея переменную, которая автоматически обновляется при изменении выбора в селекте - нам остаётся только добавить саму логику отображения.

Добавим блок, который будет отображаться только при условии product_type === 2

В этом нам поможет x-show аттрибут, который показывает или скрывает элемент в зависимости от заданного условия. Во vue js это v-show.

Block::make([
   // поля для товара с product_type === 2
])
   ->customAttributes([
      'x-show' => 'product_type === 2',
   ]),

Будьте внимательны!
x-show работает по аналогу именно v-show, оно просто добавляет display:none для блока, в DOM он все равно при этом присутствует. v-if во vue js предоставляет другое поведение.

🎉Вуаля!🎉

Мы имеем динамическую логику не создавая какие-то отдельные JS файлы, не написав по сути JS кода как такового вообще!

Moonshine немного помог.

У MoonShine есть из коробки trait Reactivity, который предоставляет возможность реактивности, но к сожалению именно скрыть элемент с помощью него у меня пока не удавалось.
данный trait может нам немного помочь, если мы навесим на наше Enum поле этот trait, то MoonShine сам навесит аттриубут x-data на форму и создаст в переменную объект reactive, в котором и будет лежать наш product_type. И x-model вешать на само поле не требуется, moonshine будет делать это самостоятельно.

Т.е. в таком варианте нам остаётся только навесить x-show на нужный блок. Только условия в данном случае будет reactive.product_type === 2 так как MoonShine хранит все переменные в объекте reactive.

Вывод

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

Спасибо за прочтение! Всем интересных и больших проектов вместе с MoonShine!

Видео версия статьи на YouTube

Комментарии (1)

Юрий
Юрий
05.08.2024 в 20:01
Очень крутой кейс. Четко все рассписал, разложил по полочкам. Думаю реально будущее за MoonShine.
ОбщайсяРазвивайсяУчисьРаботай
ОбщайсяРазвивайсяУчисьРаботай
ОбщайсяРазвивайсяУчисьРаботай
ОбщайсяРазвивайсяУчисьРаботай