Last active
May 7, 2022 20:48
-
-
Save dschreij/06ac09c3ac9be4be4d25849f08eb3121 to your computer and use it in GitHub Desktop.
Location autocomplete field using the Google Places API and Vuetify's v-combobox
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<v-combobox | |
v-bind="$attrs" | |
item-text="description" | |
item-value="description" | |
:search-input.sync="query" | |
:loading="loading" | |
:items="results" | |
v-on="$listeners" | |
@input="handleInput" | |
> | |
<template v-slot:item="{ item }"> | |
<v-list-item-content v-if="item"> | |
<v-list-item-title v-html="item.structured_formatting.main_text" /> | |
<v-list-item-subtitle v-html="item.structured_formatting.secondary_text" /> | |
</v-list-item-content> | |
</template> | |
</v-combobox> | |
</template> | |
<script> | |
import { debounce, isEmpty, isObject } from 'lodash' | |
export default { | |
props: { | |
latitude: { | |
type: Number, | |
default: 52.08966 | |
}, | |
longitude: { | |
type: Number, | |
default: 5.11436 | |
}, | |
debounceMsec: { | |
type: Number, | |
default: 200 | |
}, | |
placesFields: { | |
type: Array, | |
default: () => ([ | |
// 'address_components', | |
'formatted_address' | |
]) | |
}, | |
placesTypes: { | |
type: Array, | |
default: () => ([ | |
'address' | |
]) | |
}, | |
placesRadius: { | |
type: Number, | |
default: 100000 | |
} | |
}, | |
data () { | |
return { | |
platform: null, | |
searchService: null, | |
placesService: null, | |
query: '', | |
results: [], | |
loading: false, | |
googleSessionToken: '' | |
} | |
}, | |
watch: { | |
query (val) { | |
this.search(val) | |
} | |
}, | |
created () { | |
this.search = debounce(this.search, this.debounceMsec) | |
}, | |
mounted () { | |
this.initService() | |
}, | |
methods: { | |
initService () { | |
this.searchService = new window.google.maps.places.AutocompleteService() | |
this.placesService = new window.google.maps.places.PlacesService( | |
new window.google.maps.Map(document.createElement('div')) | |
) | |
this.generateNewSessionToken() | |
}, | |
async search (val) { | |
if (isEmpty(val) || val.length < 3) { | |
this.results = [] | |
return | |
} | |
this.loading = true | |
try { | |
this.results = await this.searchPlace(val) | |
} catch (e) { | |
// eslint-disable-next-line no-console | |
console.warn('Could not retrieve location suggestions', e) | |
} | |
this.loading = false | |
}, | |
searchPlace (val) { | |
return new Promise((resolve, reject) => { | |
this.searchService.getPlacePredictions({ | |
input: val, | |
location: new window.google.maps.LatLng(this.latitude, this.longitude), | |
radius: this.placesRadius, | |
types: this.placesTypes, | |
sessionToken: this.googleSessionToken | |
}, | |
(predictions, status) => { | |
if (status === window.google.maps.places.PlacesServiceStatus.OK) { | |
return resolve(predictions) | |
} | |
if (status === window.google.maps.places.PlacesServiceStatus.ZERO_RESULTS) { | |
return resolve([]) | |
} | |
return reject(status) | |
}) | |
}) | |
}, | |
generateNewSessionToken () { | |
this.googleSessionToken = new window.google.maps.places.AutocompleteSessionToken() | |
}, | |
async handleInput (choice) { | |
// If choice is a string, simply return that | |
if (!isObject(choice)) { | |
this.$emit('input', choice) | |
return | |
} | |
// Already set the text to the current information as provided by the choice | |
this.$emit('input', choice.description) | |
this.loading = true | |
try { | |
// Attempt to get full address including postal code | |
const placeDetails = await this.getPlaceDetails(choice) | |
this.$emit('input', placeDetails.formatted_address) | |
} catch (e) { | |
// eslint-disable-next-line no-console | |
console.error(e) | |
} | |
this.loading = false | |
this.generateNewSessionToken() | |
}, | |
getPlaceDetails (choice) { | |
return new Promise((resolve, reject) => { | |
this.placesService.getDetails({ | |
placeId: choice.place_id, | |
sessionToken: this.googleSessionToken, | |
fields: this.placesFields | |
}, | |
(result, status) => { | |
if (status === window.google.maps.places.PlacesServiceStatus.OK) { | |
return resolve(result) | |
} | |
return reject(result) | |
}) | |
}) | |
} | |
} | |
} | |
</script> |
Tip
is necessary use prop no-filter in v-combobox. Otherwise the search will bug and not show some results, the filtering is already being done by the google api
Thanks for the tip @xyanlucasx
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a first attempt to create a Vuetify-native Google autocompleting places search, using Google's Places AutocompleteService. Make sure you include the google maps api javascript library in the head of your app's html (instructions). The default values for the props are the sane defaults for my current situation. They might not be for yours, so change where appropriate.