phplaravelformslaravel-bladelaravel-livewire

Laravel Livewire form with multiple rows and multiple related selects


I try to create a form that create new rows at demmand. This rows have each one diferents selects related one each other: when one is updated, the other is updated their options list. But when updated the select of one row, form update all rows.

This is the root form:

 @foreach ($lineasComponentes as $componente)
    <div class="row linea-componente" style="width: 1620px;">
        <div class="col-lg-9">
            <div class="row">
                <div class="col-sm-2 mx-0 px-0"><livewire:form.familias :indexLoop="$loop->index" :familia_id="$componente->familia_id"></livewire:form.familias></div>
                <div class="col-sm-2 mx-0 px-0"><livewire:form.sub-familias :indexLoop="$loop->index"  :familia_id="$componente->familia_id" :sub_familia_id="$componente->sub_familia_id"></livewire:form.sub-familias></div>
                <div class="col-sm-4 mx-0 px-0"><livewire:form.componentes :indexLoop="$loop->index" :familia_id="$componente->familia_id" :sub_familia_id="$componente->sub_familia_id" :componente_id="$componente->material_id"></livewire:form.componentes></div>
            </div>
        </div>
        <button type="button" class="btn btn-danger elimina-componente">Eliminar</button>
    </div>
@endforeach

And these are the livewire components:

familias.blade.php

<select id="familias_componentes{{$ind}}" wire:model.live="formFamilia" class="form-control">
    @foreach ($familias as $familia)
        <option wire:key="{{$familia->id}}" value="{{$familia->id}}">{{$familia->nombre}}</option>
    @endforeach
</select>

Familias.php

class Familias extends Component
{
    public $formFamilia;
    public $familia_id;
    public $indexLoop;

    public function mount()
    {
        $this->formFamilia = $this->familia_id;
    }

    public function updatedFormFamilia()
    {
        $this->dispatch('familiaSelected', familiaId: $this->formFamilia);
    }

    public function render()
    {
        $familias = DB::table('familias')
        ->whereNotIn('id', [8,9])
        ->get();
        $ind = $this->indexLoop;
        return view('livewire.form.familias', compact('familias', 'ind'));
    }
}

sub-familias.blade.php

<select id="subfamilias_componente{{$ind}}" wire:model.live="formSubFamilia" class="form-control">
    @foreach ($subfamilias as $subfamilia)
        <option wire:key="{{ $subfamilia->id }}" value="{{ $subfamilia->id }}">{{ $subfamilia->nombre }}</option>
    @endforeach
</select>

SubFamilias.php

class SubFamilias extends Component
{
    public $formSubFamilia;
    public $sub_familia_id;
    public $familia_id;
    public $indexLoop;

    public function mount()
    {
        $this->formSubFamilia = $this->sub_familia_id;
    }

    #[On('familiaSelected')]
    public function setFamilia($familiaId)
    {
        $this->familia_id = $familiaId;
    }

    public function updatedFormSubFamilia()
    {
        $this->dispatch(
            'subfamiliaSelected',
            subfamiliaId: $this->formSubFamilia,
            familiaId: $this->familia_id
        );
    }

    public function render()
    {
        $subfamilias = DB::table('factor_precios as fp')
            ->select('sf.id', 'sf.nombre')
            ->leftjoin('sub_familias as sf', 'sf.id', '=', 'fp.sub_familia_id')
            ->where('fp.familia_id', '=', $this->familia_id)
            ->where('sf.id', '!=', '3')
            ->get();
        $ind = $this->indexLoop;
        return view('livewire.form.sub-familias', compact('subfamilias', 'ind'));
    }
}

componentes.blade.php

<select id="componentes{{$ind}}" wire:model.live="formComponente" class="form-control">
    @foreach ($componentes as $componente)
        <option wire:key="{{ $componente->id }}" value="{{ $componente->id }}">{{ $componente->nombre }}</option>
    @endforeach
</select>

Componentes.php

class Componentes extends Component
{
    public $formComponente;
    public $componente_id;
    public $familia_id;
    public $sub_familia_id;
    public $indexLoop;

    public function mount()
    {
        $this->formComponente = $this->componente_id;
    }

    #[On('subfamiliaSelected')]
    public function setFamilia($subfamiliaId, $familiaId)
    {
        $this->familia_id = $familiaId;
        $this->sub_familia_id = $subfamiliaId;
    }

    public function render()
    {
        $componentes = DB::table('materials as m')
        ->select('m.id', 'm.nombre')
        ->where('m.familia_id', '=', $this->familia_id)
        ->where('m.sub_familia_id', '=', $this->sub_familia_id)
        ->get();
        $ind = $this->indexLoop;
        return view('livewire.form.componentes', compact('componentes', 'ind'));
    }
}

resume: I have created a form in Laravel, with livewire components, that adds new rows on demmand. Each of those rows has three selects. The list of options in the second and third selects depend on what is selected in the previous select. Each select is a livewire component associated with the previous one (each change triggers an event, which is captured in the next component). The problem is that when I update one of the selects, the next component is updated, but in all the rows, not just in the one where the component is updated. I need the components to react to the components in their own row, not to update all the rows, as is currently the case.

Sorry for my english, I'm not a native speaker


Solution

  • To anyone who finds it useful: I got the behavior I was expecting. I use the index of the loop in the root form to identify the row to wich every select belong. I used that index to associated each select to their row, and append to the name of event in order to each component identify their corresponding event:

    I use this in the controller of component who emit

    public $indexLoop;    
    public function updatedFormFamilia()
    {
        $this->dispatch('familiaSelected' . $this->indexLoop, familiaId: $this->formFamilia);
    }
    

    And this in the controller of component who listening

    public $indexLoop;
     #[On('familiaSelected{indexLoop}')]
    public function setFamilia($familiaId)
    {
        $this->familia_id = $familiaId;
    }
    

    In the root form, I assign the value to the new variable:

    @foreach ($lineasComponentes as $componente)
    <div class="col-lg-9">
    <div class="row">
        <div class="col-sm-2 mx-0 px-0"><livewire:form.familias :familia_id="$componente->familia_id" :indexLoop="$loop->index"></livewire:form.familias></div>
        <div class="col-sm-2 mx-0 px-0"><livewire:form.sub-familias :familia_id="$componente->familia_id" :sub_familia_id="$componente->sub_familia_id" :indexLoop="$loop->index"></livewire:form.sub-familias></div>
        <div class="col-sm-4 mx-0 px-0"><livewire:form.materiales  :familia_id="$componente->familia_id" :sub_familia_id="$componente->sub_familia_id" :material_id="$componente->material_id" :indexLoop="$loop->index"></livewire:form.materiales></div>
        </div>
        </div>
        @endforeach
    

    With this, each livewire component only response to events in their corresponding row.