I have a fairly complex issue, I have an animal
model
class Animal extends Model
{
use HasFactory;
protected $fillable = [
"breed_ID",
"name",
"color_ID",
"eyes_color_ID",
"birth_date",
"animal_types_id",
"born_location",
"profile_picture_id",
"gender_ID",
"status",
"image",
"bio",
"lenght",
"weight",
"passport_url",
"chip_number",
"breeder_ID",
];
protected function genders(): BelongsTo
{
return $this->belongsTo(Gender::class);
}
public function borns(): BelongsTo
{
return $this->belongsTo(Born::class);
}
public function eyeColors(): BelongsTo
{
return $this->belongsTo(EyeColor::class);
}
public function colors(): BelongsTo
{
return $this->belongsTo(Color::class);
}
public function breeders(): BelongsTo
{
return $this->belongsTo(Breeder::class);
}
public function weights(): BelongsTo
{
return $this->belongsTo(Weight::class);
}
public function lengths(): BelongsTo
{
return $this->belongsTo(Length::class);
}
public function users(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function animalTypes(): BelongsTo
{
return $this->belongsTo(AnimalType::class);
}
public function images(): HasMany
{
return $this->hasMany(Image::class);
}
}
This animal has a breed, gender, color e.t.c
When a user wants to add a new animal they are presented a form, this form is a full page livewire component.
<main class="add-animal-page">
<section class="relative">
<div class="container px-4 mx-auto">
<div
class="flex flex-col justify-center w-full min-w-0 mb-6 break-words rounded-lg shadow-xl xl:flex-row bg-gray-50">
<form enctype="multipart/form-data" class="flex justify-center" wire:submit.prevent="upload">
<div class="w-full xl:w-4/6">
<div class="flex justify-center px-4 py-5 sm:p-6">
<div class="grid max-w-4xl grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3 lg:col-span-2">
<label for="first_name" class="block text-sm font-medium text-gray-700">Name</label>
<input type="text" name="first_name" id="first_name" autocomplete="given-name"
wire:model.defer="animal.name"
class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div class="col-span-6 sm:col-span-6">
<label for="type" class="block text-sm font-medium text-gray-700">
Type</label>
<select wire:model="animal.breeds_id" name="breed" id="breed"
class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="">Choose a type</option>
@foreach ($types as $type)
<option value={{ $type->id }}>{{ $type->animal_name }}</option>
@endforeach
</select>
</div>
<div class="col-span-6 sm:col-span-6">
<label for="breed" class="block text-sm font-medium text-gray-700">
Breed</label>
<select wire:model="animal.breeds_id" name="breed" id="breed"
class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="">Choose a breed</option>
@foreach ($breeds as $breed)
<option value={{ $breed->id }}>{{ $breed->breed_name }}</option>
@endforeach
</select>
</div>
<div class="col-span-6 sm:col-span-6">
<label for="breed" class="block text-sm font-medium text-gray-700">
Breed</label>
<select wire:model="animal.breeds_id" name="breed" id="breed"
class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="">Choose a breed</option>
@foreach ($breeds as $breed)
<option value={{ $breed->id }}>{{ $breed->breed_name }}</option>
@endforeach
</select>
</div>
<div class="col-span-6 sm:col-span-6">
<label for="gender" class="block text-sm font-medium text-gray-700">
Gender</label>
<select wire:model="animal.genders_id" name="gender" id="gender"
class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="">Choose a gender</option>
@foreach ($genders as $gender)
<option value={{ $gender->id }}>{{ $gender->type }}</option>
@endforeach
</select>
</div>
<div class="col-span-6 sm:col-span-6">
<label for="eye_color" class="block text-sm font-medium text-gray-700">
Eye color</label>
<select wire:model="animal.eye_color_id" name="eye_color" id="eye_color"
class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="">Choose an eye color</option>
@foreach ($eyeColors as $eyeColor)
<option value={{ $eyeColor->id }}>{{ $eyeColor->name }}</option>
@endforeach
</select>
</div>
<div class="col-span-6 sm:col-span-6">
<label for="Breeder" class="block text-sm font-medium text-gray-700">
Breeder</label>
<select wire:model="animal.breeders_id" name="Breeder" id="Breeder"
class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="">Choose a breeder</option>
@foreach ($breeders as $breeder)
<option value={{ $breeder->id }}>{{ $breeder->name }}</option>
@endforeach
</select>
</div>
<div class="col-span-6 sm:col-span-6">
<label for="passport" class="block text-sm font-medium text-gray-700">
Passport URL</label>
<input type="text" wire:model.defer="animal.passport_url" name="passport"
id="passport" autocomplete="text"
class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div class="col-span-6 sm:col-span-6">
<label for="chip_number" class="block text-sm font-medium text-gray-700">
Chip number</label>
<input type="text" wire:model.defer="animal.chip_number" name="chip_number"
id="chip_number" autocomplete="chip_number"
class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div class="col-span-6">
<label for="bio" class="block text-sm font-medium text-gray-700">
Bio
</label>
<div class="mt-1">
<textarea wire:model.defer="animal.bio" id="bio" name="bio" rows="3"
class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</textarea>
</div>
</div>
</div>
</div>
<div class="max-w-md mx-auto overflow-hidden rounded-lg md:max-w-xl">
<div class="md:flex">
<div class="w-full p-3 ">
<div
class="relative flex items-center justify-center h-48 bg-gray-100 border-2 border-dotted rounded-lg border-primary-light">
<div class="absolute">
<div class="flex flex-col items-center"><i
class="fa fa-folder-open fa-4x text-primary"></i> <span
class="block font-normal text-gray-400">Upload your image
here</span>
</div>
</div>
<input wire:model.defer="image" type="file"
class="w-full h-full opacity-0 cursor-pointer">
</div>
</div>
</div>
</div>
</div>
<div class="absolute bottom-0 right-0 px-6 py-3 mx-4 my-6 text-right bg-gray-50 sm:px-6">
<button type="submit"
class="inline-flex justify-center px-4 py-2 text-sm font-medium text-white border border-transparent rounded-md shadow-sm bg-primary hover:bg-primary-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Save
</button>
</div>
</form>
</div>
</div>
</section>
</main>
The form is populated via this livewire class.
class AddAnimal extends Component
{
use WithFileUploads;
public User $user;
public Animal $animal;
public Collection $genders;
public Collection $eyeColors;
public Collection $breeds;
public Collection $colors;
public Collection $breeders;
public Collection $types;
public $image;
protected array $rules = [
'animal.name' => 'required|min:2',
'animal.eye_color_id' => 'nullable',
'animal.bio' => 'nullable',
'animal.breeds_id' => 'nullable',
'animal.genders_id' => 'nullable',
'animal.breeders_id' => 'nullable',
'animal.chip_number' => 'nullable',
'animal.passport_url' => 'nullable',
'image' => 'nullable',
];
public function mount(User $user)
{
$this->user = $user;
$this->animal = new Animal();
$this->genders = Gender::all();
$this->eyeColors = EyeColor::all();
$this->breeds = Breed::all();
$this->colors = Color::all();
$this->breeders = Breeder::all();
$this->types = AnimalType::all();
}
public function render()
{
return view('livewire.add-animal')
->layout('components.layouts.dashboard', ['title' => 'Add-animal'])
->with(['user' => $this->user, 'genders' => $this->genders, 'eyeColors' => $this->eyeColors, 'breeds' => $this->breeds, 'colors' => $this->colors, 'breeders' => $this->breeders, 'types' => $this->types]);
}
How can I make it so that my dropdowns become dynamic? e.g if a user selects dog
as animal type, in the breed
dropdown only relevant dog breeds should be shown, and not cats or horses. I tried using some of the online available tutorials to get me started, but could not figure it out with all the relationships going on in my models.
Since the Breed
Model has an animal_type id, we can use Livewire's updated hook to check for changes on the animal type and render only breeds related to the animal type.
so in the livewire component,
class AddAnimal extends Component
{
public User $user;
public Animal $animal;
public Collection $genders;
public Collection $eyeColors;
// public Collection $breeds; we will use a computed property
public Collection $colors;
public Collection $breeders;
public Collection $types;
public $image;
// newly added variable to keep track of animal type changed
public $filters = [
'animal_type_id' => ''
];
protected array $rules = [
'animal.name' => 'required|min:2',
'animal.eye_color_id' => 'nullable',
'animal.bio' => 'nullable',
'animal.breeds_id' => 'nullable',
'animal.genders_id' => 'nullable',
'animal.breeders_id' => 'nullable',
'animal.chip_number' => 'nullable',
'animal.passport_url' => 'nullable',
'image' => 'nullable',
'animal.animal_type_id' => '', // make sure you have the rule can be left empty if its not required
];
public function mount(User $user)
{
$this->user = $user;
$this->animal = new Animal();
$this->genders = Gender::all();
$this->eyeColors = EyeColor::all();
$this->colors = Color::all();
$this->breeders = Breeder::all();
$this->types = AnimalType::all();
}
public function updatedAnimalAnimalTypeId($value)
{
$this->filters['animal_type_id'] = $value;
}
public function getBreedsProperty()
{
return Breed::when($this->filters['animal_type_id'], function($query, $animal_type_id){
return $query->where('animal_type_id', $animal_type_id);
})->get();
}
public function render()
{
return view('livewire.add-animal')
->layout('components.layouts.dashboard', ['title' => 'Add-animal'])
->with(['user' => $this->user, 'genders' => $this->genders, 'eyeColors' => $this->eyeColors, 'breeds' => $this->breeds, 'colors' => $this->colors, 'breeders' => $this->breeders, 'types' => $this->types]);
}
}
Note that I have used computed property
to get the breeds.
I also have made use of when
clause to avoid the null check.
so in the blade file, we just have to wire:model
the animal_type_id
.
....
<div class="col-span-6 sm:col-span-6">
<label for="type" class="block text-sm font-medium text-gray-700">
Type</label>
<select wire:model="animal.animal_type_id" name="animal_type_id" id="animal_type_id"
class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="">Choose a type</option>
@foreach ($types as $type)
<option value={{ $type->id }}>{{ $type->animal_name }}</option>
@endforeach
</select>
</div>
....
Now the breeds will be rendered based on the animal type selected.
I have assumed animal_type_id
is the correct column name in Animal
model. if its not, please change the column name respectively.