Address.vue 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <template>
  2. <div>
  3. <v-autocomplete
  4. v-model="model"
  5. :loading="loading"
  6. :items="items"
  7. :search-input.sync="search"
  8. hide-no-data
  9. hide-details
  10. return-object
  11. auto-select-first
  12. clearable
  13. no-filter
  14. :label="$t('where') + ' ?'"
  15. outlined
  16. append-icon="mdi-crosshairs-gps"
  17. @change="$emit('change', $event ? $event.value : '')"
  18. @click:append="geolocalizeUser"
  19. />
  20. <v-snackbar :value="errorMsg !== ''">
  21. {{ errorMsg }}
  22. <template #action="{ attrs }">
  23. <v-btn text v-bind="attrs" @click="errorMsg=''">
  24. {{ $t('close') }}
  25. </v-btn>
  26. </template>
  27. </v-snackbar>
  28. </div>
  29. </template>
  30. <script lang="ts">
  31. import Vue from 'vue'
  32. /**
  33. * Address properties as returned by the https://geo.api.gouv.fr/adresse API
  34. */
  35. interface AddressProperties {
  36. label: string,
  37. score: number,
  38. housenumber: string,
  39. id: string,
  40. name: string,
  41. postcode: string,
  42. citycode: string,
  43. x: number,
  44. y: number,
  45. city: string,
  46. district: string,
  47. context: string,
  48. type: 'housenumber' | 'street' | 'locality' | 'municipality'
  49. importance: number,
  50. street: string
  51. }
  52. /**
  53. * Localized addresses as returned by the https://geo.api.gouv.fr/adresse API
  54. */
  55. interface Feature {
  56. type: 'Feature',
  57. geometry: { type: 'Point', coordinates: [ number, number ]},
  58. properties: AddressProperties
  59. }
  60. export default Vue.extend({
  61. props: {
  62. value: {
  63. type: String,
  64. required: false,
  65. default: ''
  66. },
  67. type: {
  68. type: String,
  69. required: false,
  70. default: 'housenumber',
  71. validator (value: string) {
  72. return ['housenumber', 'street', 'locality', 'municipality'].includes(value)
  73. }
  74. },
  75. limit: {
  76. type: Number,
  77. required: false,
  78. default: 10
  79. },
  80. autocomplete: {
  81. type: Boolean,
  82. required: false,
  83. default: true
  84. }
  85. },
  86. data () {
  87. return {
  88. model: null as Address | null,
  89. search: null,
  90. features: [] as Array<Feature>,
  91. loading: false,
  92. errorMsg: ''
  93. }
  94. },
  95. computed: {
  96. items (): Array<Address> {
  97. return this.features.map((f: Feature) => {
  98. return {
  99. text: f.properties.name + ' (' + f.properties.postcode + ')',
  100. value: { longitude: f.geometry.coordinates[0] as number, latitude: f.geometry.coordinates[1] as number },
  101. disabled: false
  102. }
  103. })
  104. }
  105. },
  106. watch: {
  107. search (val: string) {
  108. if (!val) {
  109. this.features = []
  110. return
  111. }
  112. this.loading = true
  113. // Lazily load input items
  114. let apiUrl = 'https://api-adresse.data.gouv.fr/search/' +
  115. `?type=${this.type}` +
  116. `&autocomplete=${this.autocomplete ? 1 : 0}` +
  117. `&q=${val.replace(/[\s&?]/gi, '+')}`
  118. if (this.limit > 0) {
  119. apiUrl += `&limit=${this.limit}`
  120. }
  121. fetch(encodeURI(apiUrl))
  122. .then(res => res.json())
  123. .then(({ features }) => {
  124. this.features = features
  125. })
  126. .catch((err) => {
  127. // eslint-disable-next-line no-console
  128. console.error(err)
  129. })
  130. .finally(() => {
  131. this.loading = false
  132. })
  133. }
  134. },
  135. methods: {
  136. clear () {
  137. this.model = null
  138. this.search = null
  139. this.features = []
  140. this.loading = false
  141. this.errorMsg = ''
  142. },
  143. geolocalizeUser () {
  144. if (navigator.geolocation) {
  145. navigator.geolocation.getCurrentPosition(
  146. (position: { coords: { longitude: number, latitude: number }}) => {
  147. this.model = {
  148. text: this.$t('my_position') as string,
  149. value: { longitude: position.coords.longitude, latitude: position.coords.latitude }
  150. }
  151. },
  152. () => {
  153. this.errorMsg = this.$t('geolocation_error') as string
  154. }
  155. )
  156. } else {
  157. this.errorMsg = this.$t('geolocation_not_supported') as string
  158. }
  159. }
  160. }
  161. })
  162. </script>
  163. <style>
  164. .v-input__control {
  165. height: 56px;
  166. }
  167. </style>