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.
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.
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
:
defineProps()
to the const props
, and we use them immediately in useForm
to attach the form valuespatch
and not post
The visual result:
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:
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.');}
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: