<template> <div :class="{ expanded: isExpanded }" class="geocoder-container" > <button class="button-geocoder" @click="isExpanded = !isExpanded" > <i class="search icon" /> </button> <Multiselect v-if="isExpanded" v-model="selection" class="expanded-geocoder" :options="addresses" :options-limit="5" :allow-empty="true" track-by="label" label="label" :show-labels="false" :reset-after="true" select-label="" selected-label="" deselect-label="" :searchable="true" :placeholder="placeholder" :show-no-results="true" :loading="loading" :clear-on-select="false" :preserve-search="true" @search-change="search" @select="select" @close="close" > <template slot="option" slot-scope="props" > <div class="option__desc"> <span class="option__title">{{ props.option.label }}</span> </div> </template> <template slot="clear"> <div v-if="selection" class="multiselect__clear" @click.prevent.stop="selection = null" > <i class="close icon" /> </div> </template> <span slot="noResult"> Aucun résultat. </span> <span slot="noOptions"> Saisissez les premiers caractères ... </span> </Multiselect> <div style="display: none;"> <div id="marker" title="Marker" /> </div> </div> </template> <script> import Multiselect from 'vue-multiselect'; import { Subject } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; import axios from 'axios'; import mapService from '@/services/map-service'; const apiAdressAxios = axios.create({ baseURL: 'https://api-adresse.data.gouv.fr', withCredentials: false, }); export default { name: 'Geocoder', components: { Multiselect }, data() { return { loading: false, selection: null, text: null, selectedAddress: null, addresses: [], resultats: [], placeholder: 'Rechercher une adresse ...', isExpanded: false }; }, mounted() { this.addressTextChange = new Subject(); this.addressTextChange.pipe(debounceTime(200)).subscribe((res) => this.getAddresses(res)); }, methods: { getAddresses(query){ const limit = 5; apiAdressAxios.get(`https://api-adresse.data.gouv.fr/search/?q=${query}&limit=${limit}`) .then((retour) => { this.resultats = retour.data.features; this.addresses = retour.data.features.map(x=>x.properties); }); }, selectAddresse(event) { this.selectedAddress = event; if (this.selectedAddress !== null && this.selectedAddress.geometry) { let zoomlevel = 14; const { type } = this.selectedAddress.properties; if (type === 'housenumber') { zoomlevel = 19; } else if (type === 'street') { zoomlevel = 16; } else if (type === 'locality') { zoomlevel = 16; } // On fait le zoom mapService.zoomTo(this.selectedAddress.geometry.coordinates, zoomlevel); // On ajoute un point pour localiser la ville mapService.addOverlay(this.selectedAddress.geometry.coordinates); } }, search(text) { this.text = text; this.addressTextChange.next(this.text); }, select(e) { this.selectAddresse(this.resultats.find(x=>x.properties.label === e.label)); this.$emit('select', e); }, close() { this.$emit('close', this.selection); } } }; </script> <style scoped lang="less"> .geocoder-container { position: absolute; right: 0.5em; // each button have (more or less depends on borders) .5em space between // zoom buttons are 60px high, geolocation and full screen button is 34px high with borders top: calc(1.6em + 60px + 34px + 34px); pointer-events: auto; z-index: 1000; border: 2px solid rgba(0,0,0,.2); background-clip: padding-box; padding: 0; border-radius: 2px; display: flex; .button-geocoder { border: none; padding: 0; margin: 0; text-align: center; background-color: #fff; color: rgb(39, 39, 39); width: 28px; height: 28px; font: 700 18px Lucida Console,Monaco,monospace; border-radius: 2px; line-height: 1.15; i { margin: 0; font-size: 0.9em; } } .button-geocoder:hover { cursor: pointer; background-color: #ebebeb; } .expanded-geocoder { max-width: 400px; } } .expanded { .button-geocoder { height: 40px; color: rgb(99, 99, 99); } } #marker { width: 20px; height: 20px; border: 1px solid rgb(136, 66, 0); border-radius: 10px; background-color: rgb(201, 114, 15); opacity: 0.7; } </style>