Vue Autosuggest Autocomplete Select From Multiple Source
August 24, 2018Data Source from REST Api and Google Places
I want to build an autocomplete select/dropdown using vue-autosuggest from 2 sources: Google Places and REST API.
vue-autosuggest
is not the most popular ones, but it is actively developed for the last 12 months. I am using version 1.4.3
(18 Jul 2018).
Install
Install vue-autosuggest (for autocomplete dropdown) and axios (for calling REST Api)
npm install --save vue-autosuggest
npm install --save axios
PlacePicker Component
I created a vue component: PlacePicker.vue
.
vue-autosuggest
has 3 sources: REST Api, Google Places Library - PlacesService.findPlaceFromQuery and AutocompleteService.getPlacePredictions- Suggestions from
AutocompleteService.getPlacePredictions
is used to trigger a search when selected - Google Map is loaded below
vue-autosuggest
, and marker is shown (and panTo) when a place is selected (from REST Api or Google Places Library). - Refer to Google Maps/Places Get API Key.
- Replace
travelopy
with your own REST Api. - Refer to code for loadScript
NOTE: Take note of Places API Policies
<template>
<form>
<vue-autosuggest
:suggestions="suggestions"
:inputProps="inputProps"
:sectionConfigs="sectionConfigs"
:renderSuggestion="renderSuggestion"
:getSuggestionValue="getSuggestionValue"
ref="autocomplete" />
<div id="map" style="height: 300px;"></div>
</form>
</template>
<script>
import Vue from 'vue'
import axios from 'axios'
let googleMapPromise = null
let map = null
let placeService = null
let autocompleteService = null
let timeout = null
let marker = null
export default {
name: 'PlacePicker',
props: ['query'],
data() {
GOOGLE_API_KEY: '<PUT_YOUR_KEY>',
selected: '',
suggestions: [],
sectionConfigs: {
googleSuggest: {
limit: 6,
label: 'Suggestions',
onSelected: selected => {
this.fetchResults(selected.item, "")
// https://github.com/Educents/vue-autosuggest/issues/52
this.$refs.autocomplete.listeners.click()
}
},
travelopy: {
limit: 6,
label: 'Travelopy',
onSelected: selected => {
this.selected = selected
}
},
google: {
limit: 6,
label: 'Google',
onSelected: selected => {
this.selected = selected
}
}
},
inputProps: {
id: "autosuggest__input",
onInputChange: this.fetchResults,
placeholder: 'Search places',
initialValue: this.query,
}
},
watch: {
selected(value) {
if (value) {
const item = value.item
if (item.location) {
if (!marker) {
marker = new google.maps.Marker({ position: item.location, map: map, title: item.name })
}
else {
marker.setPosition(item.location)
marker.setTitle(item.name)
}
map.panTo(item.location)
}
else {
this.$toasted.show('Selection had no location')
}
}
},
fetchResults(val) {
this.selected = null
let delay = 300
const len = val.length
if (len == 0) {
this.suggestions = []
return
}
else if (len == 1) {
delay = 1000
}
else if (len <= 3) {
delay = 700
}
else if (len <= 5) {
delay = 500
}
clearTimeout(timeout)
timeout = setTimeout(() => {
// hack to maintain position
this.suggestions = [
{name: 'google', data: null},
{name: 'travelopy', data: null},
{name: 'googleSuggest', data: null},
]
axios.get(`/your_autocomplete_rest_api?q=${val}`)
.then(response => {
const results = response.data
if (results.length === 0) return
const newResults = results.map(item => {
// TODO: perform your own mapping here
let name = item.name
const alt_names = []
if (item.native_name)
name += ` (${item.native_name})`
let location = null
if (item.geo) {
const latlng = item.geo.split(', ')
location = new google.maps.LatLng(latlng[0], latlng[1])
}
return {
id: item.id,
type: 'place',
name: name,
address: item.address,
location: location,
place_type: item._item_type,
}
})
const suggestion = { name: 'travelopy', data: newResults }
const index = this.suggestions.findIndex(x => x.name === 'travelopy')
if (index === -1)
this.suggestions.push(suggestion)
else
Vue.set(this.suggestions, index, suggestion)
})
.catch((error) => {
// const message = lua.util.getErrorMessage(error)
// this.$toasted.error(message)
})
const request = {
query: val,
fields: ['place_id', 'name', 'formatted_address', 'icon', 'geometry'],
// locationBias: null,
}
placeService.findPlaceFromQuery(request, (results, status) => {
// placeService.textSearch(request, (results, status) => {
if (status == google.maps.places.PlacesServiceStatus.OK) {
// raw is used by new_api.py for new place creation
const newResults = results.map(item => ({ id: item.place_id, type: 'google_place', name: item.name, address: item.formatted_address, location: item.geometry.location, place_icon: item.icon, raw: item }))
const suggestion = { name: 'google', data: newResults }
const index = this.suggestions.findIndex(x => x.name === 'google')
if (index === -1)
this.suggestions.push(suggestion)
else
Vue.set(this.suggestions, index, suggestion)
}
})
const autocompleteRequest = {
input: val,
}
autocompleteService.getPlacePredictions(autocompleteRequest, (results, status) => {
if (status == google.maps.places.PlacesServiceStatus.OK) {
const newResults = results.map(item => (item.description))
const suggestion = { name: 'googleSuggest', data: newResults }
const index = this.suggestions.findIndex(x => x.name === 'googleSuggest')
if (index === -1)
this.suggestions.push(suggestion)
else
Vue.set(this.suggestions, index, suggestion)
}
})
}, delay)
},
renderSuggestion(suggestion) {
const item = suggestion.item
if (suggestion.name === 'travelopy') {
return (
<div>
<div class="float-right text-secondary">{item.place_type}</div>
<div>{item.name}</div>
<small class="text-secondary">{item.address}</small>
</div>
)
} else if (suggestion.name === 'google') {
return (
<div>
<img class="float-right" style="max-width: 18px;" src={item.place_icon} />
<div>{item.name}</div>
<small class="text-secondary">{item.address}</small>
</div>
)
} else {
// assume this is string
return item
}
},
getSuggestionValue(item) {
if (item.name == "travelopy" || item.name == "google") {
return item.item.name
} else {
return item.item
}
},
},
created() {
googleMapPromise = loadScript('https://maps.googleapis.com/maps/api/js?key=' + this.GOOGLE_API_KEY + '&libraries=places')
},
mounted() {
googleMapPromise.then(() => {
var mapCenter = new google.maps.LatLng(-33.8617374,151.2021291)
map = new google.maps.Map(document.getElementById('map'), {
center: mapCenter,
zoom: 15
})
marker = null
placeService = new google.maps.places.PlacesService(map)
autocompleteService = new google.maps.places.AutocompleteService()
})
}
}
</script>
References:
- algo-trading
- algolia
- analytics
- android
- android-ktx
- android-permission
- android-studio
- apps-script
- bash
- binance
- bootstrap
- bootstrapvue
- chartjs
- chrome
- cloud-functions
- coding-interview
- contentresolver
- coroutines
- crashlytics
- crypto
- css
- dagger2
- datastore
- datetime
- docker
- eslint
- firebase
- firebase-auth
- firebase-hosting
- firestore
- firestore-security-rules
- flask
- fontawesome
- fresco
- git
- github
- glide
- godot
- google-app-engine
- google-cloud-storage
- google-colab
- google-drive
- google-maps
- google-places
- google-play
- google-sheets
- gradle
- html
- hugo
- inkscape
- java
- java-time
- javascript
- jetpack-compose
- jetson-nano
- kotlin
- kotlin-serialization
- layout
- lets-encrypt
- lifecycle
- linux
- logging
- lubuntu
- markdown
- mate
- material-design
- matplotlib
- md5
- mongodb
- moshi
- mplfinance
- mysql
- navigation
- nginx
- nodejs
- npm
- nuxtjs
- nvm
- pandas
- payment
- pip
- pwa
- pyenv
- python
- recylerview
- regex
- room
- rxjava
- scoped-storage
- selenium
- social-media
- ssh
- ssl
- static-site-generator
- static-website-hosting
- sublime-text
- ubuntu
- unit-test
- uwsgi
- viewmodel
- viewpager2
- virtualbox
- vue-chartjs
- vue-cli
- vue-router
- vuejs
- vuelidate
- vuepress
- web-development
- web-hosting
- webpack
- windows
- workmanager
- wsl
- yarn