I'm trying to retain the selected values on the dependent dropdowns after a failed form submission. I made it work for Province dropdown but when I tried to Municipality dropdown and District dropdown, it doesn't work. The only dropdown that retains its value when I refresh the page or submit the form even if there are empty inputs is the Province dropdown. Here are my codes:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
@include('shared.head')
<body x-data="associationAddressDropdown()" x-init="initialize()">
@include('components.navigation-header')
@include('layouts.navigation')
<div class="container mt-2">
@if ($associationProfile)
<a href="{{ route('fca.view') }}" class="edit-link">View</a>
<hr>
@endif
<form action="{{ route('fca.update') }}" method="post" enctype="multipart/form-data">
@csrf
@include('fca-profile-edit.association-profile')
<hr>
<input type="submit" value="{{ isset($fca) ? 'Update' : 'Submit' }}"
class="btn btn-success text-white col-md-4 rounded-pill mb-2 mx-auto d-block">
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous">
</script>
<script src="{{ asset('js/fca-profile.js') }}"></script>
</body>
</html>
<div class="row">
<div class="col-md-4 mb-2">
<label for="associationProvince" class="form-label label-style">Province</label>
<select x-model="province" x-on:change="onProvinceChange" id="associationProvince" name="associationProvince"
class="form-select">
<option value="">Choose Province</option>
@foreach ($provinceValues as $id => $provinceValue)
<option value="{{ $provinceValue->id }}">{{ $provinceValue->province_name }}</option>
@endforeach
</select>
@error('associationProvince')
<span class="fs-6 text-danger">Please choose a province.</span>
@enderror
</div>
<div class="col-md-4 mb-2">
<label for="associationMunicipality" class="form-label label-style">Municipality</label>
<select x-model="municipality" x-on:change="onMunicipalityChange" id="associationMunicipality"
name="associationMunicipality" class="form-select">
<option value="">Choose Municipality</option>
<template x-for="municipality in municipalities" :key="municipality.id">
<option :value="municipality.id" x-text="municipality.municipality_name"></option>
</template>
</select>
@error('associationMunicipality')
<span class="fs-6 text-danger">Please choose a municipality.</span>
@enderror
</div>
<div class="col-md-4 mb-2">
<label for="associationDistrict" class="form-label label-style">District</label>
<select x-model="district" id="associationDistrict" name="associationDistrict" class="form-select">
<option value="">Choose District</option>
<template x-for="district in districts" :key="district.id">
<option :value="district.id" x-text="district.district_name"></option>
</template>
</select>
@error('associationDistrict')
<span class="fs-6 text-danger">Please choose a district.</span>
@enderror
</div>
</div>
function associationAddressDropdown() {
return {
province: Alpine.$persist(''),
municipality: Alpine.$persist(''),
district: Alpine.$persist(''),
municipality: '',
district: '',
municipalities: [],
districts: [],
initialize() {
if (this.province) {
this.fetchMunicipalities(this.province);
}
},
onProvinceChange(event) {
this.province = event.target.value;
this.fetchMunicipalities(this.province);
},
onMunicipalityChange(event) {
this.municipality = event.target.value;
this.fetchDistricts(this.municipality);
},
fetchMunicipalities(province) {
axios.get(`/provinces/${province}`).then(res => {
this.municipalities = res.data;
this.districts = [];
}).catch(error => {
console.error('Error fetching municipalities:', error);
});
},
fetchDistricts(municipality) {
axios.get(`/municipalities/${municipality}`).then(res => {
this.districts = res.data;
}).catch(error => {
console.error('Error fetching districts:', error);
});
}
};
}
I tried doing this municipality: Alpine.$persist(''), district: Alpine.$persist(''),
Just like in province because it was working for it province: Alpine.$persist(''),
When the page is rendered, if there is a saved municipality the districts must be loaded as done for the municipalities.
By selecting a province you must reset the municipality and district, by selecting a municipality you must reset the municipality
The most important: in the x-for the options are rendered testing the saved value for the select and setting the selected attribute
The HTML
.....
<body x-data="associationAddressDropdown()">
.....
<div ..... >
.....
<select x-model="municipality"
x-on:change="onMunicipalityChange"
id="associationMunicipality"
name="associationMunicipality"
class="form-select"
>
<option value="">Choose Municipality</option>
<template x-for="municipality_row in municipalities" :key="municipality_row.id">
<option :value="municipality_row.id"
:selected="municipality_row.id == municipality"
x-text="municipality_row.municipality_name"
>
</option>
</template>
</select>
.....
</div>
<div .....>
.....
<select x-model="district"
id="associationDistrict"
name="associationDistrict"
class="form-select"
>
<option value="">Choose District</option>
<template x-for="district_row in districts" :key="district_row.id">
<option :value="district_row.id"
:selected="district_row.id == district"
x-text="district_row.district_name"
>
</option>
</template>
</select>
.....
</div>
.....
</body>
The Javascript:
function associationAddressDropdown() {
return {
province: Alpine.$persist(null).using(sessionStorage),
municipality: Alpine.$persist(null).using(sessionStorage),
district: Alpine.$persist(null).using(sessionStorage),
municipalities: [],
districts: [],
init() {
if (this.province) {
this.fetchMunicipalities(this.province);
}
if (this.municipality) {
this.fetchDistricts(this.municipality);
}
},
onProvinceChange() {
this.district = null;
this.districts = [];
this.municipality = null;
this.fetchMunicipalities(this.province);
},
onMunicipalityChange() {
this.district = null;
this.fetchDistricts(this.municipality);
},
fetchMunicipalities(province) {
axios.get(`/provinces/${province}`).then(res => {
this.municipalities = res.data;
// this.districts = []; ~~~> moved
}).catch(error => {
console.error('Error fetching municipalities:', error);
});
},
.....
};
}
I also applied the following changes:
I have removed the x-init from the <body> and renamed the method initialize() as init(), so it is executed automatically when AlpineJS initializes the object
I used sessionStorage (.using(sessionStorage)
) for persist to allow fresh values when the browser is closed and then reopened
In the x-for I renamed the current row whith the suffix _row (municipality_row and district_row) to avoid confusion with the properties with the same name
in the event handlers - onProvinceChange() and onMunicipalityChange() - I removed the property assignments because this is already done by AlpineJs thanks to x-model and manual assignment can also cause conflicts
I moved the reset of the districts array from the fetchMunicipalities() method to the onProvinceChange() for a better application of the Single Responsibility Principle