<template lang="pug">
.address_autocomplete
Multiselect(
v-model="selectedPlace"
label="name"
track-by="code"
:placeholder="getText(this.placeholder)"
open-direction="bottom"
:options="places"
:multiple="false"
:searchable="true"
:loading="isLoading"
:internal-search="false"
:clear-on-select="false"
:close-on-select="true"
:selectLabel="$t('geocoding.autocomplete.select_hint')"
:deselectLabel="$t('geocoding.autocomplete.deselect_hint')"
:selectedLabel="$t('geocoding.autocomplete.selected_label')"
@search-change="asyncFind"
@select="onPlaceSelected"
v-bind="options"
:class="theme"
)
template(slot="noResult")
slot(name="noResult")
span {{getText(noResultText)}}
template(slot="noOptions")
slot(name="noOptions")
//By default, do not display "List is empty" if data was not yet fetch
div
template(slot="singleLabel", slot-scope="props")
slot(name="singleLabel" v-bind:item="props.option")
</template>
<script>
import Multiselect from 'vue-multiselect'
import { autocompleteProviders } from '@/services/geocoding-service.js'
import envService from '@/services/env-service.js'
/**
*
* Wrapper on top of Multiselect for Address autocompletation with OSM and custom geocoding APIs
*
* Note:
* :internal-search="false" should be set to false, otherwise, async fetching does't work.
*
* @vue-prop {String} value - Two-way-binding object (v-model) to store place details
* @vue-prop {String} providerName - API to hit (OSM, OpenCage, GoogleMaps, Simpliciti APIs, etc)
* @vue-prop {String} placeholder - Select placeholder (Uses i18n $t method if available)
* @vue-prop {String} extraOptions - https://vue-multiselect.js.org/#sub-props
* @namespace components
* @category components
* @subcategory shared/geocoding
* @module AddressAutocomplete
*/
export default {
name: 'AddressAutocomplete',
components: { Multiselect },
props: {
value: {
type: Object,
default: () => ({}),
},
/**
* osm: Hits OSM Nominatim directly
* simplicti_v3: Proxy OSM Nominatim though our servers
*/
providerName: {
type: String,
default: envService.getEnvValue(
'VUE_APP_AUTOCOMPLETE_DEFAULT_PROVIDER',
'simpliciti_v3'
),
enum: autocompleteProviders,
},
placeholder: {
type: String,
default: 'shared.address_autocomplete.placeholder',
},
noResultText: {
type: String,
default: 'shared.address_autocomplete.no_results',
},
extraOptions: {
type: Object,
default: () => ({}),
},
theme: {
type: String,
default: 'theme-default',
},
},
data() {
return {
options: {
optionsLimit: 4,
...this.extraOptions,
},
selectedPlace: null,
places: [],
isLoading: false,
}
},
computed: {
providerURL() {
return (this.providers[this.providerName] || {}).url || ''
},
},
watch: {
extraOptions() {
this.options = { ...this.options, ...(this.extraOptions || {}) }
},
value() {
this.updateFromValue()
},
/**
* Handle deselect (two ways binding)
*/
selectedPlace() {
if (!this.selectedPlace) {
this.$emit('input', {})
}
},
},
created() {
if (!autocompleteProviders.includes(this.providerName)) {
throw new Error(
`Invalid provider '${
this.providerName
}' (Expected providers: ${autocompleteProviders.join(', ')})`
)
}
},
mounted() {
this.updateFromValue()
},
methods: {
/**
* If no selection, try to forward geocoding the address given by v-model
*/
updateFromValue() {
//this.$log.debug('Address::updateFromValue::Check')
if (
this.selectedPlace === null &&
Object.keys(this.value || {}).length > 0
) {
//this.$log.debug('Address::updateFromValue::Find')
this.asyncFind(this.value.formatted).then((places) => {
//this.$log.debug('Address::updateFromValue::Places',places.length)
if (places.length > 0) {
this.selectedPlace = places[0]
this.onPlaceSelected(this.selectedPlace)
}
})
}
},
getText(text) {
return this.$t && this.$te(text) ? this.$t(text) : text
},
limitText(count) {
return `and ${count} other places`
},
asyncFind(query) {
return new Promise((resolve, reject) => {
this.isLoading = true
this.$geocoding
.autocompleteFetchPlaces(query, {
provider: this.providerName,
})
.then((response) => {
this.places = response
this.isLoading = false
resolve(this.places)
})
})
},
async onPlaceSelected(selectedPlace) {
let placeDetails = await this.$geocoding.autocompleteFetchPlaceDetails(
selectedPlace.code,
{
provider: this.providerName,
formatted: selectedPlace.name,
}
)
this.$emit('input', placeDetails)
this.$emit('select', placeDetails)
},
},
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
<style>
/** Customize the styles by overriding multiselect library styles either here or in a wrapper component
https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-multiselect.min.css
*/
.address_autocomplete .multiselect.theme-simpliciti > .multiselect__tags {
border: 0px;
}
.address_autocomplete .multiselect.theme-simpliciti > .multiselect__tags {
border: 0px;
border-bottom: 1px solid var(--color-slate-gray);
border-radius: 0px;
}
.address_autocomplete .multiselect.theme-simpliciti > .multiselect__tags:focus,
.address_autocomplete
.multiselect.theme-simpliciti
> .multiselect__tags:active {
outline: 0px;
box-shadow: none;
border-bottom: 1px solid var(--color-main);
}
</style>
Source