'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 1:[1]

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jacob Barrow