InstantSearch V4 Migration
Introduction
Algolia is migrated InstantSearch from version 1 to version 4, so you can make use of all latest InstantSearch features and bugfixes.
This change is gradually made accessible to everyone. If you’d like to get early access, please contact the support team.
While this migration is not mandatory, we strongly encourage it.
Depending on your implementation of the plugin, this migration should be done either:
- Automatically, through the plugin admin panel.
- Manually, if you have a custom implementation.
Automatic migration
To automatically migrate to the newest version of InstantSearch, reinstall Algolia into your theme.
This will override InstantSearch and our scripts with up-to-date versions.
If you have changed or customized your theme by editing the scripts provided by our plugin, proceed with a manual migration. Otherwise, you will lose your changes.
To automatically update the Algolia dependencies of your theme, go to your Algolia plugin admin and follow these steps:
1. Go to the Display tab.
2. Click Install to a new theme.
3. Select the theme you want to update.
5. Click Finish installation.
Manual migration
Read the complete InstantSearch migration guide for a complete list of the changes between InstantSearch v1 and v4.
If your shop has a custom theme you have to manually update your theme.
To migrate manually, open the theme code editor.
The changes described are the ones that must be applied to the original InstantSearch implementation and may differ from the actual changes depending on your custom implementation. This is a general guide in addition to the the complete InstantSearch migration guide.
InstantSearch initialization
appId
andapiKey
parameters are replaced bysearchClient
urlSync
is replaced byrouting
- In
searchFunction
,searchFunctionHelper
must be used in place ofinstant.search.helper
Changes to make in algolia_instant_search.js.liquid
:
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
@@ -48,6 +48,33 @@
);
$hiding.appendTo($('head'));
+ function getTrackedUiState(uiState) {
+ var trackedUiState = {};
+ Object.keys(uiState).forEach(function(k) {
+ if (k === 'configure' || k === 'query' || k === 'q') {
+ return;
+ }
+ trackedUiState[k] = uiState[k];
+ });
+ return trackedUiState;
+ }
+
+ function singleIndex(indexName) {
+ return {
+ stateToRoute: function(uiState) {
+ var route = getTrackedUiState(uiState[indexName] || {});
+ route.q = uiState[indexName].query;
+ return route;
+ },
+ routeToState: function(routeState) {
+ var state = {};
+ state[indexName] = getTrackedUiState(routeState || {});
+ state[indexName].query = routeState.q;
+ return state;
+ },
+ };
+ }
+
var instant = {
colors: algolia.config.colors,
distinct: Boolean(algolia.config.show_products),
@@ -75,17 +102,21 @@
: algolia.config.products_full_results_hits_per_page,
poweredBy: algolia.config.powered_by,
search: instantsearch({
- appId: algolia.config.app_id,
- apiKey: algolia.config.search_api_key,
+ searchClient: window.algoliasearch(
+ algolia.config.app_id,
+ algolia.config.search_api_key
+ ),
indexName: '' + algolia.config.index_prefix + 'products',
searchParameters: {
clickAnalytics: true,
},
- urlSync: {},
+ routing: {
+ stateMapping: singleIndex(algolia.config.index_prefix + 'products'),
+ },
searchFunction: function(searchFunctionHelper) {
// Set query parameters here because they're not kept when someone
// presses the Back button if set in the `init` function of a custom widget
- var helper = instant.search.helper;
+ var helper = searchFunctionHelper;
var page = helper.getPage();
helper.setQueryParameter(
'highlightPreTag',
Widgets
Facet widgets (menu
, rangeSlider
, refinementList
)
attributeName
option is renamed toattribute
transformData
option is replaced bytransformItems
Changes to make in algolia_facets.js.liquid
:
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
@@ -97,8 +97,9 @@
var widget = TYPES_TO_WIDGET[facet.type];
var params = _.cloneDeep(widget.params) || {};
params.container = "[class~='ais-facet-" + facet.escapedName + "']";
- params.attributeName = facet.name;
+ params.attribute = facet.name;
params.templates = {};
params.cssClasses = algolia.facetCssClasses;
@@ -125,14 +126,16 @@
}
var displayFunction = algolia.facetDisplayFunctions[facet.name];
- params.transformData = function(data) {
- var transformedData = Object.assign({}, data);
- transformedData.type = {};
- transformedData.type[facet.type] = true;
- if (displayFunction) {
- transformedData.name = displayFunction(data.name);
- }
- return transformedData;
+ params.transformItems = function(items) {
+ return items.map(function(item) {
+ var transformedItem = Object.assign({}, item);
+ transformedItem.type = {};
+ transformedItem.type[facet.type] = true;
+ transformedItem.label = displayFunction
+ ? displayFunction(item.value)
+ : item.value;
+ return transformedItem;
+ });
};
return {
Changes to make in algolia_instant_search.hogan.liquid
:
1
2
3
4
5
6
7
8
9
10
11
12
13
@@ -5,7 +5,10 @@
<div class="ais-facets">
<div class="ais-current-refined-values-container"></div>
[[# facets ]]
- <div class="ais-facet-[[ type ]] ais-facet-[[ escapedName ]]"></div>
+ <div class="ais-facet-[[ type ]] ais-facet-[[ escapedName ]]">
+ <div class="ais-range-slider--header ais-facet--header ais-header">[[ title ]]</div>
+ <div class="ais-facet-[[ escapedName ]]-container"></div>
+ </div>
[[/ facets ]]
</div>
<div class="ais-block">
Changes to make in algolia_instant_search_facet_item.hogan.liquid
:
1
2
3
4
5
6
7
8
9
@@ -2,7 +2,7 @@
[[# type.disjunctive ]]
<input type="checkbox" class="[[ cssClasses.checkbox ]]" [[# isRefined ]]checked[[/ isRefined ]]/>
[[/ type.disjunctive ]]
- [[& name ]]
+ [[& label ]]
<span class="[[ cssClasses.count ]]">
[[# helpers.formatNumber ]]
[[ count ]]
searchBox
widget
poweredBy
option is removedshowReset
andshowSubmit
options are added
Changes to make in algolia_instant_search.js.liquid
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@@ -235,9 +235,10 @@
instantsearch.widgets.searchBox({
container: '.ais-search-box-container',
placeholder: algolia.translations.searchForProduct,
- poweredBy: false,
- })
- );
+ showReset: false,
+ showSubmit: false,
+ }),
+ ]);
// Logo & clear
instant.search.addWidget({
stats
widget
body
template is renamed totext
transformData
option is removed. If you want to apply transformations to your data, you can do so in the template using some Hogan helpers (seealgolia_helpers.js.liquid
for some examples)
Changes to make in algolia_instant_search.js.liquid
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@@ -270,17 +270,7 @@
instantsearch.widgets.stats({
container: '.ais-stats-container',
templates: {
- body: instant.templates.stats,
- },
- transformData: {
- body: function(data) {
- return Object.assign({}, data, {
- processingTimeS: data.processingTimeMS / 1000,
- start: data.page * data.hitsPerPage + 1,
- end: Math.min((data.page + 1) * data.hitsPerPage, data.nbHits),
- translations: algolia.translations,
- });
- },
+ text: instant.templates.stats,
},
})
);
4. sortBySelector
widget
sortBySelector
widget was renamed tosortBy
indices
option was renamed toitems
- A
sortBy
item value is nowvalue
instead ofname
Changes to make in algolia_instant_search.js.liquid
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@@ -287,12 +287,12 @@
// Sort orders
if (activeSortOrders.length > 1) {
instant.search.addWidget(
- instantsearch.widgets.sortBySelector({
+ instantsearch.widgets.sortBy({
container: '.ais-sort-orders-container',
- indices: instant.sortOrders,
- })
+ items: instant.sortOrders,
+ }),
);
}
// Change display
Changes to make in algolia_sort_orders.js.liquid
:
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
@@ -6,7 +6,7 @@
var sort_order_base = '' + algolia.config.index_prefix + 'products';
algolia.sortOrders = [
- { name: sort_order_base, label: '' + algolia.translations.relevance },
+ { value: sort_order_base, label: '' + algolia.translations.relevance },
];
_.forEach(algolia.config.sort_orders, function(sort_order) {
@@ -16,7 +16,7 @@
(sort_order.asc.active === true || sort_order.asc.active === '1')
) {
algolia.sortOrders.push({
- name: sort_order_base + '_' + sort_order.key + '_asc',
+ value: sort_order_base + '_' + sort_order.key + '_asc',
label: sort_order.asc.title,
});
}
@@ -26,7 +26,7 @@
(sort_order.desc.active === true || sort_order.desc.active === '1')
) {
algolia.sortOrders.push({
- name: sort_order_base + '_' + sort_order.key + '_desc',
+ value: sort_order_base + '_' + sort_order.key + '_desc',
label: sort_order.desc.title,
});
}
@@ -43,7 +43,7 @@
if (collection_sort_orders) {
algolia.collectionSortOrders = [
- { name: sort_order_base, label: '' + algolia.translations.relevance },
+ { value: sort_order_base, label: '' + algolia.translations.relevance },
];
_.forEach(collection_sort_orders, function(sort_order) {
@@ -53,7 +53,7 @@
(sort_order.asc.active === true || sort_order.asc.active === '1')
) {
algolia.collectionSortOrders.push({
- name: sort_order_base + '_' + sort_order.key + '_asc',
+ value: sort_order_base + '_' + sort_order.key + '_asc',
label: sort_order.asc.title,
});
}
@@ -63,7 +63,7 @@
(sort_order.desc.active === true || sort_order.desc.active === '1')
) {
algolia.collectionSortOrders.push({
- name: sort_order_base + '_' + sort_order.key + '_desc',
+ value: sort_order_base + '_' + sort_order.key + '_desc',
label: sort_order.desc.title,
});
}
currentRefinedValues
widget
The currentRefinedValues
widget is removed and replaced by the currentRefinements
and clearRefinements
widgets that have a slightly different UI.
Here’s an example on how you can reproduce the currentRefinedValues
UI using both new widgets.
Changes to make in algolia_instant_search.js.liquid
:
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
@@ -346,30 +346,104 @@
});
// Current refined values
- var attributes = _.map(instant.facets.shown, function(facet) {
- return {
- name: facet.name,
- label: facet.title,
- };
- });
- instant.search.addWidget(
- instantsearch.widgets.currentRefinedValues({
- container: '.ais-current-refined-values-container',
- cssClasses: {
- root: 'ais-facet',
- header: 'ais-facet--header',
- body: 'ais-facet--body',
- },
- templates: {
- header: algolia.translations.selectedFilter,
- item: instant.templates.currentItem,
- clearAll: algolia.translations.clearAll,
- },
- onlyListedAttributes: true,
- attributes: attributes,
- })
- );
-
+ var createDataAttribtues = function(refinement) {
+ return Object.keys(refinement)
+ .map(function(key) {
+ return 'data-' + key + '="' + refinement[key] + '"';
+ })
+ .join(' ');
+ };
+
+ var renderListItem = function(item) {
+ var facet = instant.facets.list.find(function(f) {
+ return f.name === item.label;
+ });
+ return item.refinements
+ .map(function(refinement) {
+ return (
+ '<li class="ais-current-refined-values--item">' +
+ ' <a ' +
+ createDataAttribtues(refinement) +
+ ' class="ais-current-refined-values--link">' +
+ ' <div>' +
+ ' <div class="ais-current-refined-values--label">' +
+ facet.title +
+ ' </div>: ' +
+ refinement.label +
+ ' </div>' +
+ ' </a>' +
+ '</li>'
+ );
+ })
+ .join('');
+ };
+
+ var renderCurrentRefinements = function(renderOptions) {
+ var items = renderOptions.items;
+ var refine = renderOptions.refine;
+ var widgetParams = renderOptions.widgetParams;
+
+ widgetParams.container.innerHTML =
+ '<div class="ais-current-refined-values--header ais-facet--header ais-header">Selected filters</div>' +
+ '<div class="ais-root ais-current-refined-values ais-facet">' +
+ ' <ul class="ais-current-refined-values--list">' +
+ items.map(renderListItem).join('') +
+ ' </ul>' +
+ '</div>';
+
+ Array.prototype.slice
+ .call(
+ widgetParams.container.querySelectorAll(
+ '.ais-current-refined-values--link'
+ )
+ )
+ .forEach(function(element) {
+ element.addEventListener('click', function(event) {
+ var item = Object.keys(event.currentTarget.dataset).reduce(function(
+ acc,
+ key
+ ) {
+ var itemData = {};
+ itemData[key] = event.currentTarget.dataset[key];
+ return algolia.assign({}, acc, itemData);
+ },
+ {});
+
+ refine(item);
+ });
+ });
+ };
+
+ var customCurrentRefinements = instantsearch.connectors.connectCurrentRefinements(
+ renderCurrentRefinements
+ );
+
+ var customCurrentRefinementsWithPanel = instantsearch.widgets.panel({
+ hidden: function(options) {
+ return !instant.facets.list.some(function(facetName) {
+ return options.helper.hasRefinements(facetName);
+ });
+ },
+ })(customCurrentRefinements);
+
+ var clearRefinementsWithPanel = instantsearch.widgets.panel({
+ hidden: function(options) {
+ return !instant.facets.list.some(function(facetName) {
+ return options.helper.hasRefinements(facetName);
+ });
+ },
+ })(instantsearch.widgets.clearRefinements);
+
+ instant.search.addWidgets([
+ clearRefinementsWithPanel({
+ container: '.ais-clear-refinements-container',
+ templates: { resetLabel: algolia.translations.clearAll },
+ }),
+ customCurrentRefinementsWithPanel({
+ container: '.ais-current-refined-values-container',
+ }),
+ ]);
// Facets
_.forEach(instant.facets.widgets, function(widget) {
instant.search.addWidget(
Changes to make in algolia_instant_search.hogan.liquid
:
1
2
3
4
5
6
7
8
@@ -3,6 +3,7 @@
Show filters
</div>
<div class="ais-facets">
+ <div class="ais-clear-refinements-container"></div>
<div class="ais-current-refined-values-container"></div>
[[# facets ]]
<div class="ais-facet-[[ type ]] ais-facet-[[ escapedName ]]"></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
@@ -5,7 +5,10 @@
<div class="ais-facets">
<div class="ais-current-refined-values-container"></div>
[[# facets ]]
- <div class="ais-facet-[[ type ]] ais-facet-[[ escapedName ]]"></div>
+ <div class="ais-facet-[[ type ]] ais-facet-[[ escapedName ]]">
+ <div class="ais-range-slider--header ais-facet--header ais-header">[[ title ]]</div>
+ <div class="ais-facet-[[ escapedName ]]-container"></div>
+ </div>
[[/ facets ]]
</div>
<div class="ais-block">
Changes to make in algolia_instant_search_facet_item.hogan.liquid
:
1
2
3
4
5
6
7
8
9
@@ -2,7 +2,7 @@
[[# type.disjunctive ]]
<input type="checkbox" class="[[ cssClasses.checkbox ]]" [[# isRefined ]]checked[[/ isRefined ]]/>
[[/ type.disjunctive ]]
- [[& name ]]
+ [[& label ]]
<span class="[[ cssClasses.count ]]">
[[# helpers.formatNumber ]]
[[ count ]]
hits
widget
sortBySelector
widget is renamed tosortBy
indices
option is renamed toitems
-
A
sortBy
item value is nowvalue
instead ofname
transformData
option is replaced bytransformItems
hitsPerPage
option is removed, you can use theconfigure
widget to set it.
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
@@ -230,4 +261,10 @@
},
});
+ instant.search.addWidgets([
+ instantsearch.widgets.configure({
+ hitsPerPage: instant.hitsPerPage,
+ }),
+ ]);
+
// Search input
@@ -381,13 +455,12 @@
instant.search.addWidget(
instantsearch.widgets.hits({
container: '.ais-hits-container',
- hitsPerPage: instant.hitsPerPage,
templates: {
empty: instant.templates.empty,
item: instant.templates.product,
},
- transformData: {
- item: function(product) {
+ transformItems: function(products) {
+ return products.map(function(product) {
return Object.assign({}, product, {
_distinct: instant.distinct,
can_order:
@@ -395,15 +468,11 @@
product.inventory_policy === 'continue' ||
product.inventory_quantity > 0,
translations: algolia.translations,
- queryID: instant.search.helper.lastResults._rawResults[0].queryID,
+ queryID: product.__queryID,
productPosition: product.__hitIndex + 1,
});
- },
- empty: function(params) {
- return Object.assign({}, params, {
- translations: algolia.translations,
- });
- },
+ });
},
})
);
CSS
A lot of CSS classes changed between InstantSearch v1 and v4, and you may need to update your CSS accordingly. You can find the correspondence list between the old and the new classes in the complete InstantSearch migration guide .