Address.vue 3.9 KB

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