laravelaxiosalpine.js

How to apply persist plugin of alpinejs to dependent dropdown


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:

fca-profile-edit.blade.php

<!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>

association-profile.blade.php

<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>

fca-profile.js

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(''),


Solution

  • 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: