-
Notifications
You must be signed in to change notification settings - Fork 9
/
index.js
174 lines (160 loc) · 5.58 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/**
* @export
* @class AddressAutocomplete
* @author Deric Cain <deric.cain@gmail.com>
* @contributor Braunson Yager <braunson@gmail.com>
* @contributor Jonathan Sardo <sardoj@gmail.com>
*/
export default class AddressAutocomplete {
/**
* Creates an instance of AddressAutocomplete.
* @param {string} element - This should be in the form of either '.address' or '#address'
* @param {object|function} optionsOrCallback - This object contains options to add to the API call or a callback
* @param {function|null} callback - This callback will have the result passed as the first param
* @throws Error - If we don't have a valid element
* @memberof AddressAutocomplete
*/
constructor(element, optionsOrCallback, callback = null) {
// Can take element as '.class-name' or '#id-name'
this.element = document.querySelector(element);
// If we do not find the element, then we need to throw an error
if (!this.element) {
throw new Error(
"The element you specified is not a valid element. You should attach an input using a class '.some-class' or an ID '#some-id'."
);
}
// Default options
const defaultOptions = {
types: ['geocode'],
};
if (typeof optionsOrCallback === 'function') {
// Compatible with previous versions
// Second parameter is a callback function
this.callback = optionsOrCallback;
// There is not extra options
this.options = defaultOptions;
} else if (typeof optionsOrCallback === 'object') {
// Second parameter is an options list
this.options = Object.assign({}, defaultOptions, optionsOrCallback);
// Third parameter is a callback function
this.callback = callback;
} else {
throw new Error(
'To be able to use extra options, the type of the second parameter must be "object" and the type of the third parameter must be "function".'
);
}
// We are binding the context of 'this' to this class instance
this.extractAddress = this.extractAddress.bind(this);
this.getUsersLocation = this.getUsersLocation.bind(this);
this.handle();
}
/**
* This takes care of make everything happen
*
* @memberof AddressAutocomplete
*/
handle() {
// When the document is ready, we need to fire everything off.
document.addEventListener('readystatechange', () => {
this.initializeAutocomplete();
this.element.addEventListener('focus', this.getUsersLocation);
});
}
/**
* This method takes care of getting the autocomplete up and running with custom options
*
* @memberof AddressAutocomplete
*/
initializeAutocomplete() {
this.autocomplete = new google.maps.places.Autocomplete(
this.element,
this.options
);
this.autocomplete.addListener('place_changed', this.extractAddress);
}
/**
* Here, we are taking care of getting the address from the results.
*
* @memberof AddressAutocomplete
*/
extractAddress() {
const componentForm = {
street_number: 'short_name',
route: 'long_name',
locality: 'long_name',
administrative_area_level_1: 'short_name',
country: 'long_name',
postal_code: 'short_name',
};
const resultRaw = this.autocomplete.getPlace();
const {
address_components,
formatted_address,
geometry: { location: { lat, lng } },
} = resultRaw;
const addressObject = {
streetNumber: '',
streetName: '',
cityName: '',
stateAbbr: '',
zipCode: '',
coordinates: { lat: lat(), lng: lng() },
};
// Need to loop over the results and create a friendly object
for (let i = 0; i < address_components.length; i++) {
const addressType = address_components[i].types[0];
if (componentForm[addressType]) {
switch (addressType) {
case 'street_number':
addressObject.streetNumber = address_components[i].long_name;
break;
case 'route':
addressObject.streetName = address_components[i].long_name;
break;
case 'locality':
addressObject.cityName = address_components[i].long_name;
break;
case 'administrative_area_level_1':
addressObject.stateAbbr = address_components[i].short_name;
addressObject.state = address_components[i].long_name;
break;
case 'postal_code':
addressObject.zipCode = address_components[i].long_name;
break;
case 'country':
addressObject.countryAbbr = address_components[i].short_name;
addressObject.country = address_components[i].long_name;
break;
default:
break;
}
}
}
const resultFormatted = Object.assign({}, addressObject, {
formattedAddress: formatted_address,
});
// This is where we check for the callback and then call it, passing our resutls
this.callback(resultFormatted, resultRaw);
}
/**
* This will help us narrow down the results of the autocomplete to a user's location
*
* @memberof AddressAutocomplete
*/
getUsersLocation() {
// Using feature detection to make sure the browser supports geolocation
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(position => {
const geolocation = {
lat: position.coords.latitude,
lng: position.coords.longitude,
};
const circle = new google.maps.Circle({
center: geolocation,
radius: position.coords.accuracy,
});
this.autocomplete.setBounds(circle.getBounds());
});
}
}
}