Collection Search Page Migration
Introduction
Algolia Search - Instant Search (1.57.0) brings improvements to the Collection page feature:
- Improved indexing stability
- Preparation for upcoming collection customisations and merchandising
The migration doesn’t apply to you if you installed Algolia Search - Instant Search after July 30th, 2019 or if you do not use the Collection page feature.
Depending on your implementation of the plugin, you should either do this migration:
- Automatically via the plugin admin panel. This applies if you have enabled autocomplete, InstantSearch and other front-end features through the plugin.
- Manually, if you have a custom implementation.
Automatic migration from the admin
To automatically upgrade your shop to Algolia Search - Instant Search (1.57.0), please follow the steps from the upgrade wizard.
Clicking on Upgrade shop triggers the following actions:
- Full reindex of your products and collections: this update changes the way we index products to bring more reliability and better indexing performances.
- Update of your front-end Algolia widgets: changing the way we index products requires an update of the installed Algolia widgets.
If you have changed or customized your theme by manually editing the scripts of the plugin, you must perform the migration manually. Otherwise, you will lose your changes.
If the upgrade wizard doesn’t show up, please contact us.
Manual migration
If your shop has a custom front-end implementation, you have to manually update your theme.
Algolia Search - Instant Search (1.17.0) changes the way collection page products are indexed to Algolia, and brings customization of Collections page (Facets, Sort Orders).
This requires changing the following part in your front-end implementation:
- Products filtering
- Rules
- CSS selector logic
- Facets and Sort Orders logic
This section provides you all the steps to migrate depending on your implementation:
- You are using the InstantSearch widget, which you customized.
- You have a custom implementation, using an InstantSearch library on your own.
Before updating the code, open the Algolia Search - Instant Search application and click on the Update shop button. This will reindex your products and collections with the proper attributes needed for the code update.
If the upgrade wizard doesn’t show up, please contact us.
Migrate InstantSearch Widget customizations
If you installed Algolia through our plug-in, but customized our files (specifically the ones listed below), you should perform a manual upgrade.
The following is a list of the final versions of the files we create/modify:
- algolia_current_collection_id.liquid
- algolia_init.js.liquid
- algolia_facets.js.liquid
- algolia_sort_orders.js.liquid
- algolia_instant_search.js.liquid
Create the algolia_current_collection_id.liquid
file
To query a collection’s products, you must give your InstantSearch widget access to its ID.
To do this, pass the value of the current collection’s ID to the algolia_instant_search.js
file.
Current collection information is only accessible from Liquid files, which are evaluated by the Shopify back end before rendering the page.
For this reason, we need to create the algolia_current_collection_id.liquid
snippet file.
1. Open the theme code editor.
2. Create a new snippet file named algolia_current_collection_id.liquid
.
3. Add the following content and save the file:
1
2
3
{
"currentCollectionID": {{collection.id}}
}
4. Open the layout/theme.liquid
file.
5. Add the following line before the template_algolia_money_format
script tag:
1
<script type="text/template" id="template_algolia_current_collection_id">{% include 'algolia_current_collection_id' %}</script>
6. Save changes to the layout/theme.liquid
file.
Update the algolia_init.js.liquid
file
You now have the current collection’s ID in the <template id="template_algolia_current_collection_id">
.
To use the ID with InstantSearch, store it in a JavaScript variable.
Add the following snippet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@@ -34,6 +34,19 @@
return template.render(obj);
};
+ // Current collection page ID
+ const current_collection_id_string = algolia.getTemplate('current_collection_id').replace(/^\s+|\s+$/g, '');
+
+ if (!!current_collection_id_string) {
+ try {
+ const current_collection_id_object = JSON.parse(current_collection_id_string);
+ algolia.current_collection_id = current_collection_id_object.currentCollectionID;
+ } catch(error) {
+ // Encountered an error while trying to parse the collection ID.
+ // This most probably means that we aren't on a collection page.
+ }
+ }
+
/* Add CSS block after current script */
algolia.appendStyle = function appendStyle(content) {
function insertAfter(newNode, referenceNode) {
Update the algolia_facets.js.liquid
file
The plugin now lets you configure Facets for collections page.
To enable it, you need to update the algolia_facets.js.liquid
file:
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
@@ -71,11 +71,13 @@
algolia.facets = _.map(enabledFacets, function (facet) { return Object.assign({}, facet, { escapedName: encodeURIComponent(facet.name) }) });
algolia.shownFacets = _.filter(algolia.facets, function (facet) { return facet.type !== 'hidden' });
algolia.hiddenFacets = _.filter(algolia.config.facets, function (facet) { return facet.type === 'hidden'; });
+
algolia.facetTitles = {};
_.each(algolia.facets, function (facet) {
algolia.facetTitles[facet.name] = facet.title;
});
- algolia.facetsWidgets = _.map(algolia.shownFacets, function (facet) {
+
+ var facetToWidget = function (facet) {
var widget = TYPES_TO_WIDGET[facet.type],
params = _.cloneDeep(widget.params) || {};
@@ -115,5 +117,22 @@
name: widget.name,
params: params
};
- });
+ };
+
+ // Try to fetch facets for current collection or fallback to collections default
+ const collection_facets = algolia.current_collection_id && algolia.config.collection_facets && algolia.config.collection_facets[algolia.current_collection_id] ?
+ algolia.config.collection_facets[algolia.current_collection_id] :
+ algolia.config.collection_facets && algolia.config.collection_facets.default;
+
+ if (collection_facets) {
+ var enabledCollectionFacets = _.filter(collection_facets, function (facet) { return facet.enabled || parseInt(facet.enabled); });
+
+ algolia.collectionFacets = _.map(enabledCollectionFacets, function (facet) { return Object.assign({}, facet, { escapedName: encodeURIComponent(facet.name) }) });
+ algolia.collectionShownFacets = _.filter(algolia.collectionFacets, function (facet) { return facet.type !== 'hidden' });
+ algolia.collectionHiddenFacets = _.filter(collection_facets, function (facet) { return facet.type === 'hidden'; });
+
+ algolia.collectionFacetsWidgets = _.map(algolia.collectionShownFacets, facetToWidget);
+ }
+
+ algolia.facetsWidgets = _.map(algolia.shownFacets, facetToWidget);
}(algoliaShopify));
Update the algolia_sort_orders.js.liquid
file
The plugin now lets you configure sort orders for your collections page.
To enable this feature, you need to update the algolia_sort_orders.js.liquid
file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@@ -15,4 +15,22 @@
algolia.sortOrders.push({ name: sort_order_base + '_' + sort_order.key + '_desc', label: sort_order.desc.title });
}
});
+
+ // Try to fetch sort orders for current collection or fallback to collections default
+ const collection_sort_orders = algolia.current_collection_id && algolia.config.collection_sort_orders && algolia.config.collection_sort_orders[algolia.current_collection_id] ?
+ algolia.config.collection_sort_orders[algolia.current_collection_id] :
+ algolia.config.collection_sort_orders && algolia.config.collection_sort_orders.default;
+
+ if (collection_sort_orders) {
+ algolia.collectionSortOrders = [{ name: sort_order_base, label: '' + algolia.translations.relevance }];
+
+ _.forEach(collection_sort_orders, function (sort_order) {
+ if (!_.isUndefined(sort_order.asc) && !_.isNull(sort_order.asc) && (sort_order.asc.active === true || sort_order.asc.active === '1')) {
+ algolia.collectionSortOrders.push({ name: sort_order_base + '_' + sort_order.key + '_asc', label: sort_order.asc.title });
+ }
+ if (!_.isUndefined(sort_order.desc) && !_.isNull(sort_order.desc) && (sort_order.desc.active === true || sort_order.desc.active === '1')) {
+ algolia.collectionSortOrders.push({ name: sort_order_base + '_' + sort_order.key + '_desc', label: sort_order.desc.title });
+ }
+ });
+ }
}(algoliaShopify));
Update the algolia_instant_search.js.liquid
file
We have multiple changes to make to algolia_instant_search.js.liquid
file.
Let’s tackle them one by one:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@@ -1,11 +1,11 @@
(function (algolia, instantsearch) {
- var collectionFacetConstraint = !!algolia.is_collection_results_page &&
+ var collectionPage = !!algolia.is_collection_results_page &&
!!algolia.config.instant_search_enabled_on_collection;
if (
- (!algolia.full_results && !collectionFacetConstraint) || !algolia.config.instant_search_enabled
+ (!algolia.full_results && !collectionPage) || !algolia.config.instant_search_enabled
) {
return;
}
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
@@ -13,29 +13,41 @@
var _ = algolia._,
$ = algolia.jQuery;
- var collectionFacetValue = null;
- if (collectionFacetConstraint) {
+
+ var collectionFacetFilter = null;
+ var collectionRulesContextValue = null;
+ if (collectionPage) {
var matches = window.location.pathname.match(/\/collections\/([^/]+)/i);
- if (!!matches && matches.length === 2) {
- collectionFacetValue = matches[1];
- }
+ const handle = !!matches && matches.length === 2 ? matches[1] : null;
+
+ collectionFacetFilter = algolia.config.collection_id_indexing ?
+ algolia.current_collection_id ? 'collection_ids:"' + algolia.current_collection_id + '"' : null :
+ 'collections:"' + handle + '"';
+
+ collectionRulesContextValue = algolia.config.collection_id_query_rules ?
+ algolia.current_collection_id :
+ handle;
}
- algolia.config.results_selector += ', .algolia-shopify-instantsearch';
+ var results_selector = collectionPage ? algolia.config.collection_css_selector : algolia.config.results_selector;
- var $hiding = $('<style>' + algolia.config.results_selector + ' { visibility: hidden }</style>');
+ var activeSortOrders = collectionPage && algolia.collectionSortOrders ? algolia.collectionSortOrders : algolia.sortOrders;
+
+ results_selector += ', .algolia-shopify-instantsearch';
+
+ var $hiding = $('<style>' + results_selector + ' { visibility: hidden }</style>');
$hiding.appendTo($('head'));
var instant = algolia.instantsearch = {
colors: algolia.config.colors,
distinct: !!algolia.config.show_products,
facets: {
- hidden: algolia.hiddenFacets,
- shown: algolia.shownFacets,
- list: algolia.facets,
- widgets: algolia.facetsWidgets
+ hidden: collectionPage && algolia.collectionHiddenFacets ? algolia.collectionHiddenFacets : algolia.hiddenFacets,
+ shown: collectionPage && algolia.collectionShownFacets ? algolia.collectionShownFacets : algolia.shownFacets,
+ list: collectionPage && algolia.collectionFacets ? algolia.collectionFacets : algolia.facets,
+ widgets: collectionPage && algolia.collectionFacetsWidgets ? algolia.collectionFacetsWidgets : algolia.facetsWidgets
},
- hitsPerPage: algolia.config.products_full_results_hits_per_page,
+ hitsPerPage: collectionPage && algolia.config.collections_full_results_hits_per_page ? algolia.config.collections_full_results_hits_per_page : algolia.config.products_full_results_hits_per_page,
poweredBy: algolia.config.powered_by,
search: instantsearch({
appId: algolia.config.app_id,
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
@@ -55,15 +67,44 @@
if (instant.distinct) {
helper.setQueryParameter('distinct', true);
}
- if (!!collectionFacetConstraint && !!collectionFacetValue) {
- helper.setQueryParameter('filters', 'collections:"' + collectionFacetValue + '"');
+
+ // Collection page features
+ if (!!collectionPage) {
+ // Collection page filtering
+ if (!!collectionFacetFilter) {
+ helper.setQueryParameter('filters', collectionFacetFilter);
+ }
+
+ // Collection page merchandising:
+ // send rulesContext for promoted results only if no filters active
+ var currentState = helper.getState();
+ if (_.keys(currentState.facetsRefinements).length === 0 &&
+ _.keys(currentState.numericRefinements).length === 0 &&
+ _.keys(currentState.disjunctiveFacetsRefinements).length === 0 &&
+ page == 0
+ ) {
+ // If we are on a collection page, `collectionRulesContextValue` is defined
+ if (!!collectionRulesContextValue) {
+ helper.setQueryParameter('ruleContexts', [collectionRulesContextValue.toString()]);
+ }
+ } else {
+ helper.setQueryParameter('ruleContexts', []);
+ }
}
+
helper.setPage(page);
searchFunctionHelper.search();
}
}),
- selector: algolia.config.results_selector + ', .algolia-shopify-instantsearch',
- sortOrders: algolia.sortOrders,
+ selector: results_selector + ', .algolia-shopify-instantsearch',
+ sortOrders: activeSortOrders,
storeName: algolia.storeName,
templates: {
currentItem: algolia.getTemplate('instant_search_current_refined_values_item'),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@@ -107,9 +148,10 @@
}));
$(document).ready(function () {
-
- if ($(algoliaShopify.config.results_selector).length == 0) {
- throw new Error('Instant search CSS selector is incorrect\nFore more info see : https://community.algolia.com/shopify/css_selector.html#algolia-search');
+ if (collectionPage && $(algoliaShopify.config.collection_css_selector).length == 0) {
+ throw new Error('Instant search CSS selector for collection page is incorrect\nFore more info see : https://www.algolia.com/doc/integration/shopify/building-search-ui/instant-search/#css-selector');
+ } else if ($(algoliaShopify.config.results_selector).length == 0) {
+ throw new Error('Instant search CSS selector is incorrect\nFore more info see : https://www.algolia.com/doc/integration/shopify/building-search-ui/instant-search/#css-selector');
}
instant.$results = $(instant.selector);
1
2
3
4
5
6
7
8
9
@@ -118,7 +160,7 @@
facets: instant.facets.list,
storeName: instant.storeName,
translations: algolia.translations,
- multipleSortOrders: algolia.sortOrders.length > 1
+ multipleSortOrders: activeSortOrders.length > 1
}));
readjust();
1
2
3
4
5
6
7
8
9
@@ -198,7 +240,7 @@
);
// Sort orders
- if (algolia.sortOrders.length > 1) {
+ if (activeSortOrders.length > 1) {
instant.search.addWidget(
instantsearch.widgets.sortBySelector({
container: '.ais-sort-orders-container',
Your customizations are now migrated.
Migrate a custom front-end search UI implementation
Your shop front-end search UI is built without using the provided widgets.
When browsing a collection page, the front-end search implementation needs the current collection ID to filter products and activate Rules. To do so, we need to communicate the value of this current collection’s ID to the JavaScript files that’s responsible of the search implementation.
1. Create the algolia_current_collection_id.liquid
file
Current collection information is only accessible from Liquid files, which are evaluated by the Shopify back end before rendering the page.
For this reason we need to create the algolia_current_collection_id.liquid
snippet file.
1. Open the theme code editor.
2. Create a new snippet file named algolia_current_collection_id.liquid
3. Add the following content and save the file:
1
2
3
{
"currentCollectionID": {{collection.id}}
}
4. Open the layout/theme.liquid
file.
5. Add the following line before the template_algolia_money_format
script tag:
1
<script type="text/template" id="template_algolia_current_collection_id">{% include 'algolia_current_collection_id' %}</script>
6. Save changes to the layout/theme.liquid
file.
2. Update your custom front-end implementation code
First, we need to store the current collection’s ID in a JavaScript variable.
Make sure to add the following snippet to the JavaScript file that contains the InstantSearch logic:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var collectionFacetFilter = null;
var collectionRulesContextValue = null;
if (collectionPage) {
const current_collection_id_string = document.getElementById('template_algolia_current_collection_id')
.innerHTML
.replace(/^\s+|\s+$/g, '');
let current_collection_id = null;
if (!!current_collection_id_string) {
const current_collection_id_object = JSON.parse(
current_collection_id_string
);
current_collection_id = current_collection_id_object.currentCollectionID;
}
collectionFacetFilter = current_collection_id
? 'collection_ids:"' + current_collection_id + '"'
: null;
collectionRulesContextValue = current_collection_id;
}
The above snippet exposes all necessary variables to do proper products filtering and Rules activation.
Update products filtering logic
On Collection pages, InstantSearch should automatically filter on products that belongs to the given collection.
Your implementation should contain some logic to filter on a collections
attribute.
You should update this logic to filter on the collection_ids
property.
You can now use the collectionFacetFilter
variable which contains the proper filter value (e.g., collection_ids:132342342
).
The collectionFacetFilter
variable can be null
if the script is loaded on a collection page that does not exist, or a different type of page (e.g., a search results page).
Update Rules activation logic
If your front-end search implementation contains merchandising logic using Algolia Rules, you must change the ruleContexts
sent by InstantSearch to Algolia.
1. Look for for the line which manipulates ruleContexts
.
You should update the logic to use the collectionRulesContextValue
variable, which contains the proper value to use, as follows:
1
2
3
4
// if we are on a collection page, `collectionRulesContextValue` is defined
if (!!collectionRulesContextValue) {
helper.setQueryParameter('ruleContexts', [collectionRulesContextValue.toString()]);
}
To enable merchandising on your front-end search implementation, add the following snippet into the part that handles search parameters (searchFunction
):
1
2
3
4
// if we are on a collection page, `collectionRulesContextValue` is defined
if (!!collectionRulesContextValue) {
helper.setQueryParameter('ruleContexts', [collectionRulesContextValue.toString()]);
}
The collectionRulesContextValue
variable can be null
if the script is loaded on a collection page that does not exist, or a different type of page (e.g., a search results page).
Your front-end search implementation is now migrated.