Overlay 1.4 Instrumentation Guide
Introduction
Here you are, eager to start working on a new Overlay 1.4 instrumentation to enhance the user experience of your search and increase your brand awareness. Don't worry, we promise the process will be as simple as using the Laplace transform to derive the complex impedance for a capacitor. Just kidding, you will just need to know HTML, CSS, JavaScript and (some) AngularJS.
Right now you are wondering what to do with the Archetype we provided. This project actually contains the base structure of a custom Overlay instance that you can get to work with your catalog with just a few simple steps.
Installing development dependencies
If you already explored the Archetype structure and tried to figure out how to run it, you probably noticed that there is a lot still missing. Be patient, in the next sections we will manage to get it up and running!
First of all, you will need to install the following development dependencies:
Download the Node.js installer from its website or install it using your favorite package manager
Open a terminal and browse to the project's root directory
Run
npm install -g bower
to install Bower globallyRun
npm install -g grunt-cli
to install Grunt globallyRun
npm install
to install all other dev dependencies
Installing front end dependencies
The Overlay relies on a number of Bower dependencies, namely AngularJS and a few other front end modules. To install them, just run bower install
in the project's root directory. This will create the libs
directory with everything you need.
Running the project
Once all dependencies have been installed, run grunt
to launch a local web server listening on port 9999
: http://localhost:9999/overlay.html
What you can see on your screen is a fully functional search interface, ready to be customized. The Overlay provides a basic structure while being agnostic in terms of style. This way, adapting the interface to the look and feel of your brand is a faster and simpler process.
The Grunt process also watches for changes in src
, so that the page is reloaded automagically every time the sources are modified.
Note: The port number can be changed in Gruntfile.js
if necessary.
Connecting the Overlay to your Search API instance
Now try to perform a search. You can also apply some filters and see the power of motion.
"But, hey, those are not my products! 😱"
By default, the Archetype is connected to our demo Search API instance. If you want the Overlay to start consuming your catalog, you will need to replace the instance ID in the configuration snippet that lives in the overlay.html
file.
Before | After |
---|---|
|
|
We will talk about this configuration snippet later so you can fully understand its purpose and the range of options that are available.
Note: If you don't know what your instance ID is, just drop us an email!
The overlay.html file
The overlay.html
file (the one in the root directory of the project, do not mix up with src/structure/overlay.html
) is used by EmpathyBroker to simulate the actual client's website during the development. This page does not have any effect in the integration and is only meant to mock the client's index.html
file for testing purposes. This way we can test the initialization of the Overlay and its different configurations.
If you are a client, an agency or an integrator with access to the staging site, we suggest to incorporate the integration code right into the site instead of using the overlay.html
file. Jump to Integrating the Overlay into your website if this is the case.
Simulating the staging site
The overlay.html
file plays an important role, not only for testing the integration, but also because it is possible to add to this file the HTML and CSS needed to simulate all the necessary parts of the real website for the Overlay instrumentation.
As an example of this, we can add the client's header to this page, and configure the Overlay to use it instead of its own:
<!-- ↑ Rest of the HTML head ↑ --> <style> <!-- Styles needed to simulate the header --> </style> </head> <body> <!-- Simulating the client’s header (example) --> <div id="menu"> <form id="searchForm" action="/en/Catalog/Search/" method="get"> <input id="search" placeholder="Search" type="search"/> </form> </div> <!-- / Simulating the client’s header (example) --> <script src="https://preassets.empathybroker.com/overlay/1.4/js/eb.base.js"></script> <script src="js/eb.custom.js"></script> <!-- ↓ Initialization snippet and rest of the body ↓ -->
Project Structure
The Archetype, the project used as a template for every new Overlay’s instrumentation, is based on the base Overlay project, but it doesn't contain all the files and directories.
The base project is organized in several directories and files. Next they are explained briefly. The elements marked with this
color are in the Archetype project too. Let’s take a look at the purpose of each one:
src/
This is the root folder of the Overlay’s source code. All code is contained here.bootstrap/
Here there are some files with the definition of the application and the way to bootstrap it. This folder is not included in the Archetype and it will be rarely needed.append-overlay.js
In this file is the JavaScript code that creates and inserts the root DOM element that contains the whole Overlay.bootstrap.js
This file contains the definition of the global objectEmpathyOverlay
, which contains functions to setup and bootstrap the Overlay.ebApp.js
Here is the definition of the AngularJS application calledebApp
and the imports of all modules that it uses.styles.less
This is the main file that is compiled with less. It imports all the .less files along the Overlay. From this file is generated all the CSS in the base project.
Note: In the Archetype the main .less file that generates all custom CSS is located in thesrc
directory.
commons/
In this directory live some styling resources used as default in the Overlay.icons/
Some default icons made with HTML and CSS. These are not included in the Archetype, because they are already imported from the base project.styles/
Less resources like global variables, functions, mixins and responsive helpers.wrappers/
JavaScript wrappers used to isolate the scope.
components/
All the AngularJS components used in the application. Each component is inside its own directory and usually is distributed in multiple files: HTML template, JavaScript with the definition of the component and one or more .less files with the styles.
In the Archetype this directory contains a sample of how to override a component (result). This sample can be deleted if it is not necessary. To know more about this see the section Overriding a component.controllers/
This directory contain the main controller of the application. The role of controllers is to detect events and trigger business logic to update data.
This directory is not included in the Archetype, because the controller is not usually overridden. But if you need to override it take a look at Overriding the main controller.filters/
Here are the AngularJS filters used in the application. A filter transforms the data that it receives, with some kind of custom logic, and returns the transformed value.i18n/
In this directory are all the files that contain the messages translations.
The Archetype does not include this directory but it is possible (and recommended) to create the same directory to include the custom translations. For more information about this see Internationalization (i18n).images/
Some images used in the Overlay like the default EmpathyBroker logo. This logo is included in the same directory in the Archetype.providers/
Here there are the files that manage all the configurations of the application. To know more about these see the section Configuration parameters.baseConfig.js
This is the default Overlay public configuration.config.js
Provider to override default configuration. This is the provider that must be used in the client instrumentation and is included in the Archetype. This configuration can be also overridden from the initialization snippet, so the client can change any of them.base-features.js
Here are the default features. Most of them are disabled by default. To enable them use the next file.features.js
Provider to override default features. Unlike theconfig.js
this configuration can not be overridden by the client from the snippet.
services/
Here are the services of the project that provide functionality to the Overlay. They are organized in different directories.
In the Archetype this directory exists but it only contains a sample of how to override a service (api/search
). This sample can be deleted if the service is not going to be extended. To know more about this see Overriding a service.api/
The services that manage the data layer.data/
The services that are used to communicate with the backend API services.helpers/
Services to provide common functionality functions to the others services and components.
structure/
This directory contains all the HTML documents that define the template to organize the components in the view. Also contains the .less files containing all CSS related with this structure. Even each component has its own .less files, this .less files have the purpose of styling the HTML of the structure and containers of the components.
In the Archetype this directory contains a sample of how to override styles of the structure (header.less
). This can be deleted if it is not necessary to override. It is possible also to override the HTML structure, to know more about see Overriding the HTML Structure.
test/
The tests directory. It contains all the tests to check the right behavior of the application.bower.json
A manifest file where Bower keeps track of the front dependencies. If you need to add a new dependency, take a look at this section.Gruntfile.js
Contains the configuration for Grunt tasks.Jenkinsfile
Contains the configuration to deploy with Jenkins.karma.conf.js
Karma needs to know about your project in order to test it and this is done via this configuration file.package.json
Developing dependencies managed by npm.
Architecture
Next, there is a diagram of the architecture of the base project. As you can see, the system has a layered architecture, with helpers and configurations shared among all the layers.
The custom project replicates the base project architecture but only contains the elements to be overridden. In the previous section you can see all directories and files that live in the base and custom projects. Later on this guide you will also see how to override the base behavior in the custom project.
From top to bottom, the first layer is the HTML structure. This layer contains only HTML and CSS files that make use of the components of the second layer.
The third layer is the data layer, where the information received from the backend services is stored.
The last layer in the bottom is the api layer that communicates with the backend services. This is the only point of contact with the backend services.
The components are arranged along the structure HTML files, displaying the information and receiving events from the user. These components access to the data services to retrieve data every time it is updated. This data services are updated by the api services.
So the components layer and the api layer are communicated only through the data services. The EmpathyBrokerController
notifies the api services, through the MainTrigger
helper, whenever the ebState
changes.
All the rest of helpers, config and features are accessed from all the layers, because they contain common functionalities.
Execution Flow
The system works around the data containers (ebState
, ebSearch
and ebLinks
). The execution flow is the following:
The user types a query or filters the results.
The components set the query and/or the filters in the
ebState
.The
EmpathyBrokerController
detects the change in the state and notifies theMainTrigger
helper.The
MainTrigger
makes the api services do a request to the backend services.The api services receive the response from the backend services and set the data into the data containers
ebSearch
andebLinks
.The components realize the data has changed and display the new data.
Configuration parameters
The Overlay accepts two types of configurations: config and features. The former contains those parameters that can be configured dynamically (i.e. in the configuration snippet). The latter consists of configurations that are not supposed to vary in runtime in any situation.
Config
The config parameters model follows a 3-tiered architecture. Each level can override any configuration of the levels above.
Base configs (
providers/base-config.js
): these contain the default values for all configurations, see the tables below.Custom configs (
providers/config.js
): this can be used to set configurations that are not going to change in your instance. Placing them here helps reduce the size of the snippet in the HTML file.Snippet configs: these configurations are sacred, taking a higher priority than any other. These are set in the first parameter of the Overlay initialization, either in the
overlay.html
file (for testing purposes) or in the integration snippet.
actionCallbacks | |
---|---|
Default | undefined |
Type | Object |
An object containing the custom functions for tracking actions callbacks. The name of the properties must be the action name ( actionCallbacks: { OR actionCallbacks: { |
cookieSessionId | |
---|---|
Default |
|
Type |
|
Name of the cookie where the session id is stored. |
cookieUserId | |
---|---|
Default |
|
Type |
|
Name of the cookie where the user id is stored. |
debug | |
---|---|
Default |
|
Type |
|
Used to log debugging information to the console and enable AngularJS scopes inspecting. |
debugProductPage | |
---|---|
Default |
|
Type |
|
If true and debug is enabled, fake |
defaultCurrency | |
---|---|
Default |
|
Type |
|
Currency for the search results. The value is must be in the ISO 4217 format. |
defaultImage | |
---|---|
Default |
|
Type |
|
The URL of the default image to be inserted when a product image is not available. When not provided, our default image is used. |
disableTracking | |
---|---|
Default |
|
Type |
|
Disable all tracking events. |
displayLang | |
---|---|
Default |
|
Type |
|
Search results display language (tags and labels). The value must be in the ISO 639-1 format. |
domA2CTrigger | |
---|---|
Default |
|
Type |
|
Selector of the 'add to cart button' in the product page. If add to cart tracking in product page is enabled, a handler is attached to the elements that match with this selector |
domHeader | |
---|---|
Default |
|
Type |
|
The selector of the element under which you want to place the overlay in the desktop version. This keeps the header element visible while performing the search. If your site has multiple headers, feel free to provide as many as you want in this selector and we will pick the one that is visible. Take a look at Client header layout to see how to use it. |
domInput | |
---|---|
Default |
|
Type |
|
Selector of the input that will contain the query. In case your site search uses multiple inputs, you can provide either a class or concatenate multiple selectors using a comma. |
domTrigger | |
---|---|
Default |
|
Type |
|
Selector of the element that will open the overlay. The event that will be used to open can be configured with the feature |
endpoints | |
---|---|
Default | { |
Type |
|
Configuration of the API Endpoints (take a look at Endpoints configuration). By default only the host and the version is configured. The instance name must be configured anytime overriding this property in the snippet. |
lang | |
---|---|
Default |
|
Type |
|
The search results language (catalog data, links, top clicked, etc.). The value must be in the ISO 639-1 format. |
lazyLoading | |
---|---|
Default | undefined |
Type | Object |
If this configuration is present, it enables lazy loading the Overlay. The lazy loading makes the initialization of the Overlay occur when the user starts to search and not when the page loads. As AngularJS is not still initialized at this point, the configuration must have three properties: lazyLoading: { |
loadingAnimation | |
---|---|
Default |
|
Type |
|
The URL of your loading animation. When not provided, our default animation is used. |
pageRows | |
---|---|
Default |
|
Type |
|
Number of results to be displayed per page. |
queryOrigin | |
---|---|
Default | { |
Type |
|
This parameter can be used to override the default values of the query origin sent in the track query event. |
scope | |
---|---|
Default |
|
Type |
|
Device / App where the search is going to be performed, e.g. |
urlHandler | |
---|---|
Default | { |
Type | Object |
This parameter can be used to override the default URL query string parameter names. This should only be used when any of those parameters clashes with your URL usage. |
translations | |
---|---|
Default |
|
Type |
|
This configuration can be used to override or add new messages translations. Take a look at Adding Translations Dynamically to know more. |
Features
Features, as configs, also follow a tiered architecture. In this case, parameters cannot be overriden from the snippet, so it only has 2 layers.
Base features (
providers/base-features.js
): these contain the default values for all features, see the table below.Custom features (
providers/features.js
): this can be used to set custom features on a custom Overlay instance, overriding the default values.
a2cButtonInResult | |||
---|---|---|---|
Default |
| ||
Type |
| ||
Enable the add to cart button for each result in the results grid. |
a2cProductPage | |||
---|---|---|---|
Add to cart on product page configuration. | |||
a2cProductPage.enabled | |||
Default |
| ||
Type |
| ||
Enable add to cart tracking on product page. | |||
a2cProductPage.urlStrategy | |||
Default |
| Options |
|
Type |
| ||
As it is necessary to pass the product id to the product page, this configuration indicates how to pass it in the URL. There are two possibilities: in the hash of the URL, or as a param in the query string. | |||
a2cProductPage.urlParam | |||
Default |
| ||
Type |
| ||
The name of the param in case | |||
a2cProductPage.rightClickTTL | |||
Default |
| ||
Type |
| ||
As it is not possible to know if the user is opening the product page when right-clicks on a product, we store the product data in the |
bodyFacetsCategoriesSystem | |
---|---|
Default |
|
Type |
|
Config for categories system. This is used to identify the category facet, since it is handled in a different way than the other facets. The value must be the name of the category facet. |
bodyFacetsCheck | |
---|---|
Default |
|
Type |
|
If this config is true, each facet value will be render with a checkbox. |
bodyFacetsClear | |
---|---|
Default |
|
Type |
|
Show control to clear all selected values in a facet. |
bodyFacetsLimit | |
---|---|
Default | 6 |
Type | Number |
Number of facets to show when the |
bodyFacetsOpenClose | |
---|---|
Default | false |
Type | Boolean |
This config makes facets collapsable. |
bodyFacetsOpenCloseStartOpened | |
---|---|
Default |
|
Type |
|
If the |
bodyFacetsOverlap | |
---|---|
Default |
|
Type |
|
If the value is |
bodyFacetsPriceFilter | |
---|---|
Default |
|
Type |
|
Name of the effective price facet. |
bodyFacetsPriceRange | |
---|---|
Default |
|
Type |
|
Show price facet as a range slider. |
bodyFacetsResponsiveOverlap | |
---|---|
Default |
|
Type |
|
Open a layer with facets from left when device is tablet/mobile. |
bodyFacetsSeeMoreSystem | |
---|---|
Default |
|
Type |
|
This configures the way in how the list of the facets’ values are shown. | |
Options |
bodyResultsMotion | |
---|---|
Default |
|
Type |
|
Use motion service in all result grids. |
bodyResultsScrollToTop | |
---|---|
Default |
|
Type |
|
Enables scroll to top button. |
bodyTemplate | |||
---|---|---|---|
Default |
| Options |
|
Type |
| ||
Template to place facets and results blocks. There two possible configurations: in columns, the facets on the left and the results on the right by default; or in rows, the facets on the top and the results below by default. |
bodyTopClicked | |
---|---|
Default |
|
Type |
|
Show top clicked products on the no results page. |
categoryFilterMultiSelect | |
---|---|
Default |
|
Type |
|
Makes the category facet multi-selectable when |
components.empathize | |||
---|---|---|---|
Configuration for the Empathize component. | |||
components.empathize.enabled | |||
Default |
| ||
Type |
| ||
Enable the Empathize. | |||
components.empathize.realTime | |||
Default |
| ||
Type |
| ||
Provide immediate results in the Empathize (if false, wait for terms returned by the Search API call, EB only). | |||
components.empathize.limit | |||
Default |
| ||
Type |
| ||
Maximum number of terms shown in the Empathize. | |||
components.empathize.timeout | |||
Default |
| ||
Type |
| ||
Timeout to prevent sending every single request for fast typers (should be small so it does not harm UX). | |||
components.empathize.openOnLoad | |||
Default |
| ||
Type |
| ||
Open Empathize on page load if query is not empty. |
components.facets | |||
---|---|---|---|
Configuration for the facets component. | |||
components.facets.filter | |||
Default |
| ||
Type |
| ||
This array must contain the names of the facets to be filtered and not displayed. |
components.promoted | |||
---|---|---|---|
Configuration for the promoted links component. | |||
components.promoted.insideResults | |||
Default |
| ||
Type |
| ||
Include promoted links within the results grid. |
components.sort | |||
---|---|---|---|
Configuration for the sort component. | |||
components.sort.type | |||
Default |
| Options |
|
Type |
| ||
The way the sort control is shown: as a list or as a dropdown select. | |||
components.sort.values | |||
Default |
| ||
Type |
| ||
Sorting options to be displayed. See section Adding sorting options for more details on the format. |
components.suggestions | |||
---|---|---|---|
Configuration for the suggestions component. | |||
components.suggestions.showWithResults | |||
Default |
| ||
Type |
| ||
Show suggestions when results and suggestions are returned by search. If no results are returned, suggestions are always shown, regardless of this flag. |
debounceTime | |
---|---|
Default |
|
Type |
|
The delay time (in milliseconds) to launch the query search between user’s keystrokes. |
domBody | |
---|---|
Default |
|
Type |
|
The selector of the client's page body. This will be used to set fixed positioning and hidden overflow to the body when the Overlay is open. |
domOverlay | |
---|---|
Default |
|
Type |
|
The selector of the Overlay's root element in the DOM. Is used to place and size the Overlay over the client web. This value doesn’t need to be modified usually. |
eventTrigger | |||
---|---|---|---|
Default |
| Options |
|
Type |
| ||
Event to handle how the Overlay is opened. |
filterType | |
---|---|
Default |
|
Type |
|
Makes facets multi-selectable, applying | |
Options |
headerResponsiveTwoRows | |
---|---|
Default |
|
Type |
|
If true, the header is placed in two rows (input under the logo and with full width) when the device is mobile. |
history | |
---|---|
Configuration for the query history stored in localStorage. | |
history.scopesBlacklist | |
Default |
|
Type |
|
Array of scopes where history must be disabled. History will be disabled when the scope configured in the | |
history.storageKey | |
Default |
|
Type |
|
The local storage key to store the history. This key will be prefixed with the prefix configured in the storage helper ( | |
history.maxSize | |
Default |
|
Type |
|
Maximum number of elements in the stored history. |
infiniteScrollOffset | |
---|---|
Default | 800 |
Type |
|
Offset in pixels (starting from the bottom) to load the next page in the infinite scroll component. |
instantSearchScopesBlacklist | |
---|---|
Default |
|
Type |
|
Array of scopes where instant search must be disabled. The instant search will be disabled when the scope configured in the |
needHash | |
---|---|
Default |
|
Type |
|
Use hash on url to save params instead of the query string. With this flag on, Overlay params such as |
orderFacetValuesBySelected | |
---|---|
Default |
|
Type |
|
If this feature is on, the facet values are sorted by selected first and then by number of results. If it is false, the facet values are sorted only by number of results. |
priceStatsProperty | |
---|---|
Default |
|
Type |
|
Name of the field containing the price stats that is returned by the Search API. |
priceStatsShowCurrency | |
---|---|
Default |
|
Type |
|
This configuration shows the currency in the price slider. |
showClientHeader | |
---|---|
Default |
|
Type |
|
If the client header is used on top, the Overlay has to be opened under it. |
showFacets | |
---|---|
Default |
|
Type |
|
Whether to show the facets. |
showSelectedFilters | |
---|---|
Default |
|
Type |
|
Show currently selected filters in a list |
showSort | |
---|---|
Default |
|
Type |
|
Whether to show the sort component. |
trackQueryDebounce | |
---|---|
Debounce to be applied to query tracking events in order to prevent noise generated by partial queries. | |
trackQueryDebounce.enabled | |
Default |
|
Type |
|
Enable query debounce. | |
trackQueryDebounce.timeout | |
Default |
|
Type |
|
Debounce duration in milliseconds. |
icons | ||
---|---|---|
This object contains the configurations of the default icons used in the Overlay. | ||
Icon | Options | Default |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Common layouts
Some of the most common layouts can be achieved through configurations, without the need of writing crazy amounts of CSS.
Horizontal facets
Feature | Value | Description |
---|---|---|
|
| To place the facets over the results |
|
| To make the facets overlap the results |
|
| To make the facets able to open and close on click |
|
| To show all the filters inside each facet |
CSS | Description |
---|---|
.eb-nav-facet { | By default each facet has the 100% of width. So to place them horizontally it is necessary to set a width to fit. |
Client header
Feature | Value | Description |
---|---|---|
|
| Makes the Overlay to place below the client header |
|
| This makes the Overlay open when the user types in the search box in the client header |
Config | Value | Description |
---|---|---|
|
| It is necessary to set the selector of the client header in the config |
|
| It is necessary to set the selector of the client search box |
|
| It is necessary to set the selector of the client search box or the search button to open the overlay when the event configured in the feature |
Endpoints configuration
To make easier to configure of all endpoints the Overlay uses, this service takes charge of composing the appropriate URL for each endpoint. The service uses the endpoints object of the config to arrange the different endpoints. This object can contains the configuration for each endpoint, and also common configuration for all endpoints.
Endpoints
The endpoints that this service offers are the following. The key is used to obtain or to override the configuration for a particular endpoint:
Key | Description |
---|---|
| To retrieve search results and links |
| To retrieve search results, links and Empathize terms |
| To retrieve just the links |
| To track the conversion event (backwards compatibility) |
| To track the add to cart event |
| To track the add to wish list event |
| To track the click on result event |
| To track the query event |
| To retrieve Empathize terms (backwards compatibility) |
empathize | To retrieve Empathize terms |
| To retrieve top clicked results |
Endpoint parameters
The URL of each endpoint is composed using the different parameters, configurable by endpoint. The URL is generating concatenating the params in this way:
host + / + service + / + version + / + method
Param | Description |
---|---|
| The name of the instance (the customer name) used to replace the |
| The host used in the URL. By default it is |
| The version used in the URL |
| The part in the URL that identifies the service used |
| The part in the URL that identifies the final method used |
| This parameter, if exists, overrides the full endpoint, ignoring the rest of parameters |
These parameters can contains the following expressions that will be replaced:
Expression | Replaced by |
---|---|
| The name of the instance |
| The name of the environment, or empty for the Live environment |
For instance the method of the click endpoint is track/{instance}/click
, and if the instance name is demo
then the result will be track/demo/click
.
If the url
parameter exists then it overrides the whole URL, ignoring the rest of parameters. This is used when a particular endpoint has a completely different structure of the usual. This parameter can also contains the expressions {instance}
and {env}
.
Common parameters
Some of the parameters can be configured for all the endpoints at the same time. These parameters, Instead of being inside a particular endpoint, are in the root endpoints object. If the same parameters is configured as common and inside an endpoint at the same time, this last overrides the first. Host and version parameters have default values configured in the base config.
Params | Default value | Notes |
---|---|---|
|
| This param must be configured in the snippet |
|
| By default Live environment is used |
|
| |
|
| |
format | 'ebFormat' | This param decides whether to use old or new endpoint formats (e.g. autocomplete vs empathize ). It can take ebFormat and apiFormat as values; it should be changed to apiFormat in all new clients to meet the new endpoints standard |
Configuration sample
Next, a sample of the configuration and the resulting endpoints:
Snippet Config | Endpoints |
---|---|
endpoints: { | https://api-staging.empathybroker.com/search/v1/query/demo/search |
https://api-staging.empathybroker.com/searchlinks/v2/query/demo/links | |
https://ebdemo2.empathybroker.com/searchlinks/v1/query/demo2/empathize | |
https://presbdemo3.empathybroker.com/ebdemo3/services/topclicked?lang=es_ES |
Adding sorting options
By default, the only sorting option we provide is by Relevance. If you wish to enable a different field as sortable (such as price or top sellers), let us know and we will modify your index schema to incorporate this change.
Then you will need to add this new option to the CUSTOM_FEATURES
object in the src/providers/features.js
file:
components: { sort: { values: [ { id: 'RELEVANCE', value: '' }, { id: 'PRICE_ASC', value: 'price asc' }, { id: 'PRICE_DESC', value: 'price desc' } ] } }
Each value is an object with these two properties:
id: unique identifier for the sort value, this is used as a tag for i18n
value: sort value to be sent to the API (attribute name + asc or desc)
We will provide the list of sortable attributes at the beginning of the instrumentation, based on your needs.
Removing filters
By default, the Overlay displays every single facet that is returned by the Search endpoint. We understand this is not always the desired behavior, so you can disable as many as you want in the src/providers/features.js
file. All facets within the filter
array will be ignored by the Overlay.
For optimal performance, we recommend disabling the facet in the backend so it is not even calculated and returned.
components: { facets: { filter: ['size_facet'] } }
Internationalization (i18n)
Whether you are unhappy with the current translations or want to add a new language, the process is very similar:
First create a directory for the custom translations in src
(we suggest using i18n
as a naming convention). Then create a JavaScript file for each language that you wish to add or override. The content of the file should follow this structure:
angular.module('ebApp').config(['$translateProvider', function($translateProvider) { 'use strict'; $translateProvider.translations('en', { SEARCHBOX_PLACEHOLDER: 'What are you looking for?', RESULTS_FOUND: '{{results}} results', RESULTS_NORESULTS: 'Sorry, we could not find results for "{{query}}"', ... }); }]);
The translations
function accepts two parameters. The first one is an identifier (of the language). We use ISO 639-1 format for this one. The second parameter is an object where the keys are the tags and the values are the corresponding translations. You can check the full list of languages and translations in the base Overlay project (src/i18n
).
Note: this object is merged with the original translations, so it only needs to contain the tags to override.
If you want to know more about the translation system (e.g. in order to add a new tag and link it in a template), this is the module we use.
Currency
Currency internationalization is managed by the ebCurrency
filter. This filter takes the the current value of the ebConfig.defaultCurrency
configuration and then formats the value accordingly.
If you need to customize the behavior of this filter, you can create a new one with the same name and it will override the default filter.
Adding Translations Dynamically
Sometimes it is necessary to change a translated message dynamically, for instance the slogan in the sales period. To allow the client to change this it is possible to override a message for a particular language from the snippet.
To achieve this, add a new property (object) named translations
to the config
object of the snippet. This object must contain a property for each language that you want to override. For each language, set an object containing all the translations keys with their messages. This will override the translations regardless the translations in the i18n files.
EmpathyOverlay.config({ ... translations:{ en: { SEARCHBOX_PLACEHOLDER: 'Let's find something awesome!' }, es: { SEARCHBOX_PLACEHOLDER: '¡Busquemos algo genial!' }, } });
Accessibility
We often fail to realize that, with a few uncomplicated changes to our code, we can make the life of disabled people and keyboard users much easier.
The base Overlay complies with the level AA of WCAG 2.0 and makes use of WAI-ARIA labels to improve accessibility. This is an effort that must be maintained during the instrumentation phase, as a lot of templates are usually overriden and the custom Overlay can eventually lose this capability.
Accessibility is a broad field that evolves really fast, so we recommend to check the state of the art before each new instrumentation to be up to date on the newest standards.
Here is a list of resources you might find of help on this crusade:
- WCAG 2.0 Reference: https://www.w3.org/TR/WCAG20/
- WCAG Checklist: https://www.wuhcag.com/wcag-checklist/
- WAI-ARIA Overview: https://www.w3.org/WAI/intro/aria
- Accessible SVGs: https://css-tricks.com/accessible-svgs/
We also recommend to use this Chrome extension to spot some of the most common accessibility issues (you will need to enable Experimental Extension APIs at chrome://flags/).
Overriding the HTML Structure
The src/structure
directory in the base project contains all the HTML and CSS files that define the template to lay out the components in the view.
This structure acts as a container for the components and determines where they are positioned and how they are displayed. But remember that the components have their own styles files in their respective directories, so to style them, use those files instead.
The files are organized in multiple directories that represent the hierarchical structure of the Overlay’s view. So you can see the header which contains the logo, the search box and the close button; the side bar, that includes the facets and filters; the utils bar with the total results and the sort controller; and the main, with the results grid. Al templates can be overridden and the components that contain moved to another.
In the custom project it is possible to override this HTML structure. Also it is possible to override the default styles as you can see in the next section. But first you should understand how the build process manages the HTML templates.
When the project is built with the grunt
tool, all HTML templates, including components, are compiled into JavaScript files as a tuple of key and HTML code. And the keys used to store them are their path inside the project. Then when AngularJS needs a template, it looks for it by its key (the path).
So when a new template is compiled, if it has the same path as an existing template, it overrides the previous. Then to override a template in the custom project, just create the new template in the exactly the same path, either in the components directories or in the src/structure
.
Overriding the default styles
To override the styles of the Overlay, you can either use Less (totally recommendable) or CSS. By default the Overlay works with Less – and you will need to create .less
files, but don't panic, Less is a superset of CSS, so you can still write plain old CSS in these files.
The Overlay is prepared to work right out of the box if you use the src/styles.less
file that is provided in the Archetype. In this file, you can write all the styles or import other Less files to better organize the styles of your components:
@import 'components/result/result';
This way, you can place your custom component styles in their respective directories.
Overriding a component
If you need to override the behavior or the look and feel of a component, the process is simpler than ever!
Behavior
The Overlay is architected in a way that the behavior of every component can be overriden (or extended) with just an extension function.
The first step is to create a JS file in src/components/<COMPONENT_NAME>
. For instance, if you wish to modify the behavior of the result component, you can create a file with a name like src/components/result/result-extension.js
. In this file, you will need to create a named callback function, which will be called after the component has been initialized. This callback function receives the component controller as the first parameter; this enables its modification and the addition of new functionality.
If you need to use any AngularJS dependency within this function, as this code is outside of the application scope, you will need to get the AngularJS dependency injector in the way that is shown below.
Then you will need to export the variable that contains the function so it can be used in other files (see the next step).
/* exported ebResultExtension */ /* global document */ var ebResultExtension = function(controller) { 'use strict'; // Get the AngularJS injector to obtain dependencies var $injector = angular.element(document.querySelector('#eb-overlay')).injector(); // Your dependencies (example) var $rootScope = $injector.get('$rootScope'); var ebState = $injector.get('ebState'); // Your awesome extension (example) controller.myFunction = function() { return 'I love the new Overlay!'; }; // Event management is always done using $rootScope // ($scope cannot be injected using this method) $rootScope.$on('example-event', function() { // Event handling code }); };
Finally, you will need to import the variable in providers/features.js
(using the global
keyword from jshint
) and add it to the extend.components
object. This is the way the Overlay knows what extension function should be run for each component. Within this object, the key is the name of the AngularJS component in the base Overlay (ebResult
) and the value is the name of the extension function (ebResultExtension
).
/* global ebResultExtension */ var CUSTOM_FEATURES = { extend: { components: { ebResult: ebResultExtension } } };
When adding new behavior to an existing function, it is a good practice to call the parent implementation instead of copy-pasting its code. This way, if we modify how that function works in the base Overlay (e.g. to fix a bug), the custom Overlay would also benefit from those changes. The way to do this is the following:
// Create a variable referencing the base Overlay function var baseFunction = controller.myFunction; // Now you can override the function and the reference above will not be affected controller.myFunction = function() { baseFunction(); // We call the base function as is (instead of copy-pasting its code here) // Custom code here } // You can also add new parameters to this function on top of the base function's parameters controller.myFunction = function(baseFunctionParam1, baseFunctionParam2, customFunctionParam) { baseFunction(baseFunctionParam1, baseFunctionParam2); // In this example, baseFunction has two parameters, which should be also included in the extension signature (in the same order) // Custom code here, making use of customFunctionParam }
Note: This can also be applied to services and main controller functions, and only when you don't need to modify existing code for the overriden function.
Template
To override a template, just create a file with the same name following the same directory structure. e.g. in order to override the result template, you will need to create src/components/result/result.html
.
When the project is built, all templates are transformed into JavaScript code and these new templates override the parent templates, so respecting the original structure is required.
Note: We encourage to take accessibility into account when overriding a template. For more information about this see the Accessibility section.
Overriding a service
Overriding a service is very similar to overriding a component.
First, you will also need to create an extension function. In this case, the function receives the service object as the first parameter, so it can be modified like a component. But, unlike a component, a service has public functions to provide its functionality. Usually the public functions are reference to private functions, so modifying these, the public are also modified. But if it is necessary to add a new function to the public then the extension function must return an object with the new functions. This object will be merged with the public object returned by the service's factory so the new functions are available for the rest of the components and services.
This is easier to grasp with an example:
/* exported ebSearchServiceExtension */ /* global document */ var ebSearchServiceExtension = function(service) { 'use strict'; // Get the AngularJS injector to obtain dependencies var $injector = angular.element(document.querySelector('#eb-overlay')).injector(); // Your dependencies (example) var ebConfig = $injector.get('ebConfig'); var ebFeatures = $injector.get('ebFeatures'); // Your awesome extension (example) service.mapResults = function(response) { angular.forEach(response.placements[0].docs || [], function(item) { angular.extend(item, { image: item.imageId, hoverImage: item.AlternateImageUrl, price: item.priceCents / 100, salePrice: item.salePriceCents / 100, a2cParams: response.placements[0].addtoCartParams, url: item.clickUrl + '&redirect=true' }); }); }; // New method that we want to add as public service.newMethod = function(parameter) { parameter.stringField = '10'; parameter.intField = parseInt(parameter.stringField); return parameter; } // The new public method has to be added to the service by returning an object return { newMethod: service.newMethod } };
Finally, you will need to configure the extension in providers/features.js
just as we did with the component. In this case, the key and value pair needs to be added to the extend.services
object instead.
/* global ebSearchServiceExtension */ var CUSTOM_FEATURES = { extend: { services: { ebSearchService: ebSearchServiceExtension } } };
Overriding the main controller
If you need to override the main Controller, to perform tasks such as handling global events or managing elements that are out of the application scope (e.g. the client input or header), you will need to follow these steps:
Create a function named
setController
in thewindow.EmpathyOverlay
object. This function receives the Overlay element as the first parameter, and its aim is to set the new controller name using thedata-ng-controller
directive.Create a child controller with the name defined above. This controller should extend the parent controller making use of
$controller
, and injecting the child scope.
/* globals window */ window.EmpathyOverlay.setController = function(overlayElement) { 'use strict'; overlayElement.attr('data-ng-controller', 'MyController'); }; angular.module('ebApp').controller('MyController', ['$controller', '$scope', '$window', function($controller, $scope, $window) { 'use strict'; // Instantiate parent controller with the scope of the child controller // You can also override the parent dependencies here when necessary $controller('EmpathyBrokerController', {$scope: $scope}); // Example global event management angular.element($window).on('orientationchange resize', function() { // Event handling }); }]);
Setting up Add to Cart tracking
Tracking the Add to Cart event is crucial to determine the performance of an instance and take business decisions based on data.
There are two situations where the Add to Cart should be attributed to the Search:
Add to Cart button in the Search Results Page (SRP): this is fully handled by the Overlay as long as you add the button through the feature
a2cButtonInResult: true
.Add to Cart button in the Product Details Page (PDP): the Overlay only tracks an Add to Cart event if the user has clicked on the product in the SRP in the last 60 seconds (60 seconds is the default time and can be modified in the feature
a2cProductPage.rightClickTTL
). In order to get this to work, you will need to provide some additional information to the Overlay: the selector of your Add to Cart button (ebConfig.domA2CTrigger
). This way the Overlay will know how to handle this event. It is also required to include the Overlay integration snippet in this page in case it doesn't already.
Note: We used to have a separate library to perform this task (EmpathyTAG
), but it made the integration process more complex, so we decided to include this behavior into the Overlay.
Adding a new Bower dependency
We use Bower as our package manager for front end dependencies. If you need to use a new library with the Overlay, these are the steps to follow:
Finding a dependency
To find a new dependency with Bower, you can use the search command:
bower search <package>
It is also possible to use the info command to obtain information about a specific package and its available versions:
bower info <package>
Installing a dependency
To install the dependency you can use the install command:
bower install --save <package>
Where <package>
is the name of the dependency. We also use the --save
option to persist the new dependency to the manifest file, bower.json
. This way, it will be automatically installed the next time you run bower install
.
You can also specify a version number with the following command:
bower install --save <package>#<version>
Note: Bower assumes modules are using semver versioning, but this is not always the case. Modules not based on semver often modify the middle number with non-backwards compatible features and this can cause breaking the build. For this reason, we recommend avoiding the caret (^
) prefix and using the tilde (~
) instead. For more info on versioning: https://bower.io/docs/api/#install
Wiring an AngularJS dependency
If the dependency is an AngularJS module, it is necessary to push it into the application's array of dependencies to enable its usage:
angular.module('ebApp').requires.push('<MODULE_NAME>');
We suggest to create a dependencies
directory in src
with a JavaScript file that performs this operation.
Adding a dependency to the build process
First, it is important to identify the resources that need to be added to the build process.
CSS resources need to be included in the array of sources within the less
task (before the custom styles):
less: { overlay: { options: { paths: ['src'], cleancss: true }, src: ['<CSS_PATH>', 'src/custom.less'], dest: 'dist/<%= pkg.path %>/css/<%= pkg.name %>.css' } }
CSS_PATH
is the path of the CSS dependency that needs to be imported. Either.css
or.min.css
files can be used, as the result of this task is minified by Grunt later in the build pipeline.
JavaScript resources need to be included in the concat.dist
task (also in the beginning of the array):
concat: { options: { separator: '\n' }, dist: { src: ['<PUSH_PATH>', '<JS_PATH>', 'src/**/*.js'...], dest: 'dist/<%= pkg.path %>/js/<%= pkg.name %>.js' } }
PUSH_PATH
is the path of the file that pushes the AngularJS dependencies to the array. This is only used if you added a new AngularJS module to the Overlay.JS_PATH
is the path of the JS dependency that needs to be imported. Either.js
or.min.js
files can be used, as the result of this task is minified by Grunt later in the build pipeline.
jQuery is the evil
Don't get us wrong, we absolutely love jQuery. Just not with AngularJS or other big JavaScript frameworks. It does not add enough to justify the extra KBs. Also, AngularJS has a lighter version called jqLite that supplies most of the functionality we need. These are the reasons why we disabled the usage of jQuery by AngularJS in the Overlay.
Still, jqLite has some limitations, so here is a list of common functions that are not supported and their workarounds.
We have also created a service with a few helper functions to provide missing functionality: services/helpers/js-extensions.js
.
Deploying the Overlay
A production-ready build can be generated at any time by running the command:
grunt package
This creates the structure of target files to be deployed in the dist
directory, containing all JS and CSS resources (both minified and unminified), images and test HTML files.
The Overlay project includes bower
and grunt-cli
as dev dependencies so it only depends on Git and Node for deployments. The general deployment process is:
Source code checkout
npm install
node_modules/bower/bin/bower install
node_modules/grunt-cli/bin/grunt package
The files to be deployed are contained in the dist
directory.
EB only: To hook a custom Overlay into our CI/CD pipeline, just create a Jenkinsfile
in the project's root directory with the client name. The deployment path will look like <BASE_URL>/clients/<CLIENT_NAME>/...
, where <CLIENT_NAME>
is the name defined in the Jenkinsfile
.
clientOverlayPipeline { client = "<CLIENT_NAME>" }
Integrating the Overlay into your website
The client side integration for the Overlay consists in inserting and configuring a little code snippet in all pages where the search functionality is present.
Staging
On this environment, first of all, we need to import the necessary resources from the base Overlay and the custom resources for the current project. These resources will also be imported in the real website when going live.
<!-- Base Overlay CSS --> <link rel="stylesheet" href="https://preassets.empathybroker.com/overlay/1.4/css/eb.base.min.css"/> <!-- Custom CSS for the current project --> <link rel="stylesheet" href="https://preassets.empathybroker.com/clients/<CLIENT_NAME>/css/eb.custom.min.css"> <!-- Base Overlay JS --> <script src="https://preassets.empathybroker.com/overlay/1.4/js/eb.base.js"></script> <!-- JS with the custom behavior for the current project --> <script src="https://preassets.empathybroker.com/clients/<CLIENT_NAME>/js/eb.custom.js"></script>
As you can see eb.base.js
and eb.custom.js
are being imported without minification. This will help us debug any possible integration issue.
Client only: custom CSS and JS paths would use the URLs where your resources are deployed instead of EB's preassets
.
Then we need to include the snippet that initializes the Overlay, with a configuration object that can be dynamically customized (please note that debug is set to true for the same reason):
<script> EmpathyOverlay.config({ endpoints: { instance: 'YOUR_INSTANCE_ID', env: 'staging' }, lang: 'en', displayLang: 'en', scope: 'default', defaultCurrency: 'EUR', debug: true }); </script>
To know more about the parameters that are accepted in the configuration object, see the section Configuration parameters.
Note: To get better performance and UX place the CSS imports in the head
of the HTML document and the JS and the snippet at the end of the body
.
Live
The live snippet requires a few changes with the goal of making it as slim as possible.
First, we replace preassets
resources with assets
resources. We also use the .min
prefix on all files (right before the extension) to use minified resources.
<!-- Base Overlay CSS --> <link rel="stylesheet" href="https://assets.empathybroker.com/overlay/1.4/css/eb.base.min.css"/> <!-- Custom CSS for the current project --> <link rel="stylesheet" href="https://assets.empathybroker.com/clients/<CLIENT_NAME>/css/eb.custom.min.css"> <!-- Base Overlay JS --> <script src="https://assets.empathybroker.com/overlay/1.4/js/eb.base.min.js"></script> <!-- JS with the custom behavior for the current project --> <script src="https://assets.empathybroker.com/clients/<CLIENT_NAME>/js/eb.custom.min.js"></script>
Then, we remove the env (it points to live by default) and the debug configuration, so the snippet would basically look like this (unless the client is using additional configuration parameters):
<script> EmpathyOverlay.config({ endpoints: { instance: 'YOUR_INSTANCE_ID' }, lang: 'en', displayLang: 'en', scope: 'default', defaultCurrency: 'EUR' }); </script>
Troubleshooting
If you are experiencing integration issues, please check out our current list of FAQs & Common issues on: https://searchbroker.atlassian.net/wiki/x/_hbVB
Final thoughts
Thanks for reading! Our best efforts have been put into writing this guide and we hope it helped you in the journey of instrumenting an Overlay.
However, this is just the first version of the document and there might be some gaps in the process. If you get stuck at some point, please let us know and we will be glad to help.
Cristian Casais <cristianc@empathybroker.com>
Iván Tajes <ivant@empathybroker.com>