Search code examples
javascriptlaraveluser-interfacelaravel-livewire

Bind JSON attribute with livewire


I have a Test model, with the JSON attribute inputs, which is a flat array of strings (e.g. ['foo', 'bar', 'baz']).

I'm trying to bind this field up to Livewire so that I can have a component that allows the user to add, modify and delete elements from this array. I'm able to get the following working, where each input is automatically populated using the array:

enter image description here

To create this, I use the following code (

<div class="test">
    ...
    @php $index = 0; @endphp
    @foreach ($this->test->inputs as $input)
        @php $index ++; @endphp
        <div class="input-wrap">
            <label>{{ $index }}</label>
            <input type="text" placeholder="input..." value="{{ $input }}">
            <i class="fas fa-times"></i>
        </div>
    @endforeach
    ...
</div>

However, I'm struggling to work out the best way of binding these fields using livewire so they are synced with the backend. I thought about binding a hidden input field with the raw json in, with some frontend JS to listen to changes and keep updated, but when it comes to adding a new element this will mean duplicating the .input-wrap div on the front- and backend, which feels like a code-smell and would be trickier to maintain.

Is there a way of achieving this without relying heavily on front-end JS for the rendering logic?


Solution

  • While this suggested similar question was helpful in getting me to the answer, it didn't quite tackle what I was after. To solve this, I added the following methods to the component's class:

    public function addInput()
    {
        $this->test->inputs->append('');
        $this->test->save();
    }
    
    public function removeInput($index)
    {
        $this->test->inputs->offsetUnset($index);
        $this->test->save();
    }
    

    I also added 'test.inputs.*' => 'string' to its $rules, and to allow direct modification of the array I casted the inputs to an array object using 'inputs' => AsArrayObject::class in the model definition.

    Then, on the frontend:

        @foreach ($this->test->inputs as $input)
    
            <div class="input-wrap">
                <label>{{ $index + 1 }}</label>
                <input wire:model="test.inputs.{{ $index }}">
                <i wire:click="removeInput({{ $index }})" class="fas fa-times"></i>
            </div>
    
            @php $index ++; @endphp
    
        @endforeach