geoSearch
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, });
1
import { geoSearch } from 'instantsearch.js/es/widgets';
About this widget
The geoSearch
widget lets you search for results based on their position within a specified area (a bounding box).
It also provides features such as “search on map interactions”.
The geoSearch
widget doesn’t let you search around a central point or
within polygons.
If that’s important to you, you must use the Algolia API instead.
Requirements
- Your hit records must have a
_geoloc
attribute so they can be displayed on the map. - You must load the Google Maps library and pass a reference to the widget: the library doesn’t come with InstantSearch.js.
- You must explicitly set the map container’s
height
. For example:
1
2
3
.ais-GeoSearch-map {
height: 500px; /* You can change this height */
}
Examples
1
2
3
4
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 |
||
Copy
|
|||
initialZoom
|
type: number
default: 1
Optional
By default, the map sets the zoom level based on the displayed markers (results). However, after InstantSearch has applied a refinement, there may be no results. When this happens, a specific zoom level is required to render the map. |
||
Copy
|
|||
initialPosition
|
type: object
default: { lat: 0, lng: 0 }
Optional
By default, the map sets the position based on the displayed markers (results). However, after InstantSearch has applied a refinement, there may be no results. When this happens, a specific position is required to render the map. |
||
Copy
|
|||
mapOptions
|
type: object
Optional
|
||
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.
You can use plain HTML to build your custom markers (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 you can override:
|
||
Copy
|
Templates
You can customize parts of the widget’s UI using the Templates API.
Every template provides an html
function you can use as a tagged template. Using html
lets you safely provide templates as an HTML string. It works directly in the browser without a build step. See Templating your UI for more information.
The html
function is available starting from v4.46.0.
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 with connectGeoSearch
If you want to create your own UI of the geoSearch
widget, you can use connectors.
To use connectGeoSearch
, you can import it with the declaration relevant to how you installed InstantSearch.js.
1
import { connectGeoSearch } from 'instantsearch.js/es/connectors';
Then it’s a 3-step process:
// 1. Create a render function
const renderGeoSearch = (renderOptions, isFirstRender) => {
// Rendering logic
};
// 2. Create the custom widget
const customGeoSearch = 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 following rendering option example code snippets use Leaflet to render the map. If you prefer not to use Leaflet, you can use another library (such as Google Maps or Mapbox).
items
|
type: object[]
Hits that matched the search request. |
||
Copy
|
|||
position
|
type: object
The current search position, when applicable. |
||
Copy
|
|||
currentRefinement
|
type: object
The search’s bounding box:
|
||
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 users can refine on map move. |
||
isRefineOnMapMove
|
type: function
Returns |
||
setMapMoveSinceLastRefine
|
type: function
Indicates that the map has moved since the last refinement. This function should be called on each map move. It helps ensure the map is only re-rendered when it has moved. |
||
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 = 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 transforming, removing, or reordering items. In addition, the full |
||
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 = connectGeoSearch(
renderGeoSearch
);
// Instantiate the custom widget
search.addWidgets([
customGeoSearch({
container: document.querySelector('#geo-search'),
initialZoom: 12,
initialPosition: {
lat: 48.864716,
lng: 2.349014,
},
})
]);