I'm trying to append the value inside the alpine JS variable to Laravel blade component's attribute
I'm using laravel 10, Alpine V3 and Livewire V3 Here's my code :
<template x-for="(field, index) in fields" :key="index">
<tr>
<td x-text="index+1"></td>
<td>
<x-forms.input wire:model="journals.index.account" type="text"></x-forms.input>
</td>
<td>
<x-forms.input wire:model="journals.index.amount" type="text"></x-forms.input>
</td>
<td><button type="button" class="btn btn-danger btn-small" @click="removeField(index)">×</button></td>
</tr>
</template>
The x-forms.input component :
<div>
<input
type="{{ $type }}"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500">
</div>
And here's my alpine code :
<script>
function handler () {
return {
fields: [],
addNewField() {
this.fields.push({
txt1: '',
txt2: ''
});
},
removeField(index) {
this.fields.splice(index, 1);
}
}
}
</script>
My Livewire class :
class CreateJournal extends Component
{
public $journals = [];
public function save()
{
foreach ($this->journals as $journal) {
\App\Models\Payable::create($journal);
}
}
}
The intended result is to append the index variable of alpine to the blade wire:model attribute like so :
<x-forms.input model="journals.**index**.account" type="text"></x-forms.input>
How can i achieve this ?
I suppose that wire:model is evaluated before Alpine objects are instantiated, so it can't interact with Alpine's variables.
We could use @entangle to use an Alpine variable that references a Livewire variable, but since version 3 of Livewire it is possible to use the magic property $wire to directly reference the Livewire component, so we can use this feature.
From your example the relationship between the Livewire journals property and the Alpine fields[] property is not very clear, I will assume that they refer to the same data and therefore I will do without fields[]. I also assumed that your addNewField() and removeField() will handle rows, not fields (this was a bit cryptic)
The Livewire class:
<?php
namespace App\Livewire;
use Livewire\Component;
class TheClass extends Component
{
// these are example data
public $journals = [
['account' => 'acc001', 'amount' => 10] ,
['account' => 'acc002', 'amount' => 20]
];
}
The view:
<div x-data="handler">
<table>
<template x-for="(row, index) in $wire.journals" :key="row.rowKey">
<tr>
<td x-text="index + 1"></td>
<td>
<x-forms.input x-model="row.account" type="text" />
</td>
<td>
<x-forms.input x-model="row.amount" type="text" />
</td>
<td>
<button type="button"
class="btn btn-danger btn-small"
@click="removeRow(index)"
>
×
</button>
</td>
</tr>
</template>
</table>
<br>
<button @click="addNewRow()">
[ADD ROW]
</button>
<button @click="$wire.$refresh">
[REFRESH]
</button>
<br>
<br>
// the foreach is for debugging
@foreach($journals as $row)
@php($rowId = $row['rowKey'] ?? ' ')
<div>
{{ "($rowId) - ({$row['account']}) - ({$row['amount']})" }}
</div>
@endforeach
<script>
function handler () {
return {
init() {
let rowKey = Date.now();
this.$wire.journals.forEach (el => {typeof el.rowKey == "undefined" && (el.rowKey = rowKey++)});
},
addNewRow() {
this.$wire.journals.push ({
account: '',
amount: '',
rowKey: Date.now()
});
},
removeRow(index) {
this.$wire.journals.splice (index, 1);
}
}
}
</script>
</div>
In the x-for loop I referenced the journals property via $wire.
I also added to each row a "unique" key (in the init() and addNewRow() methods) to use as :key because when a row is deleted using splice(), the indexes are compacted and this can confuse x-for. Consider whether to modify this functionality and for example manage the key in the backend (you can use an id if it already exists).
I also inserted a @foreach for debugging purposes only: by clicking on the REFRESH button the data is sent to the backend and the component is updated accordingly, showing the changes.
Here a working version of the input Blade component:
<div>
<input {{ $attributes->merge([
'class' => 'bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg ' .
'focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 ' .
'dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 ' .
'dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500'
])
}}
>
</div>