92 lines
2.8 KiB
JavaScript
92 lines
2.8 KiB
JavaScript
|
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 () {
|
||
|
window.addEventListener('cart:country:update', async 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 = await this.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'))
|
||
|
}
|
||
|
})
|
||
|
|
||
|
// When the input changes we update the actual value and also the
|
||
|
// state list via an Event
|
||
|
this.nameTarget.addEventListener('change', 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
|
||
|
}
|
||
|
}
|