import { CartBaseController } from "./cart_base_controller"; /* * Populates a state field where users can type to filter and select * from a predefined list. It waits for an `cart:country:update` event * to become populated. */ export default class extends CartBaseController { // All are required! static targets = ["id", "list", "name"]; connect() { this.cart_country_update_event = this._cart_country_update_event.bind(this); this.change_event = this._change_event.bind(this); window.addEventListener( "cart:country:update", this.cart_country_update_event ); // When the input changes we update the actual value and also the // state list via an Event this.nameTarget.addEventListener("change", this.change_event); } disconnect() { window.removeEventListener( "cart:country:update", this.cart_country_update_event ); this.nameTarget.removeEventListener("change", this.change_event); } async _cart_country_update_event(event) { if (this.data.get("group") !== event.detail.group) return; this.idTarget.value = ""; this.nameTarget.value = ""; this.listTarget.innerHTML = ""; const statesRequired = event.detail.data.statesRequired == "true"; this.nameTarget.disabled = !statesRequired; this.nameTarget.required = statesRequired; if (!statesRequired) return; const states = await this.states(event.detail.iso); const site = window.site; states.forEach((state) => { let option = document.createElement("option"); option.value = state.attributes.name; option.dataset.id = state.id; this.listTarget.appendChild(option); }); this.nameTarget.pattern = states.map((x) => x.attributes.name).join("|"); this.nameTarget.addEventListener("input", (event) => this.nameTarget.setCustomValidity("") ); this.nameTarget.addEventListener("invalid", (event) => this.nameTarget.setCustomValidity(site.i18n.states.validation) ); if (event.detail.selectedState) { this.nameTarget.value = event.detail.selectedState; this.nameTarget.dispatchEvent(new Event("change")); } } _change_event(event) { const options = Array.from(this.listTarget.options); const option = options.find((x) => x.value == this.nameTarget.value); // TODO: If no option is found, mark the field as invalid if (!option) return; this.idTarget.value = option.dataset.id; this.idTarget.dispatchEvent(new Event("change")); } /* * Fetch the state list from storage or from API using a country ISO * code */ async states(countryIso) { const stateId = `states:${countryIso}`; let states = JSON.parse(this.storageTemp.getItem(stateId)); if (states) return states; // There's no state query, but we can fetch the country and include // its states. const response = await this.spree.countries.show(countryIso, { include: "states", }); // TODO: Show error message if (response.isFail()) { this.handleFailure(response); return {}; } states = response.success().included; // Order alphabetically by name states.sort((x, y) => x.attributes.name > y.attributes.name); this.storageTemp.setItem(stateId, JSON.stringify(states)); return states; } }