Laravel: Модальные окна с состоянием на основе роутов в связке Inertia + VueJS
Модальные окна могут превратить разработку любого проекта в кромешный ад. Но в более-менее серьёзном приложении без них просто не обойтись. Больше всего меня раздражает хранить состояние компонентов в общей бизнес логике приложения. В этой статье я делюсь опытом, как можно облегчить эти экзистенциальные муки.
Сегодня я поделюсь своим опытом работы с модалками в проекте на Laravel + Inertia + VueJS.
🚧 Статья рассчитана на тех, кто уже имеет минимальный опыт работы с Inertia.js и Vue.js
Доступные варианты
Использование Vue.js даёт нам отличные преимущества. Можно скачать огромное количество готовых компонентов типа: vue-js-modal. Или же можно набросать собственный простейший компонент. В любом из случаев прийдётся хранить состояние на стороне клиента, примерно таким образом:
<script>
export default {
data() {
return {
createModalIsOpen: false
}
},
mounted() {
if (location.hash === 'createModal') {
this.createModalIsOpen = true
}
}
}
</script>
Как видно из примера /resource#createModal
, мы полагаемся на хешь в адресной строке, но нам никак не получить это значение на бекенде. Можно конечно использовать url параметры resource?modal=createModal
, но вопрос передачи данных в модальное окно по-прежнему остаётся открытым.
Вариант решения
Самым логичным решением, как мне видится — будет создание роута вида: /resource/create
, ниже привожу пример моего контроллера:
class CompanyUserController
{
public function index(Company $company)
{
return inertia('Companies/Users/Index', [
'company' => $company,
'users' => $company
->users()
->orderBy('created_at', 'desc')
->paginate(),
]);
}
public function create(Company $company)
{
inertia()->modal('Companies/Users/CreateModal');
return $this->index($company);
}
}
Внимательный читатель заметит, что у нас нигде нет вызова метода модалки modal()
и подумает, что я, что-то упустил. Но ответ гораздо проще. Давайте присмотрим, что у нас тут в сервис провайдере AppServiceProvider
, а точнее в его методе boot()
// AppServiceProvider.php boot()
ResponseFactory::macro('modal', function ($modal) {
inertia()->share(['modal' => $modal]);
});
Я создал макрос для фабрики ответов из Inertia. В нём мы просто передаём адрес как пропс на фронтенд. Собственно давайте теперь его обработаем на фронтенде. Для этого я воспользуюсь миксинами из Vue.js
// UseModal.js
const useModal = {
computed: {
modalComponent() {
return this.$page.props.modal
? () => import(`@/Pages/${this.$page.props.modal}`)
: false
}
}
}
export { useModal }
Этот миксин проверяет, задан ли модальный компонент и либо динамически импортирует компонент Vue либо возвращает false
и ничего не рендерит. Символ @
это алиас для ./resources/js
это одна из возможностей Laravel Mix.
Сам по себе наш миксин ничего не делает. Нужно использовать его в глобальном инстансе нашего Vue приложения. Вот пример того, как это может выглядеть:
new Vue({
mixins: [useModal],
render: h => h(App, {
props: {
initialPage: JSON.parse(el.dataset.page),
resolveComponent: name => import(`./Pages/${name}`).then(module => module.default),
},
}),
}).$mount(el)
Остался последний вопрос, как же мы будем рендерить компонент модалки? Поскольку у нас теперь есть миксин, мы можем вызывать и рендерить компонент из любой части приложения. Пример вызова компонента:
<Component
v-bind="$page.props"
v-if="$root.modalComponent"
:is="$root.modalComponent"
/>
Это динамический Vue компонент, таким образом мы можем передавать данные для отображения, имя и путь в атрибут :is="<component>"
Обратите внимание, как мы проверяем наличие модального окна и как мы передаем данные. Модальное окно имеет доступ к пропсам, как и обычное представление Inertia
Как передать дополнительные параметры в пропсы?
Для того, что бы передать дополнительные данные, например список пользователей, ролей или иную связанную модель, нужно просто передать массив в наш index метод:
class CompanyUserController
{
public function index(Company $company, array $modalProps = [])
{
return inertia('Companies/Users/Index', array_merge([
'company' => $company,
'users' => $company
->users()
->orderBy('created_at', 'desc')
->paginate(),
], $modalProps));
}
public function create(Company $company)
{
inertia()->modal('Companies/Users/CreateModal');
return $this->index($company, [
'roles' => Role::all(),
'moreOptions' => ['...', '...'],
]);
}
}