We need a form for creating menus and a Livewire class with a method for storing the menus.
To better understand what we need to do, we will go over an example from a previous implementation below. This eample uses the same markup for creating and editing, however in our new implementation it may be simpler to have separate creating and editing forms, depending on which will be more efficient, and simpler to maintain.
Notice the form changes when you choose a diffrent type:
Under the hood this uses AlpineJS
Consider the following code:
(It is a lot but we are going to step through it a bit below)
<form method="POST" action="{{isset($model) ? url('/menus/'.$model->id):'/menus'}}">
@csrf @isset($model->id) @method('PATCH') @endisset
<div x-data="{ menuType: '{{isset($model->type) ? $model->type : 'main_menu'}}' }">
<div
class="form-group row"
>
<label for="type" class="col-md-3 col-form-label text-md-right">@lang('Type')</label>
<div class="col-md-9">
<select
x-on:change="menuType = $event.target.value"
id="type"
name="type"
class="form-control"
>
@if(isset($model->type))
<option value="{{$model->type}}">
@switch($model->type)
@case('internal_link')
Direct Local Link
@break
@case('external_link')
Direct External Link
@break
@case('main_menu')
Full Menu
@break
@default
@endswitch
</option>
@endif
<option value="main_menu">Full Menu</option>
<option value="internal_link">Direct Local Link</option>
<option value="external_link">Direct External Link</option>
</select>
</div>
</div><!--form-group-->
@include('frontend.menus.includes.form.partials.link', ['model' => $model??null])
</div>
@include('frontend.menus.includes.form.partials.active', ['model' => $model??null])
@include('frontend.menus.includes.form.partials.sort', ['model' => $model??null])
@include('frontend.menus.includes.form.partials.label', ['model' => $model??null])
@include('frontend.menus.includes.form.partials.title', ['model' => $model??null])
<input type="hidden" id="office" name="group" value="office"/>
{{-- <input type="hidden" id="office" name="type" value="main_menu"/> --}}
@can('admin.access.sidebar.admin')
<div class="form-group row">
<label for="group" class="col-md-3 col-form-label text-md-right">@lang('Group')</label>
<div class="col-md-9">
<select id="group" name="group" class="form-control">
@if(isset($model->group))
<option value="{{$model->group}}">
@switch($model->group)
@case('office')
office (cloud)
@break
@case('admin')
admin (settings)
@break
@default
@endswitch
</option>
@endif
<option value="office">office (cloud)</option>
<option value="admin">admin (settings)</option>
</select>
</div>
</div><!--form-group-->
@include('frontend.menus.includes.form.partials.permissions', ['model' => $model??null])
@endcan
@include('frontend.menus.includes.form.partials.icon-picker', ['model' => $model??null])
@include('frontend.menus.includes.form.partials.save-button')
</form>
<div x-data="{ menuType: '{{isset($model->type) ? $model->type : 'main_menu'}}' }">
The x-data attribute here sets up what we need for the change, notice the default here is ''main_menu"
<select
x-on:change="menuType = $event.target.value"
id="type"
name="type"
class="form-control"
>
Here in the first select of the form we have the x-on:change which fires whenever a new selection is made. This then sets the menuType
var equal to the value selected.
@if(isset($model->type))
<option value="{{$model->type}}">
@switch($model->type)
@case('internal_link')
Direct Local Link
@break
@case('external_link')
Direct External Link
@break
@case('main_menu')
Full Menu
@break
@default
@endswitch
</option>
@endif
<option value="main_menu">Full Menu</option>
<option value="internal_link">Direct Local Link</option>
<option value="external_link">Direct External Link</option>
And here are the options. Now there are a few things going on here but the @switch is just checking the $model->type
to so that the correct human readable option can be shown. This is only if we have a model e.g. @if(isset($model->type))
, or when the form is editing and not creating a new menu
.
<option value="main_menu">Full Menu</option>
<option value="internal_link">Direct Local Link</option>
<option value="external_link">Direct External Link</option>
Here we can see our option values which when selected will set the menuType
var.
@include('frontend.menus.includes.form.partials.link', ['model' => $model??null])
</div>
@include('frontend.menus.includes.form.partials.active', ['model' => $model??null])
@include('frontend.menus.includes.form.partials.sort', ['model' => $model??null])
@include('frontend.menus.includes.form.partials.label', ['model' => $model??null])
@include('frontend.menus.includes.form.partials.title', ['model' => $model??null])
This series of includes just lead to partials containing reusable form elements. Let's inspect one of them:
{{-- frontend.menus.includes.form.partials.link --}}
<div x-show="menuType !='main_menu'">
<div
class="form-group row"
>
<label for="iframe" class="col-md-3 col-form-label text-md-right">@lang('iFrame')</label>
<div class="col-md-9">
<select id="iframe" name="iframe" class="form-control">
@isset($model->iframe)
<option value="{{$model->iframe}}">{{$model->iframe === 1 ? 'Yes' : 'No'}}</option>
@endisset
<option value="0">No</option>
<option value="1">Yes</option>
</select>
</div>
</div><!--form-group-->
<div
class="form-group row"
>
<label for="link" class="col-md-3 col-form-label text-md-right">@lang('Url')</label>
<div class="col-md-9">
<input type="text" id="link" name="link" class="form-control" value="{{isset($model->link)?$model->link:''}}" />
</div>
</div><!--form-group-->
</div>
<div x-show="menuType !='main_menu'">
Notice here that the x-show attribute contains a conditional !=
. This form element will only be visible when the var menuType
in equal to main_menu
.
Some notes on creating the new Livewire implementation.
- The
LivewireComponent
class can have a createForm[]
property. The keys in each array can be modeled in the form with something like for instance wire:model.defer="createForm.type"
. And there should be a createMenu()
or storeMenu()
method for storing the model.
- The blade file for the form should be explicitly bound to the
LivewireComponent
class in App\Providers\LivewireServiceProvider
see Our Spec for an example.
- There also needs to be a test, and the test should utilize
Livewire::test()
. See this test for an example.
- We do not want to spent much time on the look for now, we want to just keep it simple and we can iterate on the design later, or perhaps involve someone else in the design. So in short it does not have to look good. It is perfectly fine if it looks horrible.
- We will need a
MenuController
class which can have just a create method which return the create view for now.
- And a create route for the menu. Please create a separate routes file for menu routes.