I would like to try implement alpine.js framework into my existing native javascript function. I have an array of addresses (which contain street, postcode, etc) and it has an added function so that I can add multiple addresses for one customer. The code below is when I am using native javascript which works as desired:
<div>
<form id="customerForm" method="post" action="{{ route('customers.store') }}">
@csrf
<div class="mb-4">
...
</div>
<div class="mb-4">
...
</div>
<div id="addressFields">
<label>Address</label>
<div class="address-fields">
<div class="address-field mb-2">
<input type="text" name="addresses[0][street1]" placeholder="Street 1" />
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[0][street2]" placeholder="Street 2" />
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[0][postcode]" placeholder="Postcode" />
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[0][city]" placeholder="City" />
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[0][state]" placeholder="State" />
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[0][country]" placeholder="Country" />
</div>
</div>
</div>
<button type="button" id="addAddress">Add Address</button><br><br>
<button type="submit">Create Customer</button>
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let addressIndex = 1;
// Function to add address fields dynamically
function addAddressFields() {
const addressFields = document.getElementById('addressFields');
const addressContainer = document.createElement('div');
addressContainer.classList.add('address-fields');
addressContainer.innerHTML = `
<br/>
<div>
<label>Address ${addressIndex}</label>
<button class ="removeAddress" id="removeAddress">Remove</button>
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[${addressIndex}][street1]" placeholder="Street 1"/>
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[${addressIndex}][street2]" placeholder="Street 2"/>
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[${addressIndex}][postcode]" placeholder="Postcode"/>
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[${addressIndex}][city]" placeholder="City"/>
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[${addressIndex}][state]" placeholder="State"/>
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[${addressIndex}][country]" placeholder="Country"/>
</div>
`;
addressFields.appendChild(addressContainer);
addressIndex++;
}
document.getElementById('addAddress').addEventListener('click', function() {
addAddressFields();
});
document.addEventListener('click', function(event) {
if (event.target.classList.contains('removeAddress')) {
event.preventDefault();
event.target.closest('.address-fields').remove();
addressIndex--;
}
});
});
</script>
Now, I already tried to use alpine.js based on my original code. There are some issue with it. Based on what I did, when I create customer, the addresses details only shows the latest addresses input. Meaning, for example, I fill up the addresses, then I want to add another one. So there are two addresses. When I create customer, it only display one addresses (which is the latest).
<div x-data="addresses: []">
<form id="customerForm" method="post" action="{{ route('customers.store') }}">
<div>
...
</div>
<div>
...
</div>
<template x-for="(address, index) in addresses" :key="index">
<div>
<label x-text="'Address ' + (index + 1)"></label>
<div>
<input type="text" name="addresses[${index}][street1]" placeholder="Street 1"
x-model="address.street1" />
</div>
<div>
<input type="text" name="addresses[${index}][street2]" placeholder="Street 2"
x-model="address.street2" />
</div>
<div>
<input type="text" name="addresses[${index}][postcode]" placeholder="Postcode"
x-model="address.postcode" />
</div>
<div>
<input type="text" name="addresses[${index}][city]" placeholder="City" x-model="address.city" />
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[${index}][state]" placeholder="State"
x-model="address.state" />
</div>
<div class="address-field mb-2">
<input type="text" name="addresses[${index}][country]" placeholder="Country"
x-model="address.country" />
</div>
</div>
</template>
<button type="button" @click="addresses.push({})">Add Address Alpinejs</button>
</form>
</div>
I need help on how I can improve and fix my code. Thank you very much for your help and much appreciated.
You want to directly bind to the parent. You should look at the data from the x-for
as read only to make it make more sense.
Example:
<div x-data="{ data: [{ firstName: '', lastName: '', id : Date.now()}] }" style="margin-top: 20px">
<button @click="data.push({ firstName: '', lastName: '', id: Date.now()})" style="margin-bottom: 10px">Add Row</button>
<table>
<template x-for="(obj, index) in data" :key="obj.id">
<tr>
<td><input type="text" x-model="data[index]['firstName']"></td>
<td><input type="text" x-model="data[index]['lastName']"></td>
<td><button @click="data.splice(index, 1)">Remove Item</button></td>
</tr>
</template>
</table>
</div>
Code taken from this CodePen.