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