100,000+ contributions in the last year
emargareten / inertia-modal Goto Github PK
View Code? Open in Web Editor NEWImplement backend-driven modal dialogs for Laravel+Inertia apps.
License: MIT License
Implement backend-driven modal dialogs for Laravel+Inertia apps.
License: MIT License
A request for an enhancement: to also accept Arrayable
types when passing the props to Inertia:modal()
.
Example:
public function edit(Request $request, ContactLog $contact_log)
{
return Inertia::modal(
'ContactLog/Edit/Modal',
EditContactLogViewModel::make($contact_log)->toArray() // <-- currently requires explicit ->toArray()
)->baseRoute('contact-logs.index');
}
This will make the behaviour more seamless and consistent, since Inertia::render() supports it; and also in general wherever things are returned or propagated down from controllers to responses (e.g., Eloquent API Resources).
I happened to stumble upon this as I was swapping a project from momentum-modal to this package, and everything was seamless except for this one aspect.
I can work on a PR for this when I find some time later, but wanted to note this down here for further discussion.
Thoughts?
I was trying to setup this package as I am planning to work with quite a few modals. I couldn't even get one to work. So far I am just getting an error saying
**App\Http\Controllers\Contact\Form::__invoke(): Return value must be of type Emargareten\InertiaModal\Modal, null returned**
I can't even tell what I am doing wrong. Below is my setup:
The modal component:
<script setup>
import { defineProps, watch } from 'vue'
import { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue'
import { IconTrash } from '@tabler/icons-vue'
import { Link, router, useForm } from '@inertiajs/vue3'
import { useModal } from 'vendor/emargareten/inertia-modal'
const props = defineProps({
contact: Object,
})
const { show, close, redirect } = useModal()
const form = useForm({
title: props.contact?.title,
description: props.contact?.description,
redirectUrl: `/boards/${props.contact?.board_id}`,
})
watch(() => props.contact, (contact) => {
if (contact) {
form.title = contact.title
form.description = contact.description
form.redirectUrl = `/boards/${contact.board_id}`
}
})
function closeModal() {
router.get(route('boards.show', { board: props.contact.board_id }), {}, {
preserveState: true,
})
}
function onSubmit() {
form.put(route('contacts.update', { contact: props.contact.id }))
}
</script>
<template>
<TransitionRoot
:show="show"
as="template"
appear
>
<Dialog
as="div"
class="relative z-10"
@close="close"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0"
enter-to="opacity-100"
leave="duration-200 ease-in"
leave-from="opacity-100"
leave-to="opacity-0"
@after-leave="redirect"
>
<div class="fixed inset-0 bg-black bg-opacity-40" />
</TransitionChild>
<div class="fixed inset-0 overflow-y-auto">
<div
class="flex items-start justify-center min-h-full px-4 py-12 text-center"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100"
leave="duration-200 ease-in"
leave-from="opacity-100 scale-100"
leave-to="opacity-0 scale-95"
>
<DialogPanel
class="w-full max-w-3xl overflow-hidden text-left align-middle transition-all transform bg-white rounded-md shadow-xl"
>
<div class="flex flex-col sm:flex-row">
<form
class="flex-1 p-5"
@submit.prevent="onSubmit"
>
<div>
<label
class="sr-only"
for="title"
>Title</label>
<textarea
id="title"
v-model="form.title"
class="block w-full text-sm border-gray-300 rounded-md shadow-sm focus:border-blue-400 focus:ring-blue-400"
name="title"
rows="1"
/>
</div>
<div class="mt-4">
<label
class="inline-block mb-1 text-sm font-semibold text-gray-700"
for="description"
>Description</label>
<textarea
id="description"
v-model="form.description"
class="block w-full text-sm border-gray-300 rounded-md shadow-sm focus:border-blue-400 focus:ring-blue-400"
name="description"
rows="4"
/>
</div>
<div class="mt-2 space-x-2">
<button
class="px-4 py-2 text-sm font-medium text-white rounded-md shadow-sm bg-rose-600 hover:bg-rose-500 focus:ring-2 focus:ring-offset-2 focus:ring-rose-500 focus:outline-none"
type="submit"
>
Save contact
</button>
<button
class="px-4 py-2 text-sm font-medium text-gray-700 rounded-md hover:text-black focus:ring-2 focus:ring-offset-2 focus:ring-rose-500 focus:outline-none"
type="button"
@click="close"
>
Cancel
</button>
</div>
</form>
<div class="p-5 bg-gray-100 sm:w-48">
<h3 class="mb-2 text-xs font-semibold tracking-wide text-gray-500 uppercase">
Actions
</h3>
<Link
:href="`/cards/${card?.id}`"
method="delete"
as="button"
class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-gray-200 rounded-md shadow-sm hover:bg-gray-300 focus:ring-2 focus:ring-offset-2 focus:ring-rose-500 focus:outline-none"
>
<IconTrash class="w-4 h-4 mr-1 -ml-1 shrink-0" />
<span>Delete contact</span>
</Link>
</div>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</template>
The plugin setup in app.ts:
createInertiaApp({
title: title => `${title} - ${appName}`,
resolve: name => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob<DefineComponent>('./Pages/**/*.vue')),
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(modal, {
resolve: name => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob<DefineComponent>('./Pages/**/*.vue')),
})
.use(plugin)
.use(ZiggyVue, Ziggy)
.mount(el)
},
progress: {
color: '#4B5563',
},
})
my routes:
Route::group(
['middleware' => 'auth'], function () {
Route::get(
'/',
\App\Http\Controllers\Contact\Index::class
)->name('contacts.index');
Route::get(
'/favourites',
\App\Http\Controllers\Contact\Index::class
)->name('contacts.favourites');
Route::get(
'/lists',
\App\Http\Controllers\Contact\Index::class
)->name('contacts.lists');
Route::get(
'/deleted',
\App\Http\Controllers\Contact\Index::class
)->name('contacts.deleted');
Route::get(
'/create',
\App\Http\Controllers\Contact\Form::class
)->name('contacts.create');
Route::post(
'/',
\App\Http\Controllers\Contact\Store::class
)->name('contacts.store');
Route::patch(
'/{contact}',
\App\Http\Controllers\Contact\Update::class
)->name('contacts.update');
Route::delete(
'/{contact}',
\App\Http\Controllers\Contact\Trash::class
)->name('contacts.destroy');
Route::get(
'/{contact:cid}',
\App\Http\Controllers\Contact\Show::class
)->name('contacts.show');
Route::get(
'/{contact:cid}/edit',
\App\Http\Controllers\Contact\Form::class
)->name('contacts.edit');
});
And lastly my controller is a self-invoking one:
<?php
namespace App\Http\Controllers\Contact;
use App\Http\Controllers\Controller;
use App\Models\Contact;
use Emargareten\InertiaModal\Modal;
use Inertia\Inertia;
class Form extends Controller
{
public function __invoke(Contact $contact = null): Modal
{
return Inertia::modal('Contacts/ContactForm', [
'contact' => $contact ?? new Contact(),
])->baseRoute('contacts.index');
}
}
What could be the issue? On the other hand, please improve on the documentation so people can easily get started
Lines 15 to 18 in 02b7ab5
This line of code is causing php artisan inertia:start-ssr
to fail. See: inertiajs/inertia#1849 (comment)
So while I managed probably setup the inertia-modal to work, I am still facing an issue with the following error when I try to open a route in a modal. The error says,
All Inertia requests must receive a valid Inertia response, however a plain JSON response was received.
{"props":{"errors":[],"auth":{"user":{"id":1,"full_name":"Kingsley Motion"},"avatar":""},"ziggy":{"url":"http://kwik-leak.test","port":null,"defaults":[],"routes":{"sanctum.csrf-cookie":{"uri":"sanctum/csrf-cookie","methods":["GET","HEAD"]},"ignition.healthCheck":{"uri":"_ignition/health-check","methods":["GET","HEAD"]},"ignition.executeSolution":{"uri":"_ignition/execute-solution","methods":["POST"]},"ignition.updateConfig":{"uri":"_ignition/update-config","methods":["POST"]},"contacts.index":{"uri":"/","methods":["GET","HEAD"]},"contacts.favourites":{"uri":"favourites","methods":["GET","HEAD"]},"contacts.lists":{"uri":"lists","methods":["GET","HEAD"]},"contacts.deleted":{"uri":"deleted","methods":["GET","HEAD"]},"contacts.create":{"uri":"create","methods":["GET","HEAD"]},"contacts.store":{"uri":"/","methods":["POST"]},"contacts.update":{"uri":"{contact}","methods":["PATCH"],"bindings":{"contact":"id"}},"contacts.destroy":{"uri":"{contact}","methods":["DELETE"],"bindings":{"contact":"id"}},"contacts.show":{"uri":"{contact}","methods":["GET","HEAD"],"bindings":{"contact":"cid"}},"contacts.edit":{"uri":"{contact}/edit","methods":["GET","HEAD"],"bindings":{"contact":"cid"}},"phones.create":{"uri":"phones/{contact}/create","methods":["GET","HEAD"],"bindings":{"contact":"cid"}},"phones.store":{"uri":"phones/{contact}","methods":["POST"],"bindings":{"contact":"cid"}},"phones.edit":{"uri":"phones/{contact}/edit","methods":["GET","HEAD"],"bindings":{"contact":"cid"}},"phones.update":{"uri":"phones/{contact}","methods":["PATCH"],"bindings":{"contact":"cid"}},"phones.destroy":{"uri":"phones/{contact}/{phone}","methods":["DELETE"],"bindings":{"contact":"cid","phone":"id"}},"companies.create":{"uri":"companies/{contact}/create","methods":["GET","HEAD"],"bindings":{"contact":"cid"}},"companies.store":{"uri":"companies/{contact}","methods":["POST"],"bindings":{"contact":"cid"}},"companies.edit":{"uri":"companies/{contact}/edit","methods":["GET","HEAD"],"bindings":{"contact":"cid"}},"companies.update":{"uri":"companies/{contact}","methods":["PATCH"],"bindings":{"contact":"cid"}},"companies.destroy":{"uri":"companies/{company}","methods":["DELETE"],"bindings":{"company":"compid"}},"categories.create":{"uri":"categories/create/{company?}","methods":["GET","HEAD"]},"categories.store":{"uri":"categories","methods":["POST"]},"categories.edit":{"uri":"categories/edit","methods":["GET","HEAD"]},"categories.update":{"uri":"categories/{category}","methods":["PATCH"],"bindings":{"category":"gid"}},"categories.destroy":{"uri":"categories/{category}","methods":["DELETE"],"bindings":{"category":"gid"}},"broad.search":{"uri":"search/{term?}","methods":["GET","HEAD"]},"mail.compose":{"uri":"{contact}/compose-mail","methods":["GET","HEAD"],"bindings":{"contact":"id"}},"mail.send":{"uri":"{contact}/send-mail","methods":["POST"],"bindings":{"contact":"id"}},"profile.edit":{"uri":"profile","methods":["GET","HEAD"]},"profile.update":{"uri":"profile","methods":["PATCH"]},"profile.destroy":{"uri":"profile","methods":["DELETE"]},"register":{"uri":"register","methods":["GET","HEAD"]},"login":{"uri":"login","methods":["GET","HEAD"]},"password.request":{"uri":"forgot-password","methods":["GET","HEAD"]},"password.email":{"uri":"forgot-password","methods":["POST"]},"password.reset":{"uri":"reset-password/{token}","methods":["GET","HEAD"]},"password.store":{"uri":"reset-password","methods":["POST"]},"verification.notice":{"uri":"verify-email","methods":["GET","HEAD"]},"verification.verify":{"uri":"verify-email/{id}/{hash}","methods":["GET","HEAD"]},"verification.send":{"uri":"email/verification-notification","methods":["POST"]},"password.confirm":{"uri":"confirm-password","methods":["GET","HEAD"]},"password.update":{"uri":"password","methods":["PUT"]},"logout":{"uri":"logout","methods":["POST"]}},"location":"http://kwik-leak.test/create"},"toast":null,"modal":{"component":"Contacts/ContactForm","redirectURL":"http://kwik-leak.test/","props":{"contact":[]},"key":"8c140d82-7ad6-4327-bd2b-8575a9a71bf2"}},"url":"/create","version":"45652216da2731bf5ea55c3ee2f75ec7"}
Why is the response a plain JSON? Is this intentional or do I need to adjust the headers manually?
I have a route, /users/5/edit
and pass the user into the modal as a prop using an HTTP Resource.
public function __invoke(User $user)
{
return Inertia::modal('User/Edit')
->with([
'user' => new UserResource($user),
])
->baseRoute('users.index');
}
If I go directly to the user edit page the user is wrapped in a "data" property. However, if I create an inertia link to open the user edit modal the user data is NOT wrapped in the data property.
When trying to figure out the problem myself I noticed the renderModal
function in Emargareten\InertiaModal\Modal
is only used when I click the inertia link and is not fired when I go directly to the edit user page.
Hi, first thanks for the useful package!
I encountered the following issue:
If the modal is closed and reopened before the URL adjusts to the redirect route, the redirect function targets the modal resulting in a cyclic redirect issue, thus the modal cannot be closed.
Hello,
i have a problem when i try to execute an action from an inertia modal. The message that i get is as follows All Inertia requests must receive a valid Inertia response, however a plain JSON response was received.
.
What i do is i have a sweet alert where if a user presses okay i execute the action in the controller.
Vue:
const startTicket = () => {
let form = useForm({
status: IN_PROGRESS,
text: "ЗАПОЧНАТО!",
});
Swal.fire({
title: "Започни тикет",
text: "Дали сте сигурен дека сакате да го започнете тикетот?",
}).then((result) => {
if (result.isConfirmed) {
form.post(route("tickets.automated_comments.store", props.ticket));
}
});
};
Route:
Route::post('/automated-comments', [TicketCommentController::class, 'storeAutomatedComment'])->name('automated_comments.store');
Controller function:
public function storeAutomatedComment(Ticket $ticket, TicketCommentRequest $request): RedirectResponse
{
$validated = $request->validated();
(new TicketService())->createTicketComment($ticket, $validated['text']);
$ticket->status = $request->status;
$ticket->save();
return redirect()->route('vehicles.tickets.show', [$ticket->vehicle_id, $ticket->id]);
}
Hi!
The current version 0.6.x does not support inertia-laravel v1.0. The Inertia version is required for Laravel 11 support.
Thanks very much for your work in this library.
Some context: I'm working with a project that was swapped from momentum-modal to this package.
I have a modal, which has an action within it that performs a task and then triggers a router.reload
:
router.reload({
preserveScroll: true,
replace: true,
});
In other words, the current URL (a modal route) is being reloaded. When this happens, I'm not seeing expected reloaded data reflected in the modal even though the router.reload
network request is made.
What I have to do now is:
router.reload({
preserveScroll: true,
replace: true,
onSuccess: () => {
// Explicitly update things that need to be updated
form.something = props.payload.something;
},
});
I tried playing with preserveState
, no difference whether true or false.
Is this a bug, or is there something else I'm missing or misunderstanding about how reloads work?
Sorry I don't know where else to contact you. I'm wondering of the improvements your packages makes compared to momentum-modal from which it is inspired?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.