geoSearch
instantsearch.widgets.geoSearch({ container: string|HTMLElement, googleReference: object, // Optional parameters initialZoom: number, initialPosition: object, mapOptions: object, builtInMarker: object, customHTMLMarker: object, enableRefine: boolean, enableClearMapRefinement: boolean, enableRefineControl: boolean, enableRefineOnMapMove: boolean, templates: object, cssClasses: object, });
About this widget
The geoSearch
widget displays the list of results on a Google Maps map. It lets you search for results based on their position, but also provides some of the common geo search patterns such as search on map interactions.
Requirements
Note that the widget uses the geo search capabilities of Algolia. Your hits must have a _geoloc
attribute so they can be displayed on the map.
The feature is currently incompatible with several values in the _geoloc
attribute.
You are responsible for loading the Google Maps library, it doesn’t come with InstantSearch.js. You need to load the library and pass a reference to the widget. You can find more information about how to install the library in the Google Maps documentation.
Don’t forget to explicitly set the height
of the map container (see below), otherwise it won’t show.
1
2
3
.ais-GeoSearch-map {
height: 500px; /* You can adapt the height */
}
Examples
1
2
3
4
instantsearch.widgets.geoSearch({
container: '#geo-search',
googleReference: window.google,
});
Options
container
|
type: string|HTMLElement
Required
The CSS Selector or |
||
Copy
|
|||
googleReference
|
type: object
Required
The reference to the global See the Google Maps documentation for more information. |
||
Copy
|
|||
initialZoom
|
type: number
default: 1
Optional
By default, the map sets the zoom based on to the markers that are displayed on it. Yet when InstantSearch.js refines the results, they may be empty. When it happens, it needs a zoom level to render the map. |
||
Copy
|
|||
initialPosition
|
type: object
default: { lat: 0, lng: 0 }
Optional
By default, the map sets the position based on to the markers that are displayed on it. Yet when InstantSearch.js refines the results, they may be empty. When it happens, it needs a position to render the map. |
||
Copy
|
|||
mapOptions
|
type: object
Optional
The options forwarded to the Google Maps constructor. See the Google Maps documentation for more information. |
||
Copy
|
|||
builtInMarker
|
type: object
Optional
The options for customizing the built-in Google Maps markers. This is ignored when the
|
||
Copy
|
|||
customHTMLMarker
|
type: object
Optional
The options for customizing the HTML marker. InstantSearch.js provides an alternative to the built-in Google Maps markers to give a full control over the marker rendering. You can use plain HTML to build your marker (see
|
||
Copy
|
|||
enableRefine
|
type: boolean
default: true
Optional
If |
||
Copy
|
|||
enableClearMapRefinement
|
type: boolean
default: true
Optional
If |
||
Copy
|
|||
enableRefineControl
|
type: boolean
default: true
Optional
If |
||
Copy
|
|||
enableRefineOnMapMove
|
type: boolean
default: true
Optional
If |
||
Copy
|
|||
templates
|
type: object
Optional
The templates to use for the widget. |
||
Copy
|
|||
cssClasses
|
type: object
default: {}
Optional
The CSS classes to override.
|
||
Copy
|
Templates
HTMLMarker
|
type: string|function
Optional
The template to use for the marker. |
||
Copy
|
|||
reset
|
type: string|function
Optional
The template for the reset button. |
||
Copy
|
|||
toggle
|
type: string|function
Optional
The template for the toggle label. |
||
Copy
|
|||
redo
|
type: string|function
Optional
The template for the redo label. |
||
Copy
|
HTML output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="ais-GeoSearch">
<div class="ais-GeoSearch-map">
<!-- Map element here -->
</div>
<div class="ais-GeoSearch-control">
<label class="ais-GeoSearch-label">
<input class="ais-GeoSearch-input" type="checkbox">
Search as I move the map
</label>
</div>
<button class="ais-GeoSearch-reset">
Clear the map refinement
</button>
</div>
Customize the UI - connectGeoSearch
If you want to create your own UI of the geoSearch
widget, you can use connectors.
It’s a 3-step process:
// 1. Create a render function
const renderGeoSearch = (renderOptions, isFirstRender) => {
// Rendering logic
};
// 2. Create the custom widget
const customGeoSearch = instantsearch.connectors.connectGeoSearch(
renderGeoSearch
);
// 3. Instantiate
search.addWidgets([
customGeoSearch({
// instance params
})
]);
Create a render function
This rendering function is called before the first search (init
lifecycle step)
and each time results come back from Algolia (render
lifecycle step).
const renderGeoSearch = (renderOptions, isFirstRender) => {
const {
object[] items,
object position,
object currentRefinement,
function refine,
function sendEvent,
function clearMapRefinement,
function isRefinedWithMap,
function toggleRefineOnMapMove,
function isRefineOnMapMove,
function setMapMoveSinceLastRefine,
function hasMapMoveSinceLastRefine,
object widgetParams,
} = renderOptions;
if (isFirstRender) {
// Do some initial rendering and bind events
}
// Render the widget
}
Rendering options
The examples built with the connector use Leaflet to render the map. Make sure to have the library correctly setup before trying the demo. You can find more details in the Leaflet documentation. We picked Leaflet but you can use any library you prefer (e.g., Google Maps, Mapbox, etc.)
items
|
type: object[]
The hits that matched the search request. |
||
Copy
|
|||
position
|
type: object
The current position of the search, when applicable. |
||
Copy
|
|||
currentRefinement
|
type: object
The current bounding box of the search, with:
|
||
refine
|
type: function
Sets a bounding box to filter the results from the given map bounds. The function accepts an object with:
|
||
sendEvent
|
type: (eventType, hit, eventName) => void
The function to send
|
||
Copy
|
|||
clearMapRefinement
|
type: function
Resets the current bounding box refinement. |
||
isRefinedWithMap
|
type: function
Returns |
||
toggleRefineOnMapMove
|
type: function
Toggles whether the user is able to refine on map move. |
||
isRefineOnMapMove
|
type: function
Returns |
||
setMapMoveSinceLastRefine
|
type: function
Sets whether the map has moved since the last refinement. This should be call on each map move. The call to the function triggers a new render only when the value changes. |
||
hasMapMoveSinceLastRefine
|
type: function
Returns |
||
widgetParams
|
type: object
All original widget options forwarded to the render function. |
Create and instantiate the custom widget
We first create custom widgets from our rendering function, then we instantiate them. When doing that, there are two types of parameters you can give:
- Instance parameters: they are predefined parameters that you can use to configure the behavior of Algolia.
- Your own parameters: to make the custom widget generic.
Both instance and custom parameters are available in connector.widgetParams
, inside the renderFunction
.
const customGeoSearch = instantsearch.connectors.connectGeoSearch(
renderGeoSearch
);
search.addWidgets([
customGeoSearch({
// Optional parameters
enableRefineOnMapMove: boolean,
transformItems: function,
})
]);
Instance options
enableRefineOnMapMove
|
type: boolean
default: true
Optional
If |
||
Copy
|
|||
transformItems
|
type: function
default: items => items
Optional
Receives the items, and is called before displaying them. Should return a new array with the same shape as the original array. Useful for mapping over the items to transform, and remove or reorder them. |
||
Copy
|
Full 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
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
// Create the render function
let map = null;
let markers = [];
let isUserInteraction = true;
const renderGeoSearch = (renderOptions, isFirstRendering) => {
const {
items,
currentRefinement,
refine,
clearMapRefinement,
widgetParams,
} = renderOptions;
const {
initialZoom,
initialPosition,
container,
} = widgetParams;
if (isFirstRendering) {
const element = document.createElement('div');
element.style.height = '100%';
const button = document.createElement('button');
button.textContent = 'Clear the map refinement';
map = L.map(element);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution:
'© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
map.on('moveend', () => {
if (isUserInteraction) {
const ne = map.getBounds().getNorthEast();
const sw = map.getBounds().getSouthWest();
refine({
northEast: { lat: ne.lat, lng: ne.lng },
southWest: { lat: sw.lat, lng: sw.lng },
});
}
});
button.addEventListener('click', () => {
clearMapRefinement();
});
container.appendChild(element);
container.appendChild(button);
}
container.querySelector('button').hidden = !currentRefinement;
markers.forEach(marker => marker.remove());
markers = items.map(({ _geoloc }) =>
L.marker([_geoloc.lat, _geoloc.lng]).addTo(map)
);
isUserInteraction = false;
if (!currentRefinement && markers.length) {
map.fitBounds(L.featureGroup(markers).getBounds(), {
animate: false,
});
} else if (!currentRefinement) {
map.setView(initialPosition, initialZoom, {
animate: false,
});
}
isUserInteraction = true;
};
// Create the custom widget
const customGeoSearch = instantsearch.connectors.connectGeoSearch(
renderGeoSearch
);
// Instantiate the custom widget
search.addWidgets([
customGeoSearch({
container: document.querySelector('#geo-search'),
initialZoom: 12,
initialPosition: {
lat: 48.864716,
lng: 2.349014,
},
})
]);