Front-End Custom Events
Customizing
The extension uses libraries to help assist with the front-end implementation for autocomplete, InstantSearch, and insight features. When you approach customization, you have to understand that you are customizing the implementation itself and not the components it’s based on.
These libraries are here to help add to your customization. As the extension has already initialized these components, you should hook into the area between the extension and the libraries.
The library bundle
Depending on your extension version, you can have different versions of the autocomplete.js v0.x or InstantSearch libraries installed. Refer to the extension’s repository for more information.
Knowing the version of the extension helps you understand what’s available in these libraries for you to leverage. It also helps you find which documentation to reference when you start working on your customization.
The algoliaBundle.js
file holds the libraries for autocomplete and InstantSearch as well as their dependencies. They’re also accessible in the algoliaBundle
global and added to most of the event hooks for access.
The search-insights.js
library is a standalone for the insights features like Click and Conversion Analytics and Personalization.
Accessing front-end configurations
The extension handles the implementation of the front-end features based on your configuration in Stores > Configuration > Algolia Search. The extension stores these settings in the global JavaScript object algoliaConfig
. The data for this object returns from the block method Algolia\AlgoliaSearch\Block\Configuration::getConfiguration()
and set in the configuration.phtml
template file. The autocomplete and InstantSearch implementations refer to this global variable to build their UI.
To inspect the algoliaConfig
object, type algoliaConfig
in your browser’s console when on your Magento store.
In this object, you can see references for search credentials, index name prefix, display settings for autocomplete and InstantSearch, facets configuration, and other options related to the search UI.
This is a helpful reference to understand what’s built on the front-end and for your customizations.
How to use event hooks
The extension provides hooks to help you add or change parameters for implementation. The algolia
window variable is an object that handles the setting and firing of these events.
Try typing algolia
in your browser’s console when on your Magento store. Your console outputs an object with methods that help trigger events throughout the front-end implementation for autocomplete, InstantSearch, and insights.
There are different events available to you depending on your extension version. You can see all available events by entering algolia.allowedHooks
in your browser’s console.
It’s highly recommended that you find where these events dispatch in the extension to help understand what parameters are available to use and what to return.
When it comes to the extension, the primary use of the algolia
object is to add your callback function to the available events. To add your hook, use the algolia.registerHook
method, which accepts the parameters of eventName
and the callback function.
1
2
3
4
algolia.registerHook('beforeWidgetInitialization', function(allWidgetConfiguration, algoliaBundle) {
// add your code here
return allWidgetConfiguration;
});
The example adds the customisation at the beforeWidgetInitialization
event. The event fires from the instantsearch.js
file as:
1
allWidgetConfiguration = algolia.triggerHooks('beforeWidgetInitialization', allWidgetConfiguration, algoliaBundle);
You can see that the event has access to the variable allWidgetConfiguration
. This variable holds all the InstantSearch widgets configuration already created for you based on your extension settings. Using this event, you can return a modified allWidgetConfiguration
for the InstantSearch implementation.
Where to place your hook
You must add your hook before the event fires, by placing your JavaScript customization in your custom module or theme. It’s not recommended to edit the extension or to override the extension files unless necessary.
The extension adds JavaScript files to the head of the web page directly. You can add your file in the same manner using the front-end XML layout file.
1
2
3
4
5
6
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<head>
<script src="Your_CustomModule::custom_hooks.js" />
</head>
</page>
Or you can add your file via requireJS. Read the Magento JavaScript Developer Guide to learn more about how to call and initialize JavaScript in Magento 2.
For either option, the hook fires when the event triggers. It’s not necessary to wait for the DOM to be ready.
Debugging your hook
If your hook doesn’t fire, use your browser’s developer tools to add a break-point on the event. You can then inspect algolia.registeredHooks
to see all the registered hooks added at that point of the script.
If your hook wasn’t added by this point, run your hook earlier by moving the customization higher in the loading order, but not before common.js
. The extension requires that common.js
runs before to access the algolia
global.
Please remember that hooks don’t need to wait for DOM ready. In fact, running it after DOM ready registers the hooks after the event fires which isn’t ideal.
Autocomplete menu events
You can adjust all the logic of the autocomplete.js v0 integration by registering a custom method in your JS file.
Registering of a hook can be done by using algolia
JS object.
You can learn how to add a custom JS file in a Create a custom extension tutorial.
Possible hooks:
beforeAutocompleteSources(sources, algoliaClient, algoliaBundle)
- can be used to modify default data sources
- the hook must return
sources
variable
beforeAutocompleteOptions(options)
- can be used to modify default options of autocomplete menu
- the hook must return
options
variable
These hooks are triggered right before the autocomplete feature initializes.
Example of the hooks:
1
2
3
4
5
6
7
8
9
algolia.registerHook('beforeAutocompleteSources', function(sources, algoliaClient, algoliaBundle) {
// Add or modify sources, then return them
return sources;
});
algolia.registerHook('beforeAutocompleteOptions', function(options) {
// Modify options, then return them
return options;
});
afterAutocompleteStart(algoliaAutocompleteInstance)
- can be used to observe events on the autocomplete element
- the hook must return
algoliaAutocompleteInstance
variable
Example of the hook:
1
2
3
4
5
// Bind new event on the autocomplete element
algolia.registerHook('afterAutocompleteStart', function(algoliaAutocompleteInstance) {
// Modify default autocomplete instance, then return it
return algoliaAutocompleteInstance;
});
Instant search page events
You can adjust all the logic of the InstantSearch.js integration by registering a couple of custom hooks:
beforeInstantsearchInit(instantsearchOptions, algoliaBundle)
- can be used to modify default
instantsearch
options
- can be used to modify default
beforeWidgetInitialization(allWidgetConfiguration, algoliaBundle)
- can be used to add/remove/modify any widget(s)
beforeInstantsearchStart(search, algoliaBundle)
- can be used to modify the
instantsearch
instance before call ofstart()
method
- can be used to modify the
afterInstantsearchStart(search, algoliaBundle)
- can be used to modify the
instantsearch
instance after call ofstart()
method
- can be used to modify the
By registering these hook(s) in your JavaScript file, you can directly modify their parameters which must be returned back from the method.
Example of the beforeInstantsearchInit(instantsearchOptions, algoliaBundle)
hook:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Modify default `instantsearchOptions`
algolia.registerHook('beforeInstantsearchInit', function(instantsearchOptions, algoliaBundle) {
// Adding a custom Query Rule context
var newQueryRuleContext = 'new-custom-query-rule-context';
instantsearchOptions.searchParameters.ruleContexts.push(newQueryRuleContext);
// see other possible searchParameters: https://www.algolia.com/doc/api-reference/api-parameters/
// Example of an after search callback
var callbackSearchFunction = instantsearchOptions.searchFunction;
instantsearchOptions.searchFunction = function(helper) {
// Add your `searchFunction` methods here
// Run the previous `searchFunction`
callbackSearchFunction(helper);
}
return instantsearchOptions;
});
Example on how to add a new toggleRefinement
widget to instant search page:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
algolia.registerHook('beforeWidgetInitialization', function(allWidgetConfiguration) {
const wrapper = document.getElementById('instant-search-facets-container');
const widgetConfig = {
container: wrapper.appendChild(createISWidgetContainer('in_stock')),
attributeName: 'in_stock',
label: 'In Stock',
values: {
on: 1
},
templates: {
header: '<div class="name">Is in stock</div>'
}
};
if (typeof allWidgetConfiguration['toggle'] === 'undefined') {
allWidgetConfiguration['toggle'] = [widgetConfig];
} else {
allWidgetConfiguration['toggle'].push(widgetConfig);
}
return allWidgetConfiguration;
});
All default widgets can be found in allWidgetConfiguration
object and can be removed or modified in this method.
However, some widgets, like hits, can not be added multiple times. In that case, you should add the widget manually in beforeInstantsearchStart
:
1
2
3
4
5
6
7
8
algolia.registerHook('beforeInstantsearchStart', function (search) {
search.addWidget(
algoliaBundle.instantsearch.widgets.hits({
container: '#custom-second-hits',
})
);
return search;
});
Insights events
You can add new events for Click and Conversion Analytics and Personalization by registering this custom hook:
afterInsightsBindEvents(algoliaInsights)
- can be used to add new events
algoliaInsights
gives you access to methods for tracking:trackClick(eventData)
trackView(eventData)
trackConversion(eventData)
- to format
eventData
for insights you can use:buildEventData(eventName, objectID, indexName, position = null, queryID = null)
- Click and Conversion Analytics requires the optional parameters for
position
andqueryID
Example of a custom click event for personalization:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
algolia.registerHook('afterInsightsBindEvents', function(algoliaInsights) {
var selectors = document.querySelectorAll('.class-selector');
selectors.forEach(function (e) {
e.addEventListener('click', function (event) {
// selector in this example has an data-objectid attribute
// with the objectID as the value
var objectId = this.dataset.objectid;
// use the buildEventData function to format event data
var eventData = algoliaInsights.buildEventData(
'Clicked Event Name', // eventName
objectId, // objectID
algoliaConfig.indexName + '_products' // indexName
);
algoliaInsights.trackClick(eventData);
});
});
});
Example usages
You can use this section as inspiration for your customization. These examples are not definitive and will need to be adjusted to suit your needs.
Add category sorting in the autocomplete menu
In this example, we will create a new section that displays categories that are returned by the product results. Please read our guide on creating a custom extension to learn more about templating in this section.
In order to retrieve the categories that are returned by a product search, we need to create a new section that queries our product index. When we define the source of this section, we have to pass in the facets
parameter that returns the categories attribute which the extension indexes as: categories.level0
, categories.level2
, etc..
The search will return product results as hits, so we have to render the facets instead of products. This can be done in the template suggestions function. Make sure that your template variables match those returned by the facets in order to render them in autocomplete.
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
algolia.registerHook('beforeAutocompleteSources', function (sources, algolia_client, algoliaBundle) {
algoliaConfig.autocomplete.templates['NAME OF AUTOCOMPLETE TEMPLATE'] = algoliaBundle.Hogan.compile(jQuery('#autocomplete_catprod_template').html());
var categoryProductsSource = {
source: algoliaBundle.autocomplete.sources.hits(algolia_client.initIndex('NAME OF PRODUCT INDEX'), {
hitsPerPage: 2,
analyticsTags: 'autocomplete',
facets: ['categories.level0, categories.level2'],
maxValuesPerFacet: 3
}),
displayKey: 'value',
name: sources.length + 1,
templates: {
header: '<div class="category">Category w/ Products </div>',
empty: '<div class="aa-no-results">' + algoliaConfig.translations.noResults + '</div>',
suggestion: function (hit, payload) {
hit.__queryID = payload.queryID;
hit.__position = payload.hits.indexOf(hit) + 1;
var facets = (hit.facets);
if (hit.facets) {
algoliaConfig.autocomplete.templates['NAME OF AUTOCOMPLETE TEMPLATE'].render(hit.facets);
}
}
}
};
sources.push(categoryProductsSource);
return sources;
});
Add filters to your autocomplete search
To add a new filter, we have to modify the section’s search parameters to include the new filter condition. To do this, we have to recreate the source with our updated options.
In the example below, we are appending a new numericFilter
to the search parameters, in_stock=1
. Because we are recreating the source value, we need to recreate the options passed into the product source as defined in common.js
. In the numericFilters
, we will return an array of conditions that will include our new condition.
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
algolia.registerHook('beforeAutocompleteSources', function(sources, algoliaClient, algoliaBundle) {
for (i = 0; i < sources.length; i++) {
if (sources[i].name == 'products') {
var productSection = algoliaConfig.autocomplete.sections.filter(function(section) {
if (section.name == 'products') {
return section;
}
});
// recreate the options from common.js
var productOptions = {
hitsPerPage: productSection[0].hitsPerPage,
analyticsTags: 'autocomplete',
clickAnalytics: true,
facets: ['categories.level0'],
numericFilters: ['visibility_search=1', 'in_stock=1'] // add filter options
};
sources[i].source = algoliaBundle.autocomplete.sources.hits(algolia_client.initIndex(algoliaConfig.indexName + '_products'), productOptions);
}
}
return sources;
});
Add custom rules context to InstantSearch
If you have to add new contexts for some of your custom rules, please use the specified front-end hook for your InstantSearch version. We recommend you to append your new context to the preconfigured ones added by the extension, as follows:
1
2
3
4
5
6
7
algolia.registerHook('beforeInstantsearchInit', function(instantsearchOptions, algoliaBundle) {
// Adding a custom Query Rule context
var newQueryRuleContext = 'new-custom-query-rule-context';
instantsearchOptions.searchParameters.ruleContexts.push(newQueryRuleContext);
return instantsearchOptions;
});
Sort facet values with InstantSearch
By default, facet values displayed for a search are sorted by the number of matching results. However, this can be changed. In the following example, we will sort the size facet values based on the size
values. To do this, we need to hook into the event beforeWidgetInitialization
to modify the facet for size.
To sort the values in the refinementList
based on size, we are going to add the sortBy
function to our refinementList
. This function will allow you to match values against each other and sort each value.
To determine the order, we will use the orderedSizes
array variable defined in the code below. This can be pulled from any source, like Magento or from another index if needed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
algolia.registerHook('beforeWidgetInitialization', function(allWidgetConfiguration, algoliaBundle) {
var orderedSizes = ['XXS', 'XS', 'S', 'M', 'L', 'XL', 'XXL'];
for (var i = 0; i < allWidgetConfiguration.refinementList.length; i++) {
if (allWidgetConfiguration.refinementList[i].attribute == 'size') {
allWidgetConfiguration.refinementList[i].sortBy = function(a, b) {
return orderedSizes.indexOf(a.name) - orderedSizes.indexOf(b.name);
};
}
}
return allWidgetConfiguration;
});