Now let's work on the restaurant edit form. In fact, it will be really similar to the create form. We've done most of the groundwork there.
Edit method in Controller
app/Http/Controllers/Admin/RestaurantController.php
public function edit(Restaurant $restaurant): Response{ $this->authorize('restaurant.update'); $restaurant->load(['city', 'owner']); return Inertia::render('Admin/Restaurants/Edit', [ 'restaurant' => $restaurant, 'cities' => City::get(['id', 'name']), ]);}
Similarly to the create()
method, we check the permissions and load the data into the Vue component.
We also need to use $restaurant->load()
to load the other relationships to use things like restaurant.owner.email
in the Vue.
In terms of routes, we don't need to add/change anything as we have Route::resource('restaurants', RestaurantController::class)
in the routes/web.php
file, which also automatically uses Route Model Binding, so we can pass Restaurant $restaurant
as a parameter here so that it would be automatically resolved.
Edit Restaurant: Vue Component
This is really similar to the Create.vue
. I will just point out a few differences below.
resources/js/Pages/Admin/Restaurants/Edit.vue:
<script setup>import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue'import { Head, useForm } from '@inertiajs/vue3'import InputError from '@/Components/InputError.vue'import InputLabel from '@/Components/InputLabel.vue'import PrimaryButton from '@/Components/PrimaryButton.vue'import SelectInput from '@/Components/SelectInput.vue'import TextareaInput from '@/Components/TextareaInput.vue'import TextInput from '@/Components/TextInput.vue' const props = defineProps({ cities: { type: Array }, restaurant: { type: Object }}) const form = useForm({ restaurant_name: props.restaurant.name, email: props.restaurant.owner.email, owner_name: props.restaurant.owner.name, city: props.restaurant.city_id, address: props.restaurant.address}) const submit = () => { form.patch(route('admin.restaurants.update', props.restaurant))}</script> <template> <Head :title="'Edit ' + restaurant.name" /> <AuthenticatedLayout> <template #header> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ 'Edit ' + restaurant.name }} </h2> </template> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 text-gray-900 overflow-x-scroll"> <div class="p-6 text-gray-900 overflow-x-scroll"> <form @submit.prevent="submit" class="flex flex-col gap-4"> <div class="form-group"> <InputLabel for="restaurant_name" value="Restaurant Name" /> <TextInput id="restaurant_name" type="text" v-model="form.restaurant_name" :disabled="form.processing" /> <InputError :message="form.errors.restaurant_name" /> </div> <div class="form-group"> <InputLabel for="email" value="Email" /> <TextInput id="email" type="email" v-model="form.email" :disabled="true" /> <InputError :message="form.errors.email" /> </div> <div class="form-group"> <InputLabel for="owner_name" value="Owner Name" /> <TextInput id="owner_name" type="text" v-model="form.owner_name" :disabled="true" /> <InputError :message="form.errors.owner_name" /> </div> <div class="form-group"> <InputLabel for="city" value="City" /> <SelectInput id="city" v-model="form.city" :options="cities" option-value="id" option-label="name" :default-option="{ id: '', name: 'City Name' }" :disabled="form.processing" /> <InputError :message="form.errors.city" /> </div> <div class="form-group"> <InputLabel for="address" value="Address" /> <TextareaInput id="address" v-model="form.address" class="resize-none" rows="3" :disabled="form.processing" /> <InputError :message="form.errors.address" /> </div> <div> <PrimaryButton :processing="form.processing">Update Restaurant</PrimaryButton> </div> </form> </div> </div> </div> </div> </div> </AuthenticatedLayout></template>
The differences from the Create.Vue
:
- We assign the
defineProps()
to theconst props
, and we use them immediately inuseForm
to attach the form values - The routes to submit are with
patch
and notpost
- I also made the fields of owner name/email disabled. They are here just for information purposes but are not editable, as we aren't going to re-create the user for them
The visual result:
Button to Edit Form
Now we need to update the Index.vue
and add a new column to the table with the Edit button.
resources/js/Pages/Admin/Restaurants/Index.vue
<th>Address</th> <th>Owner Name</th> <th>Owner Email</th> <th></th> </tr></thead><tbody>
restaurant.owner.email }}</a> </td> <td> <Link :href="route('admin.restaurants.edit', restaurant)" class="btn btn-secondary" > Edit </Link> </td> </tr> </tbody></table>
We also introduce a second button styling to make it a bit different:
resources/css/app.css
@layer components { // ... .btn-secondary { @apply bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 focus:bg-gray-50 active:bg-gray-50 focus:ring-primary-500 }}
The visual result:
Submit Form: Controller and Validation
Finally, we need to make our form actually work. Similarly to the create form example, we first generate the Form Request class:
php artisan make:request Admin/UpdateRestaurantRequest
app/Http/Requests/Admin/UpdateRestaurantRequest.php
namespace App\Http\Requests\Admin; use Illuminate\Foundation\Http\FormRequest;use Illuminate\Support\Facades\Gate; class UpdateRestaurantRequest extends FormRequest{ public function authorize(): bool { return Gate::allows('restaurant.update'); } public function rules(): array { return [ 'restaurant_name' => ['required', 'string', 'max:255'], 'city' => ['required', 'numeric', 'exists:cities,id'], 'address' => ['required', 'string', 'max:1000'], ]; }}
And we use that Form Request class in the new update()
method of Controller:
use App\Http\Requests\Admin\UpdateRestaurantRequest; public function update(UpdateRestaurantRequest $request, Restaurant $restaurant): RedirectResponse{ $validated = $request->validated(); $restaurant->update([ 'city_id' => $validated['city'], 'name' => $validated['restaurant_name'], 'address' => $validated['address'], ]); return to_route('admin.restaurants.index') ->withStatus('Restaurant updated successfully.');}
Display Success and Status Messages
Now, see that withStatus()
method? It should display the message somewhere on top, but it wouldn't work without a few extra steps.
We need to pass the session('status')
as additional "global" property in the HandleInertiaRequests
Middleware so that it would be available in the Vue components:
app/Http/Middleware/HandleInertiaRequests.php:
class HandleInertiaRequests extends Middleware{ public function share(Request $request): array { return array_merge(parent::share($request), [ 'auth' => [ 'user' => $request->user(), ], // ... 'status' => session('status'), ]); }}
And then, we can access that value as $page.props.status
in the Vue components.
For example, let's add that to the global layout, so we would be able to show any message from any Controller there.
resources/js/Layouts/AuthenticatedLayout.vue:
<template> // ... <main> <div v-if="$page.props.status" class="max-w-7xl mx-auto pt-6 px-4 sm:px-6 lg:px-8"> <div class="alert alert-success">{{ $page.props.status }}</div> </div> <slot /> </main></template>
Finally, let's define those alert CSS classes.
resources/css/app.css:
@layer components { // ... .alert { @apply font-medium p-6 rounded-xl; } .alert-success { @apply text-green-600 border border-green-500 bg-green-50; }}
The visual result:
Having a slight problem with the page.props.status it is not going away without refreshing the page. How do we get it to go away after about a minute or less?
this is default behavior and doesn't have a timeout to hide it. if you navigate to another page it will be gone.