|
15 | 15 | </template> |
16 | 16 |
|
17 | 17 | <script> |
| 18 | + const ADDRESS_COMPONENTS = { |
| 19 | + street_number: 'short_name', |
| 20 | + route: 'long_name', |
| 21 | + locality: 'long_name', |
| 22 | + administrative_area_level_1: 'short_name', |
| 23 | + administrative_area_level_2: 'county', |
| 24 | + country: 'long_name', |
| 25 | + postal_code: 'short_name' |
| 26 | + }; |
| 27 | +
|
| 28 | + const CITIES_TYPE = ['locality', 'administrative_area_level_3']; |
| 29 | + const REGIONS_TYPE = ['locality', 'sublocality', 'postal_code', 'country', |
| 30 | + 'administrative_area_level_1', 'administrative_area_level_2']; |
| 31 | +
|
18 | 32 | export default { |
19 | 33 | name: 'VueGoogleAutocomplete', |
20 | 34 |
|
|
44 | 58 | enableGeolocation: { |
45 | 59 | type: Boolean, |
46 | 60 | default: false |
| 61 | + }, |
| 62 | +
|
| 63 | + geolocationOptions: { |
| 64 | + type: Object, |
| 65 | + default: null |
47 | 66 | } |
48 | 67 | }, |
49 | 68 |
|
|
62 | 81 | * @type {String} |
63 | 82 | */ |
64 | 83 | autocompleteText: '', |
| 84 | +
|
| 85 | + geolocation: { |
| 86 | + /** |
| 87 | + * Google Geocoder Objet |
| 88 | + * @type {Geocoder} |
| 89 | + * @link https://developers.google.com/maps/documentation/javascript/reference#Geocoder |
| 90 | + */ |
| 91 | + geocoder: null, |
| 92 | +
|
| 93 | + /** |
| 94 | + * Filled after geolocate result |
| 95 | + * @type {Coordinates} |
| 96 | + * @link https://developer.mozilla.org/en-US/docs/Web/API/Coordinates |
| 97 | + */ |
| 98 | + loc: null, |
| 99 | +
|
| 100 | + /** |
| 101 | + * Filled after geolocate result |
| 102 | + * @type {Position} |
| 103 | + * @link https://developer.mozilla.org/en-US/docs/Web/API/Position |
| 104 | + */ |
| 105 | + position: null |
| 106 | + } |
65 | 107 | } |
66 | 108 | }, |
67 | 109 |
|
|
94 | 136 | options |
95 | 137 | ); |
96 | 138 |
|
97 | | - this.autocomplete.addListener('place_changed', () => { |
| 139 | + this.autocomplete.addListener('place_changed', this.onPlaceChanged); |
| 140 | + }, |
98 | 141 |
|
| 142 | + methods: { |
| 143 | + /** |
| 144 | + * When a place changed |
| 145 | + */ |
| 146 | + onPlaceChanged() { |
99 | 147 | let place = this.autocomplete.getPlace(); |
100 | 148 |
|
101 | 149 | if (!place.geometry) { |
|
105 | 153 | return; |
106 | 154 | } |
107 | 155 |
|
108 | | - let addressComponents = { |
109 | | - street_number: 'short_name', |
110 | | - route: 'long_name', |
111 | | - locality: 'long_name', |
112 | | - administrative_area_level_1: 'short_name', |
113 | | - administrative_area_level_2: 'county', |
114 | | - country: 'long_name', |
115 | | - postal_code: 'short_name' |
116 | | - }; |
117 | | -
|
118 | | - let returnData = {}; |
119 | | -
|
120 | 156 | if (place.address_components !== undefined) { |
121 | | - // Get each component of the address from the place details |
122 | | - for (let i = 0; i < place.address_components.length; i++) { |
123 | | - let addressType = place.address_components[i].types[0]; |
124 | | -
|
125 | | - if (addressComponents[addressType]) { |
126 | | - let val = place.address_components[i][addressComponents[addressType]]; |
127 | | - returnData[addressType] = val; |
128 | | - } |
129 | | - } |
130 | | -
|
131 | | - returnData['latitude'] = place.geometry.location.lat(); |
132 | | - returnData['longitude'] = place.geometry.location.lng(); |
133 | | -
|
134 | 157 | // return returnData object and PlaceResult object |
135 | | - this.$emit('placechanged', returnData, place, this.id); |
| 158 | + this.$emit('placechanged', this.formatResult(place), place, this.id); |
136 | 159 |
|
137 | 160 | // update autocompleteText then emit change event |
138 | 161 | this.autocompleteText = document.getElementById(this.id).value |
139 | 162 | this.onChange() |
140 | 163 | } |
141 | | - }); |
142 | | - }, |
| 164 | + }, |
143 | 165 |
|
144 | | - methods: { |
145 | 166 | /** |
146 | 167 | * When the input gets focus |
147 | 168 | */ |
148 | 169 | onFocus() { |
149 | | - this.geolocate(); |
| 170 | + this.biasAutocompleteLocation(); |
150 | 171 | this.$emit('focus'); |
151 | 172 | }, |
152 | 173 |
|
|
209 | 230 | this.autocompleteText = value |
210 | 231 | }, |
211 | 232 |
|
| 233 | + /** |
| 234 | + * Update the coordinates of the input |
| 235 | + * @param {Coordinates} value |
| 236 | + */ |
| 237 | + updateCoordinates (value) { |
| 238 | + if (!value && !(value.lat || value.lng)) return; |
| 239 | + if (!this.geolocation.geocoder) this.geolocation.geocoder = new google.maps.Geocoder(); |
| 240 | + this.geolocation.geocoder.geocode({'location': value}, (results, status) => { |
| 241 | + if (status === 'OK') { |
| 242 | + results = this.filterGeocodeResultTypes(results); |
| 243 | + if (results[0]) { |
| 244 | + this.$emit('placechanged', this.formatResult(results[0]), results[0], this.id); |
| 245 | + this.update(results[0].formatted_address); |
| 246 | + } else { |
| 247 | + this.$emit('error', 'no result for provided coordinates'); |
| 248 | + } |
| 249 | + } else { |
| 250 | + this.$emit('error', 'error getting address from coords'); |
| 251 | + } |
| 252 | + }) |
| 253 | + }, |
| 254 | +
|
| 255 | + /** |
| 256 | + * Update location based on navigator geolocation |
| 257 | + */ |
| 258 | + geolocate () { |
| 259 | + this.updateGeolocation ((geolocation, position) => { |
| 260 | + this.updateCoordinates(geolocation) |
| 261 | + }) |
| 262 | + }, |
| 263 | +
|
| 264 | + /** |
| 265 | + * Update internal location from navigator geolocation |
| 266 | + * @param {Function} (geolocation, position) |
| 267 | + */ |
| 268 | + updateGeolocation (callback = null) { |
| 269 | + if (navigator.geolocation) { |
| 270 | + let options = {}; |
| 271 | + if(this.geolocationOptions) Object.assign(options, this.geolocationOptions); |
| 272 | + navigator.geolocation.getCurrentPosition(position => { |
| 273 | + let geolocation = { |
| 274 | + lat: position.coords.latitude, |
| 275 | + lng: position.coords.longitude |
| 276 | + }; |
| 277 | + this.geolocation.loc = geolocation; |
| 278 | + this.geolocation.position = position; |
| 279 | +
|
| 280 | + if (callback) callback(geolocation, position); |
| 281 | + }, err => { |
| 282 | + this.$emit('error', 'Cannot get Coordinates from navigator', err); |
| 283 | + }, options); |
| 284 | + } |
| 285 | + }, |
| 286 | +
|
| 287 | +
|
212 | 288 | // Bias the autocomplete object to the user's geographical location, |
213 | 289 | // as supplied by the browser's 'navigator.geolocation' object. |
214 | | - geolocate() { |
| 290 | + biasAutocompleteLocation () { |
215 | 291 | if (this.enableGeolocation) { |
216 | | - if (navigator.geolocation) { |
217 | | - navigator.geolocation.getCurrentPosition(position => { |
218 | | - let geolocation = { |
219 | | - lat: position.coords.latitude, |
220 | | - lng: position.coords.longitude |
221 | | - }; |
| 292 | + this.updateGeolocation((geolocation, position) => { |
222 | 293 | let circle = new google.maps.Circle({ |
223 | | - center: geolocation, |
224 | | - radius: position.coords.accuracy |
| 294 | + center: geolocation, |
| 295 | + radius: position.coords.accuracy |
225 | 296 | }); |
226 | 297 | this.autocomplete.setBounds(circle.getBounds()); |
227 | | - }); |
| 298 | + }) |
| 299 | + } |
| 300 | + }, |
| 301 | +
|
| 302 | + /** |
| 303 | + * Format result from Geo google APIs |
| 304 | + * @param place |
| 305 | + * @returns {{formatted output}} |
| 306 | + */ |
| 307 | + formatResult (place) { |
| 308 | + let returnData = {}; |
| 309 | + for (let i = 0; i < place.address_components.length; i++) { |
| 310 | + let addressType = place.address_components[i].types[0]; |
| 311 | +
|
| 312 | + if (ADDRESS_COMPONENTS[addressType]) { |
| 313 | + let val = place.address_components[i][ADDRESS_COMPONENTS[addressType]]; |
| 314 | + returnData[addressType] = val; |
| 315 | + } |
| 316 | + } |
| 317 | +
|
| 318 | + returnData['latitude'] = place.geometry.location.lat(); |
| 319 | + returnData['longitude'] = place.geometry.location.lng(); |
| 320 | + return returnData |
| 321 | + }, |
| 322 | +
|
| 323 | + /** |
| 324 | + * Extract configured types out of raw result as |
| 325 | + * Geocode API does not allow to do it |
| 326 | + * @param results |
| 327 | + * @returns {GeocoderResult} |
| 328 | + * @link https://developers.google.com/maps/documentation/javascript/reference#GeocoderResult |
| 329 | + */ |
| 330 | + filterGeocodeResultTypes (results) { |
| 331 | + if (!results || !this.types) return results; |
| 332 | + let output = []; |
| 333 | + let types = [this.types]; |
| 334 | + if (types.includes('(cities)')) types = types.concat(CITIES_TYPE); |
| 335 | + if (types.includes('(regions)')) types = types.concat(REGIONS_TYPE); |
| 336 | +
|
| 337 | + for (let r of results) { |
| 338 | + for (let t of r.types) { |
| 339 | + if (types.includes(t)) { |
| 340 | + output.push(r); |
| 341 | + break; |
| 342 | + } |
228 | 343 | } |
229 | 344 | } |
| 345 | + return output; |
230 | 346 | } |
231 | 347 | } |
232 | 348 | } |
|
0 commit comments