Привет, коллеги! 👋
Используя админ-панель 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!

Юрий