In this section, we will see how places.js turns any HTML <input>
into an autocomplete address search bar.
Selected: none
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<input type="search" id="address" class="form-control" placeholder="Where are we going?" />
<p>Selected: <strong id="address-value">none</strong></p>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<script>
(function() {
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#address')
});
var $address = document.querySelector('#address-value')
placesAutocomplete.on('change', function(e) {
$address.textContent = e.suggestion.value
});
placesAutocomplete.on('clear', function() {
$address.textContent = 'none';
});
})();
</script>
You should consider using this snippet when you want your users to easily find their addresses, and have it presented in a good and reliable format.
In this section, we will see how you can interact with Places in order to fill in a complete address form.
Having an address autocomplete offers great user experience, but if the address is meant to be processed internally or used for shipping, you may have to reformat or access parts of the address, which can be cumbersome when reading an already formatted address.
In order to help you with this, Places.js emits a change event when a user selects an address from its dropdown. The event includes the selected address, also called a suggestion, in a structured format, which can then be used to populate other fields. You can learn more about the suggestion object in the documentation.
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
<form action="/billing" class="form">
<div class="form-group">
<label for="form-address">Address*</label>
<input type="search" class="form-control" id="form-address" placeholder="Where do you live?" />
</div>
<div class="form-group">
<label for="form-address2">Address 2</label>
<input type="text" class="form-control" id="form-address2" placeholder="Street number and name" />
</div>
<div class="form-group">
<label for="form-city">City*</label>
<input type="text" class="form-control" id="form-city" placeholder="City">
</div>
<div class="form-group">
<label for="form-zip">ZIP code*</label>
<input type="text" class="form-control" id="form-zip" placeholder="ZIP code">
</div>
</form>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<script>
(function() {
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#form-address'),
templates: {
value: function(suggestion) {
return suggestion.name;
}
}
}).configure({
type: 'address'
});
placesAutocomplete.on('change', function resultSelected(e) {
document.querySelector('#form-address2').value = e.suggestion.administrative || '';
document.querySelector('#form-city').value = e.suggestion.city || '';
document.querySelector('#form-zip').value = e.suggestion.postcode || '';
});
})();
</script>
This template can be used as a helper for shipping forms.
Note: This template intercepts the suggestion from the change
event and updates other <input>
fields, so that the user can then validate and/or modify the data if it is needed. This is considered a best practice because Places addresses can sometimes be incorrect or incomplete: new streets are built all the time; cities are sometimes renamed; or simply because the underlying OpenStreetMap data is not perfect in all areas. Therefore, you should not use the suggestion data without allowing the end user the possibility to modify the address.
Note: There are some challenges with address forms that have not been addressed in this example for the sake of simplicity. For instance, in some countries, there can be multiple postal codes for a single city, and you should then use a dropdown with the possible values provided in the suggestion, while still allowing the user to correct it if needed.
We will use the Leaflet JavaScript library as an example to display the places.js results on the map and update them when needed.
To try this example, you need to add leaflet in your code:
1
2
<link rel="stylesheet" href="https://cdn.jsdelivr.net/leaflet/1/leaflet.css" />
<script src="https://cdn.jsdelivr.net/leaflet/1/leaflet.js"></script>
Then use this:
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
<div id="map-example-container"></div>
<input type="search" id="input-map" class="form-control" placeholder="Where are we going?" />
<style>
#map-example-container {height: 300px};
</style>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<script>
(function() {
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#input-map')
});
var map = L.map('map-example-container', {
scrollWheelZoom: false,
zoomControl: false
});
var osmLayer = new L.TileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
minZoom: 1,
maxZoom: 13,
attribution: 'Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors'
}
);
var markers = [];
map.setView(new L.LatLng(0, 0), 1);
map.addLayer(osmLayer);
placesAutocomplete.on('suggestions', handleOnSuggestions);
placesAutocomplete.on('cursorchanged', handleOnCursorchanged);
placesAutocomplete.on('change', handleOnChange);
placesAutocomplete.on('clear', handleOnClear);
function handleOnSuggestions(e) {
markers.forEach(removeMarker);
markers = [];
if (e.suggestions.length === 0) {
map.setView(new L.LatLng(0, 0), 1);
return;
}
e.suggestions.forEach(addMarker);
findBestZoom();
}
function handleOnChange(e) {
markers
.forEach(function(marker, markerIndex) {
if (markerIndex === e.suggestionIndex) {
markers = [marker];
marker.setOpacity(1);
findBestZoom();
} else {
removeMarker(marker);
}
});
}
function handleOnClear() {
map.setView(new L.LatLng(0, 0), 1);
markers.forEach(removeMarker);
}
function handleOnCursorchanged(e) {
markers
.forEach(function(marker, markerIndex) {
if (markerIndex === e.suggestionIndex) {
marker.setOpacity(1);
marker.setZIndexOffset(1000);
} else {
marker.setZIndexOffset(0);
marker.setOpacity(0.5);
}
});
}
function addMarker(suggestion) {
var marker = L.marker(suggestion.latlng, {opacity: .4});
marker.addTo(map);
markers.push(marker);
}
function removeMarker(marker) {
map.removeLayer(marker);
}
function findBestZoom() {
var featureGroup = L.featureGroup(markers);
map.fitBounds(featureGroup.getBounds().pad(0.5), {animate: false});
}
})();
</script>
Warning: This is an advanced feature.
Although Places.js comes with a good default template that should fit most use cases, you may want to customize both the input value and dropdown suggestion templates to better fit your needs.
In order to help you modify the rendered values for both objects, Places exposes a templates option that can be configured for both the value
and suggestion
components.
Templates are functions called with a suggestion object.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<input type="search" id="address-templates" class="form-control" placeholder="Where are we going?" />
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<script>
(function() {
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#address-templates'),
templates: {
value: function(suggestion) {
return 'Maybe ' + suggestion.name + ' in ' + suggestion.country + '?';
},
suggestion: function(suggestion) {
return '<u>Click here to select ' + suggestion.name + ' from ' + suggestion.country + '</u>';
}
}
});
})();
</script>
Warning: This is an advanced feature.
The default Algolia Places styling can be enhanced by overriding the default rules.
If you need full control and want to disable all default styling, you can do it by setting the style
option to false.
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
<div id="input-styling-address">
<input type="search" placeholder="Where are we going?" />
</div>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<style>
#input-styling-address input {
display: inline-block;
border: 1px solid #d9d9d9;
border-radius: 12px;
background: #ffffff;
padding: 1em 0 1em 45px;
width: 100%;
}
#input-styling-address input:focus, #input-styling-address input:active {
outline: 0;
border-color: #aaaaaa;
background: #ffffff;
}
#input-styling-address .ap-nostyle-dropdown-menu {
box-shadow: none;
border: 1px solid #dadada;
border-radius: 0;
background: #fff;
width: 100%;
}
#input-styling-address .ap-nostyle-input-icon {
display: block;
position: absolute;
background: none;
border: none;
}
#input-styling-address .algolia-places-nostyle { width: 50%; }
#input-styling-address .ap-nostyle-icon-pin { left: 5px;top: 10px; }
#input-styling-address .ap-nostyle-icon-clear { right: 5px;top: 15px }
#input-styling-address input:hover { border-color: silver; }
#input-styling-address input::placeholder { color: #aaaaaa; }
#input-styling-address .ap-nostyle-suggestion { border-bottom: 1px solid #efefef; }
</style>
<script>
(function() {
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#input-styling-address input'),
style: false,
debug: true
});
})();
</script>
See our documentation about styling for more details.
Important: Regardless of how you want to style your results, your updated styles must still be compliant with our Usage Policy and must display the Algolia Logo.
Places offers many ways to restrict the scope of its search to either certain types of records, certain countries, or even certain locations.
In this section, we will explore how we can use these restrictions to improve the relevance to best match the end user expectations.
Concept: type parameter.
In this section, we will see how you can restrict the scope of Places to only a certain type.
There are many use-cases where having a full blown address search is not necessary, and simply searching for a city is enough.
In order to help you with this, Places.js exposes a configuration parameter to restrict the type of records you want to search against. You can build a city search input by using the type parameter.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<input type="search" id="city" class="form-control" placeholder="In which city do you live?" />
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<script>
(function() {
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#city'),
templates: {
value: function(suggestion) {
return suggestion.name;
}
}
}).configure({
type: 'city',
aroundLatLngViaIP: false,
});
})();
</script>
Note: It is possible to restrict the type of records to more than a single type by passing an array of types that you want to search against. For instance, you can combine the city
type with the airport
type. However, be careful when searching for cities and other types at the same time. Due to the fact that cities are prioritized in the ranking, other types of records can be pushed back and have difficulties appearing in the results.
You can also search in countries only.
places.js turns any HTML <input>
into a Country as-you-type search bar. You can filter on a specific list of countries if needed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<link rel="stylesheet" type="text/css" href="//github.com/downloads/lafeber/world-flags-sprite/flags16.css" />
<div class="f16">
<input type="search" id="country" class="form-control" placeholder="What's your favorite country?" />
</div>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<script>
(function() {
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#country'),
templates: {
suggestion: function(suggestion) {
return '<i class="flag ' + suggestion.countryCode + '"></i> ' +
suggestion.highlight.name;
}
}
}).configure({
type: 'country',
});
})();
</script>
Concept: countries parameter.
In this section, we will see how you can restrict the scope of Places to search only in some countries.
Places.js exposes a countries parameter will restrict the search to records that belong to this array of countries. For instance, if you want to restrict the search to records in France:
Selected: none
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
<div class="dynamic-control-group" style="max-width: 240px">
<label for="country-selector">Select country</label>
<select id="country-selector" class="btn btn-black">
<option value="fr">France</option>
<option value="us">United States</option>
</select>
</div>
<input type="search" id="single-country-search" class="form-control" placeholder="Where are we going?" />
<p>Selected: <strong id="single-country-address-value">none</strong></p>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<script>
(function() {
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#single-country-search')
}).configure({
countries: ['fr']
});
var $address = document.querySelector('#single-country-address-value')
placesAutocomplete.on('change', function(e) {
$address.textContent = e.suggestion.value
});
placesAutocomplete.on('clear', function() {
$address.textContent = 'none';
});
var $country = document.querySelector('#country-selector')
$country.addEventListener('change', function(e){
placesAutocomplete.configure({ countries: [e.target.value] });
})
})();
</script>
Note: It is considered a best practice to restrict Places search to only the countries that are relevant to your use case. Restricting Places to a single country can greatly improve the relevance of the results.
Concepts: aroundLatLng and aroundRadius parameters.
Reusing the map example, we want to only search around Paris, France.
This is useful when you really want to display results around a specific area.
To try this example, you need to add leaflet in your code:
1
2
<link rel="stylesheet" href="https://cdn.jsdelivr.net/leaflet/1/leaflet.css" />
<script src="https://cdn.jsdelivr.net/leaflet/1/leaflet.js"></script>
Then use this:
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
<div id="map-example-container-paris"></div>
<input type="search" id="input-map-paris" class="form-control" placeholder="Find a street in Paris, France. Try "Rivoli"" />
<style>
#map-example-container-paris {
height: 300px
}
</style>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<script>
(function() {
var latlng = {
lat: 48.8566,
lng: 2.34287
};
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#input-map-paris')
}).configure({
aroundLatLng: latlng.lat + ',' + latlng.lng, // Paris latitude longitude
aroundRadius: 10 * 1000, // 10km radius
type: 'address'
});
var map = L.map('map-example-container-paris', {
scrollWheelZoom: false,
zoomControl: false
});
var osmLayer = new L.TileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
minZoom: 12,
maxZoom: 18,
attribution: 'Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors'
}
);
var markers = [];
map.setView(new L.LatLng(latlng.lat, latlng.lng), 12);
map.addLayer(osmLayer);
placesAutocomplete.on('suggestions', handleOnSuggestions);
placesAutocomplete.on('cursorchanged', handleOnCursorchanged);
placesAutocomplete.on('change', handleOnChange);
function handleOnSuggestions(e) {
markers.forEach(removeMarker);
markers = [];
if (e.suggestions.length === 0) {
map.setView(new L.LatLng(latlng.lat, latlng.lng), 12);
return;
}
e.suggestions.forEach(addMarker);
findBestZoom();
}
function handleOnChange(e) {
markers
.forEach(function(marker, markerIndex) {
if (markerIndex === e.suggestionIndex) {
markers = [marker];
marker.setOpacity(1);
findBestZoom();
} else {
removeMarker(marker);
}
});
}
function handleOnClear() {
map.setView(new L.LatLng(latlng.lat, latlng.lng), 12);
}
function handleOnCursorchanged(e) {
markers
.forEach(function(marker, markerIndex) {
if (markerIndex === e.suggestionIndex) {
marker.setOpacity(1);
marker.setZIndexOffset(1000);
} else {
marker.setZIndexOffset(0);
marker.setOpacity(0.5);
}
});
}
function addMarker(suggestion) {
var marker = L.marker(suggestion.latlng, {opacity: .4});
marker.addTo(map);
markers.push(marker);
}
function removeMarker(marker) {
map.removeLayer(marker);
}
function findBestZoom() {
var featureGroup = L.featureGroup(markers);
map.fitBounds(featureGroup.getBounds().pad(0.5), {animate: false});
}
})();
</script>
Note: Both parameters are key to restrict the search to a certain area. If a location is passed in aroundLatLng
without the aroundRadius
parameter being set, Places will prioritize results around the location but will also search the rest of the world for additional results. This is why the aroundLatLng
parameter should be considered as a wait to first search around a location rather than as a filter.
Using the .configure
method, you can dynamically modify the behaviour of the Places search, to better tailor it to your users' need.
For instance, if your service covers multiple countries or is available in multiple languages, you can dynamically modify Places configuration to better represent where your customer wants to search and in which language the results should be displayed.
Selected: none
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
<style>
.dynamic-controls {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.dynamic-control-group {
padding: 8px;
box-sizing: border-box;
display: flex;
flex-direction: column;
flex: 1 1 auto;
align-items: stretch;
width: 100%;
}
.dynamic-control-group > label {
font-size: 12px;
margin-bottom: 4px;
color: #333;
text-transform: uppercase;
}
.dynamic-control-group > select {
height: 32px;
overflow: hidden;
}
</style>
<div class="dynamic-controls">
<div class="dynamic-control-group">
<label for="dynamic-country-selector">Search in country</label>
<select id="dynamic-country-selector" class="btn btn-black">
<option value="de">Germany</option>
<option value="us">United States</option>
<option value="">World</option>
</select>
</div>
<div class="dynamic-control-group">
<label for="dynamic-type-selector">Search for</label>
<select id="dynamic-type-selector" class="btn btn-black">
<option value="city">City</option>
<option value="address">Address</option>
</select>
</div>
<div class="dynamic-control-group">
<label for="dynamic-language-selector">Display in</label>
<select id="dynamic-language-selector" class="btn btn-black">
<option value="en">English</option>
<option value="de">German</option>
<option value="">Use default</option>
</select>
</div>
<div class="dynamic-control-group">
<label for="dynamic-nb-hits">Display X results</label>
<select id="dynamic-nb-hits" class="btn btn-black">
<option value="4">4</option>
<option value="8">8</option>
<option value="0">Use default (5)</option>
</select>
</div>
</div>
<input type="search" id="dynamic-search" class="form-control" placeholder="Where are we going?" />
<p>Selected: <strong id="dynamic-address-value">none</strong></p>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<script>
(function() {
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#dynamic-search')
}).configure({
countries: ['de'],
type: 'city',
language: 'en',
hitsPerPage: 4
});
var $address = document.querySelector('#dynamic-address-value')
placesAutocomplete.on('change', function(e) {
$address.textContent = e.suggestion.value
});
placesAutocomplete.on('clear', function() {
$address.textContent = 'none';
});
var $country = document.querySelector('#dynamic-country-selector')
$country.addEventListener('change', function(e){
var country = e.target.value;
if (country) {
placesAutocomplete.configure({ countries: [country] });
}
else {
placesAutocomplete.configure({ countries: undefined });
}
});
var $type = document.querySelector('#dynamic-type-selector')
$type.addEventListener('change', function(e){
placesAutocomplete.configure({ type: e.target.value });
});
var $language = document.querySelector('#dynamic-language-selector')
$language.addEventListener('change', function(e){
var language = e.target.value;
if (language) {
placesAutocomplete.configure({ language: language });
}
else {
placesAutocomplete.configure({ language: undefined });
}
});
var $nbHits = document.querySelector('#dynamic-nb-hits')
$nbHits.addEventListener('change', function(e) {
var hitsPerPage = parseInt(e.target.value, 10);
if (hitsPerPage) {
placesAutocomplete.configure({ hitsPerPage: hitsPerPage });
}
else {
placesAutocomplete.configure({ hitsPerPage: undefined });
}
});
})();
</script>
Using Algolia's autocomplete.js library, you can search in your own data along with showing Algolia Places results.
You need an Algolia account to do so and the Algolia Places dataset:
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
<input type="search" id="autocomplete-dataset" class="form-control" placeholder="Search for vacation rentals or cities" />
<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearchLite.min.js"></script>
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.js"></script>
<style>
.algolia-autocomplete {
width: 100%;
}
.ad-example-dropdown-menu {
width: 100%;
color: black;
background-color: #fff;
border: 1px solid #ccc;
border-top: none;
border-radius: 5px;
padding: .5em;
box-shadow: 1px 1px 32px -10px rgba(0,0,0,0.62);
}
.ad-example-dropdown-menu .ad-example-suggestion {
cursor: pointer;
padding: 5px 4px;
}
.ad-example-dropdown-menu .ad-example-suggestion img {
height: 2em;
margin-top: .5em;
margin-right: 10px;
float: left;
}
.ad-example-dropdown-menu .ad-example-suggestion small {
font-size: .8em;
color: #bbb;
}
.ad-example-dropdown-menu .ad-example-suggestion.ad-example-cursor {
background-color: #B2D7FF;
}
.ad-example-dropdown-menu .ad-example-suggestion em {
font-weight: bold;
font-style: normal;
}
.ad-example-header {
font-weight: bold;
padding: .5em 0;
margin-bottom: 1em;
border-bottom: 1px solid #ccc;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0/dist/cdn/placesAutocompleteDataset.min.js"></script>
<script>
(function() {
var client = algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76');
var index = client.initIndex('airbnb');
// create the first autocomplete.js dataset: vacation rentals
var rentalsDataset = {
source: autocomplete.sources.hits(index, {hitsPerPage: 2}),
displayKey: 'name',
name: 'rentals',
templates: {
header: '<div class="ad-example-header">Vacation rentals</div>',
suggestion: function(suggestion) {
return '<img src="' + suggestion.thumbnail_url + '" />' +
'<div>' +
suggestion._highlightResult.name.value + '<br />' +
'<small>' + suggestion._highlightResult.city.value + '</small>' +
'</div>';
}
}
};
// create the second dataset: places
// we automatically inject the default CSS
// all the places.js options are available
var placesDataset = placesAutocompleteDataset({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
algoliasearch: algoliasearch,
templates: {
header: '<div class="ad-example-header">Cities</div>'
},
hitsPerPage: 3
});
// init
var autocompleteInstance = autocomplete(document.querySelector('#autocomplete-dataset'), {
hint: false,
debug: true,
cssClasses: {prefix: 'ad-example'}
}, [
rentalsDataset,
placesDataset
]);
var autocompleteChangeEvents = ['selected', 'autocompleted'];
autocompleteChangeEvents.forEach(function(eventName) {
autocompleteInstance.on('autocomplete:'+ eventName, function(event, suggestion, datasetName) {
console.log(datasetName, suggestion);
});
});
})();
</script>
See the documentation about the placesAutocompleteDataset function.
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
<input type="search" id="input-map-instantsearch" class="form-control" placeholder="Where are you looking for a coffee?" />
<div id="map-instantsearch-container"></div>
<style>
#map-instantsearch-container {height: 300px};
</style>
<script src="https://cdn.jsdelivr.net/instantsearch.js/2.10.1/instantsearch.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0/dist/cdn/placesInstantsearchWidget.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBawL8VbstJDdU5397SUX7pEt9DslAwWgQ"></script>
<script>
(function() {
var search = instantsearch({
appId: 'latency',
apiKey: 'ffc36feb6e9df06e1c3c4549b5af2b31',
indexName: 'starbucks',
});
var configure = instantsearch.widgets.configure({
hitsPerPage: 25,
});
var searchBox = placesInstantsearchWidget({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#input-map-instantsearch')
});
var geosearch = instantsearch.widgets.geoSearch({
container: '#map-instantsearch-container',
googleReference: window.google,
builtInMarker: {
createOptions: function(item) {
return {
title: item.Brand + ' ' + item.Name
};
}
}
});
search.addWidget(configure);
search.addWidget(searchBox);
search.addWidget(geosearch);
search.start();
})();
</script>
Concepts: type parameter, aroundLatLngViaIP, aroundPrecision, programmatic search.
In this section, we will see how you can emulate reverse geocoding to find city closest to a user using the Places API Client.
When constructing a search UI using InstantSearch, you may want to filter your dataset to only display some of your products based on the distance to the end user. This is usually done using the aroundLatLngViaIP filter, or aroundLatLng filter if you have access to precise geolocation information. However geographical filters are hard to interpret when displayed in raw format, as noone really knows where the coordinates 48.8566, 2.34287
are.
Using Places, you can do a query to find the city in which your user is located and display that city name instead of a geolocation.
Example:
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
<div>
<strong>Searching around: </strong>
<input type="search" id="reverse-city" placeholder="What's your favorite city?"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<script>
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#reverse-city')
}).configure({
type: 'city',
hitsPerPage: 1,
aroundLatLngViaIP: true
});
placesAutocomplete.search().then(function(suggestions) {
if (!suggestions[0]) {
return;
}
var name = suggestions[0].name;
var country = suggestions[0].country;
var formattedCity = locale_names[0] + ', ' + country;
var infoElt = document.querySelector("#reverse-city");
infoElt.value = formattedCity;
});
</script>
Note: This is not a reverse geocoding API, and it is still bound by the ranking used for search, which means that in some cases large close-by cities can be promoted over a more closely located city.
In some cases, you may not want to tie your Places search to an input, but rather access it only programmatically.
In these cases, you can use the regular JavaScript API Client, or the api client in your preferred language. You can head to the API Clients page to see which api clients support Places.
For instance, here we will rewrite the previous example using the JS api client instead.
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
<div>
<strong>Searching around: </strong>
<div id="api-client-reverse-city"></div>
</div>
<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>
<script>
var placesClient = algoliasearch.initPlaces(
'<YOUR_PLACES_APP_ID>',
'<YOUR_PLACES_API_KEY>'
);
var preferredLanguage = window.navigator.language.split('-')[0];
placesClient.search({
query: '',
aroundLatLngViaIP: true,
type: 'city',
hitsPerPage: 1,
language: preferredLanguage,
}).then(function(results) {
var hits = results.hits;
if (!hits[0]) {
return;
}
var locale_names = hits[0].locale_names;
var country = hits[0].country;
var formattedCity = locale_names[0] + ', ' + country;
var infoElt = document.querySelector("#api-client-reverse-city");
infoElt.textContent = formattedCity;
});
</script>
Note: This example uses the standard JavaScript API client in which the Places API is integrated. This example does not use the Places.js library.
Note: This is not a reverse geocoding API, and it is still bound by the ranking used for search, which means that in some cases large close-by cities can be promoted over a more closely located city. You should use the reverse API if you want results that are only affected by the geolocation.
Concepts: reverse API.
In this section, we will see how you can do reverse geocoding with a precision up to the street using the reverse places client.
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
<style>
.reverse-geo-controls {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
}
</style>
<div style="display: none;">
<input id="reverse-demo-ignored"/>
</div>
<div class="reverse-geo-controls">
<div class="form-group" style="flex-grow: 1; margin-right: 8px;">
<label for="reverse-geo-lat">Latitude</label>
<input id="reverse-geo-lat" class="form-control" placeholder="try 48.87"/>
</div>
<div class="form-group" style="flex-grow: 1; margin-left: 8px; margin-right: 8px;">
<label for="reverse-geo-lng">Longitude</label>
<input id="reverse-geo-lng" class="form-control" placeholder="try 2.31"/>
</div>
<div class="form-group" style="margin-left: 8px;">
<button id="reverse-locate-me" class="btn btn-black" style="width: 150px;">Locate me</button>
</div>
</div>
<form action="/locate" class="form">
<div class="form-group">
<label for="reverse-address">Address</label>
<input type="text" class="form-control" id="reverse-address" placeholder="Street number and name" />
</div>
<div class="form-group">
<label for="reverse-town">City*</label>
<input type="text" class="form-control" id="reverse-town" placeholder="City">
</div>
<div class="form-group">
<label for="reverse-zip">ZIP code*</label>
<input type="text" class="form-control" id="reverse-zip" placeholder="ZIP code">
</div>
</form>
<script src="https://cdn.jsdelivr.net/algoliasearch/3.31/algoliasearchLite.min.js"></script>
<script>
(function() {
var places = algoliasearch.initPlaces('<YOUR_PLACES_APP_ID>', '<YOUR_PLACES_API_KEY>');
function updateForm(response) {
var hits = response.hits;
var suggestion = hits[0];
if (suggestion && suggestion.locale_names && suggestion.city) {
document.querySelector('#reverse-address').value = suggestion.locale_names.default[0] || '';
document.querySelector('#reverse-town').value = suggestion.city.default[0] || '';
document.querySelector('#reverse-zip').value = (suggestion.postcode || [])[0] || '';
}
}
var lat, lng;
var $button = document.querySelector('#reverse-locate-me');
var $latInput = document.querySelector('#reverse-geo-lat');
var $lngInput = document.querySelector('#reverse-geo-lng');
$latInput.addEventListener('change', function(e) {
try {
lat = parseFloat(e.target.value);
if (typeof lat !== 'undefined' && typeof lng !== 'undefined') {
places.reverse({
aroundLatLng: lat + ',' + lng,
hitsPerPage: 1,
}).then(updateForm);
}
} catch (e) {
lat = undefined;
}
});
$lngInput.addEventListener('change', function(e) {
try {
lng = parseFloat(e.target.value);
if (typeof lat !== 'undefined' && typeof lng !== 'undefined') {
places.reverse({
aroundLatLng: lat + ',' + lng,
hitsPerPage: 1,
}).then(updateForm);
}
} catch (e) {
lng = undefined;
}
});
$button.addEventListener('click', function() {
$button.textContent = 'Locating...';
navigator.geolocation.getCurrentPosition(function(response) {
var coords = response.coords;
lat = coords.latitude.toFixed(6);
lng = coords.longitude.toFixed(6);
$latInput.value = lat;
$lngInput.value = lng;
$button.textContent = 'Locate me';
places.reverse({
aroundLatLng: lat + ',' + lng,
hitsPerPage: 1
}).then(updateForm);
});
});
})();
</script>
Important: This example does not rely on the Places.js library, but the JavaScript algoliasearch-client library. This API is only available in the JS Client for version 3.31 and above.
Note: The format of the response is slightly different from the one that is available with the Places.js library (it matches the format of the rawAnswer field in Places.js suggestions object).
Note: There are many use cases where you will want to combine the reverse geocoding API with the regular Places search. In this case, you do not need to import both the standard Places library and the JS Client, as the reverse functionality is included in the standard Places.js library. You can access the reverse endpoint programmatically using placesInstance.reverse(location)
.
Using the postcodeSearch
attribute, you can restrict the search to the postcodes field only.
Important: While this attribute will improve the quality of the search when looking for postcode, please note that postcode in OSM are sparse and that some postcodes may be missing or approximate. As such, we recommend you to always let the user modify this section and only rely on this data as a mean to suggest rather than a source of truth.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<input type="search" id="docs-postcode-search" class="form-control" placeholder="Search for a french city by postcode (e.g. 75008)" />
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<script>
(function() {
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#docs-postcode-search')
}).configure({
postcodeSearch: true,
countries: ['fr'],
type: 'city'
});
})();
</script>
Concepts: dynamic configuration, reverse API, multiple instance.
As we explained in the Focusing Search section, restricting the search to a single country can greatly increase the relevance of the search, because it allows Places to leverage certain specificities of that particular country. However, your userbase might not necessarily be located in a single country, which means that by default you cannot use this option to your advantage.
In this example, we will present a way to dynamically restrict the scope of the Places search to a single country on page load, while still allowing the user to change the behaviour if he prefers less restricted search. We will also expose a way for the user to use the reverse geocoding API to fill in the details for him based on the geolocation provided by the browser. In other words, this is a more advanced implementation of the Complete Form example introduced earlier.
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
<link rel="stylesheet" type="text/css" href="//github.com/downloads/lafeber/world-flags-sprite/flags16.css" />
<style>
.form-inline-group {
display: flex;
flex-direction: row;
}
.form-inline-group .primary {
flex-grow: 1;
margin-left: 0.5em;
}
.form-info {
font-size: 0.85em;
color: rgba(0, 0, 0, 0.75);
margin-bottom: 1em;
}
</style>
<form action="/shippit" class="form">
<div class="form-inline-group">
<div class="form-group">
<label for="dynamic-form-hsn">House Nr.</label>
<input type="text" class="form-control" id="dynamic-form-hsn" placeholder="ex: 123" />
</div>
<div class="form-group primary">
<label for="dynamic-form-address">Address*</label>
<div class="reverse-geo-controls">
<input type="search" class="form-control" id="dynamic-form-address" placeholder="Where do you live?" />
<div style="margin-left: 0.5em;">
<button id="locate-me" class="btn btn-black" style="width: 150px;">Try to locate me</button>
</div>
</div>
</div>
</div>
<div class="form-info"> Searching in <span id="dynamic-scope">the entire world</span>. You can modify this by changing your Country of residence below.</div>
<div class="form-group">
<label for="dynamic-form-address2">Address 2</label>
<input type="text" class="form-control" id="dynamic-form-address2" placeholder="Appartment/Suite number" />
</div>
<div class="form-group">
<label for="dynamic-form-city">City*</label>
<input type="text" class="form-control" id="dynamic-form-city" placeholder="City">
</div>
<div class="form-group">
<label for="dynamic-form-zip">ZIP code*</label>
<input type="text" class="form-control" id="dynamic-form-zip" placeholder="ZIP code">
</div>
<div class="form-group">
<label for="dynmic-form-country">Country of residence*</label>
<div class="f16">
<input type="search" class="form-control" id="dynamic-form-country" placeholder="Country">
</div>
</div>
</form>
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>
<script>
(function() {
/* create a regular autocomplete form */
var addressPlacesInstance = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#dynamic-form-address'),
type: 'address',
templates: {
value: function(suggestion) {
return suggestion.name;
}
}
});
addressPlacesInstance.on('change', function resultSelected(e) {
document.querySelector('#dynamic-form-address').value = e.suggestion.name || '';
document.querySelector('#dynamic-form-city').value = e.suggestion.city || '';
document.querySelector('#dynamic-form-zip').value = e.suggestion.postcode || '';
document.querySelector('#dynamic-form-country').value = e.suggestion.country || '';
});
/* enrich the country field with a country search */
var countryPlacesInstance = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#dynamic-form-country'),
type: 'country',
templates: {
suggestion: function(suggestion) {
return '<i class="flag ' + suggestion.countryCode + '"></i> ' +
suggestion.highlight.name;
}
}
});
/* If the user changes the country, update the address search configuration */
countryPlacesInstance.on('change', function(e) {
var country = e.suggestion.name;
var countryCode = e.suggestion.countryCode;
document.querySelector('#dynamic-form-country').value = country;
document.querySelector('#dynamic-scope').textContent = country;
addressPlacesInstance.configure({ countries: [countryCode] });
})
/* do a onload query for the country */
var preferredLanguage = window.navigator.language.split('-')[0];
algoliasearch.initPlaces('<YOUR_PLACES_APP_ID>','<YOUR_PLACES_API_KEY>').search({
query: '',
aroundLatLngViaIP: true,
type: 'country',
hitsPerPage: 1,
language: preferredLanguage,
}).then(function(response) {
var hits = response.hits;
var suggestion = hits[0];
var country = suggestion.locale_names[0];
var countryCode = suggestion.country_code;
document.querySelector('#dynamic-form-country').value = country;
document.querySelector('#dynamic-scope').textContent = country;
addressPlacesInstance.configure({ countries: [countryCode] });
});
/* -- use this instead once the method is implemented
countryPlacesInstance.search('').then(function(suggestions) {
var suggestion = suggestions[0];
var country = suggestion.name;
var countryCode = suggestion.countryCode;
document.querySelector('#dynamic-form-country').value = country;
document.querySelector('#dynamic-scope').textContent = country;
addressPlacesInstance.configure({ countries: [countryCode] });
});
*/
var $button = document.querySelector('#locate-me');
/* If the user does a click on the Locate me button, do a reverse query */
$button.addEventListener('click', function(e) {
e.preventDefault();
$button.textContent = 'Locating...';
navigator.geolocation.getCurrentPosition(function(response) {
var coords = response.coords;
lat = coords.latitude.toFixed(6);
lng = coords.longitude.toFixed(6);
$button.textContent = 'Try to locate me';
addressPlacesInstance.reverse(lat + ',' + lng);
});
});
/* on reverse results, update the form with the first suggestion */
addressPlacesInstance.on('reverse', function resultSelected(e) {
var suggestion = e.suggestions[0];
document.querySelector('#dynamic-form-address').value = suggestion.name || '';
document.querySelector('#dynamic-form-city').value = suggestion.city || '';
document.querySelector('#dynamic-form-zip').value = suggestion.postcode || '';
document.querySelector('#dynamic-form-country').value = suggestion.country || '';
document.querySelector('#dynamic-form-hsn').value = "";
});
})();
</script>
Concepts: _rankingInfo, query strategy, geolocation precision.
In this section, we will see how we can leverage _rankingInfo
to provide additional information to our user about certain behaviours of the query strategy.
As with any Algolia query, _rankingInfo
will include information about how the ranking was computed based on the words
, exact
, filters
, nbTypos
, etc.
However, Places also enriches the _rankingInfo with additional information based on which part of the query strategy was used to return this result.
Finally, in countries where Places supports house level precision queries, it also provides some data on how precise the geolocation resolution was.
As such, on top of the regular _rankingInfo
fields, Places exposes the two following:
The query
field of the _rankingInfo
object is a reference to which query strategy was used to find this result while executing the Algolia Places Query.
You can read more about the Algolia Places query strategy here.
It can take 3 values depending of the strategy used to find this record:
You can leverage this attribute to better understand how your constraints impact the relevance of your search.
Note: This is a very advanced feature, which will be irrelevant to most users.
Note: All searches that Places does internally still take into account the filters that you applied, so if you restrict a query to only a few countries, a worldwide query will only look into these few countries.
The roadNumberPrecision
field of the _rankingInfo
object provides additional information on how precise the geolocation of the record is.
By default, Places only offers precision up to the street level, which means that all the house numbers of a street will have the same geolocation. However, Places offers house level precision in France, if you are part of the opt-in beta for this program.
In this case, the roadNumberPrecision
field will return either:
In this demo, we will visually show which query strategy was used to find each suggestion (on the left of the suggestion), as well as show the geolocation precision of results on the right.
Remember that house level precision is only available in France, so here are some example queries that you can try to see how things work under the hood:
L
(or any first letter that is not the first letter of a large city that is close to you)55 rue d'Amsterdam, Paris
(look at how there are 2 records and only one has the exact house number - 3 circles vs 2 circles)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
<input type="search" id="ranking-info" class="form-control" placeholder="Where are we going?" />
<script src="https://cdn.jsdelivr.net/npm/places.js@1.19.0"></script>
<style>
.suggestion {
display: flex;
flex-direction: row;
box-sizing: border-box;
align-items: center;
width: 100%;
padding-right: 18px;
}
.suggestion-icon {
width: 20px;
height: 20px;
margin-right: 8px;
flex-shrink: 0;
display: flex;
align-items: center;
opacity: 0.2;
}
.suggestion-name {
margin-left: 4px;
margin-right: 4px;
flex-grow: 0;
flex-shrink: 0;
}
.suggestion-address {
flex-grow: 1;
flex-shrink: 1;
margin-right: 8px;
font-size: 0.8em;
color: rgba(74, 74, 76, 0.5);
}
.suggestion-precision {
width: 20px;
height: 20px;
margin-left: 8px;
flex-shrink: 0;
display: flex;
align-items: center;
animation: none;
}
</style>
<script>
(function() {
function formatIcon(query) {
switch (query) {
case "worldwide_query":
// globe icon - result found while searching in the whole world (still respects filter restrictions)
return '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><path d="M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zM9 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L7 13v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H6V8h2c.55 0 1-.45 1-1V5h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>'
case "local_query":
// building icon - result found while searching in the country of the user (still respects filter restrictions)
return '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path d="M12 .6L2.5 6.9h18.9L12 .6zM3.8 8.2c-.7 0-1.3.6-1.3 1.3v8.8L.3 22.1c-.2.3-.3.5-.3.6 0 .6.8.6 1.3.6h21.5c.4 0 1.3 0 1.3-.6 0-.2-.1-.3-.3-.6l-2.2-3.8V9.5c0-.7-.6-1.3-1.3-1.3H3.8zm2.5 2.5c.7 0 1.1.6 1.3 1.3v7.6H5.1V12c0-.7.5-1.3 1.2-1.3zm5.7 0c.7 0 1.3.6 1.3 1.3v7.6h-2.5V12c-.1-.7.5-1.3 1.2-1.3zm5.7 0c.7 0 1.3.6 1.3 1.3v7.6h-2.5V12c-.1-.7.5-1.3 1.2-1.3z"/></svg>'
case "geo_query":
// pin icon - result found while searching near location of the user (still respects filter restrictions)
return '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 14 20"><path d="M7 0C3.13 0 0 3.13 0 7c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5C5.62 9.5 4.5 8.38 4.5 7S5.62 4.5 7 4.5 9.5 5.62 9.5 7 8.38 9.5 7 9.5z"/></svg>'
default:
return '';
}
}
function formatName (highlight) {
var name = highlight.name;
return name;
}
function formatAddress (address) {
var suburb = address.suburb;
var city = address.city;
var postcode = address.postcode;
var administrative = address.administrative;
var country = address.country;
return [ suburb, city, postcode, administrative, country ]
.filter(function(v) { return !!v })
.join(', ')
}
function formatPrecision (type, roadNumberPrecision) {
if (type === 'city') {
// 0 circle - city level precision
return '';
}
switch (roadNumberPrecision) {
case "exact":
// 3 circles - house level precision
return '<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">' +
'<circle r="12" cy="16" cx="16" fill="#ddd"/>' +
'<circle r="8" cy="16" cx="16" fill="#aaa"/>' +
'<circle r="4" cy="16" cx="16" fill="#000"/>' +
'</svg>';
case "closest":
// 2 circles - house number not found - returned geolocation of the closest house number.
return '<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">' +
'<circle r="12" cy="16" cx="16" fill="#ddd"/>' +
'<circle r="8" cy="16" cx="16" fill="#aaa"/>' +
'</svg>';
default:
// 1 circle - street level precision
return '<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">' +
'<circle r="12" cy="16" cx="16" fill="#ddd"/>' +
'</svg>';
}
}
function formatSuggestion (suggestion) {
var highlight = suggestion.highlight;
var _rankingInfo = suggestion.hit._rankingInfo;
var type = suggestion.type;
return '<div class="suggestion">' +
'<span class="suggestion-icon">' + formatIcon(_rankingInfo.query) + '</span>' +
'<span class="suggestion-name">' + formatName(highlight) + '</span>' +
'<span class="suggestion-address">' + formatAddress(highlight) + '</span>' +
'<span class="suggestion-precision">' + formatPrecision(type, _rankingInfo.roadNumberPrecision) + '</span>' +
'</div>'
}
var placesAutocomplete = places({
appId: '<YOUR_PLACES_APP_ID>',
apiKey: '<YOUR_PLACES_API_KEY>',
container: document.querySelector('#ranking-info'),
templates: {
suggestion: formatSuggestion
}
}).configure({
getRankingInfo: true
});
})();
</script>