Динамический выпадающий список Laravel livewire с множеством связей в одной модели

#laravel #laravel-livewire

#laravel #laravel-livewire

Вопрос:

У меня довольно сложная проблема, у меня есть животное

Модель

 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);
    }
}
 

У этого животного есть порода, пол, окрас и т. д.

Когда пользователь хочет добавить новое животное, ему представляется форма, эта форма представляет собой полностраничный компонент livewire.

 <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>
 

Форма заполняется с помощью этого класса livewire.

 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]);
    }
 

Как я могу сделать так, чтобы мои выпадающие списки стали динамическими? например, если пользователь выбирает dog тип животного, в breed раскрывающемся списке должны отображаться только соответствующие породы собак, а не кошки или лошади. Я попытался использовать некоторые из доступных онлайн-руководств, чтобы начать работу, но не смог разобраться со всеми отношениями, происходящими в моих моделях.

Комментарии:

1. имеет ли модель Breed animal_type_id ? чтобы мы могли изменять породы в соответствии с типом животного. если нет, нам нужно использовать саму модель Animal и получить идентификаторы пород, чтобы мы могли показывать конкретные породы в соответствии с типом animal

2. Да, у породы есть animal_type_id внутренняя сторона

Ответ №1:

Поскольку Breed модель имеет идентификатор animal_type, мы можем использовать обновленный хук Livewire для проверки изменений в типе animal и отображения только пород, связанных с типом animal.

итак, в компоненте livewire,

 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]);
    }


}
 

Обратите внимание, что я использовал computed property для получения пород.
Я также использовал when предложение, чтобы избежать проверки null.

итак, в файле блейда нам просто нужно wire:model 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>
....

 

Теперь породы будут отображаться на основе выбранного типа животного.

Я предположил animal_type_id , что это правильное имя столбца в Animal модели. если это не так, пожалуйста, измените имя столбца соответственно.