Back to Course |
Laravel Vue Inertia: Food Ordering Project Step-By-Step

Edit Restaurant Form

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 the const props, and we use them immediately in useForm to attach the form values
  • The routes to submit are with patch and not post
  • 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: