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:

  1. Download the Node.js installer from its website or install it using your favorite package manager

  2. Open a terminal and browse to the project's root directory

  3. Run npm install -g bower to install Bower globally

  4. Run npm install -g grunt-cli to install Grunt globally

  5. Run 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.

You should be able to see something like this

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

instance: 'demo'

instance: 'YOUR_INSTANCE_ID'

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 object EmpathyOverlay, which contains functions to setup and bootstrap the Overlay.

      • ebApp.js
        Here is the definition of the AngularJS application called ebApp 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 the src 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 the config.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:

  1. The user types a query or filters the results.

  2. The components set the query and/or the filters in the ebState.

  3. The EmpathyBrokerController detects the change in the state and notifies the MainTrigger helper.

  4. The MainTrigger makes the api services do a request to the backend services.

  5. The api services receive the response from the backend services and set the data into the data containers ebSearch and ebLinks.

  6. 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.

  1. Base configs (providers/base-config.js): these contain the default values for all configurations, see the tables below.

  2. 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.

  3. 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 (query, click, add2cart, open, close or resultsChange). The functions have to be declared before the EmpathyOverlay.config call, or be declared as anonymous functions in the object. These functions will receive a "data" object as param, containing all information about the action. This information can be customized for each client.

actionCallbacks: {
query: searchCallback,
click: clickCallback,
add2cart: add2cartCallback,
open: openCallback,
close: closeCallback,
resultsChange: resultsChangeCallback
}

OR

actionCallbacks: {
query: function(data) { /* custom code */ },
click: function(data) { /* custom code */ },
add2cart: function(data) { /* custom code */ },
open: function() { /* custom code */ },
close: function() { /* custom code */ },
resultsChange: function() { /* custom code */ }
}


cookieSessionId

Default

'__eb_session_id'

Type

String

Name of the cookie where the session id is stored.


cookieUserId

Default

'__eb_session_id'

Type

String

Name of the cookie where the user id is stored.


debug

Default

false

Type

Boolean

Used to log debugging information to the console and enable AngularJS scopes inspecting.


debugProductPage

Default

false

Type

Boolean

If true and debug is enabled, fake product_page.html is used instead product page. For debug purposes only.


defaultCurrency

Default

'USD'

Type

String

Currency for the search results. The value is must be in the ISO 4217 format.


defaultImage

Default

null

Type

String

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

false

Type

Boolean

Disable all tracking events.


displayLang

Default

'en'

Type

String

Search results display language (tags and labels). The value must be in the ISO 639-1 format.


domA2CTrigger

Default

'#eb-a2c'

Type

String

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

undefined

Type

String

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

'#eb-input'

Type

String

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

'.eb-trigger'

Type

String

Selector of the element that will open the overlay. The event that will be used to open can be configured with the feature eventTrigger.



endpoints

Default

{ 
host: 'https://api{-}{env}.empathybroker.com',
version: 'v1'
}

Type

Object

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

'en'

Type

String

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: {
queryParam: 'q', // the query string param that contains the query
domTrigger: '#search-box', // the element that will open the Overlay
domEvent: 'click'  // the event that will open the Overlay
}


loadingAnimation

Default

undefined

Type

String

The URL of your loading animation. When not provided, our default animation is used.


pageRows

Default

20

Type

Number

Number of results to be displayed per page.


queryOrigin

Default

{
default: 'default',
empathizeTerm: 'empathize_term',
empathizeCategory: 'empathize_category',
suggestions: 'partial',
history: 'history',
spellcheck: 'spellcheck',
url: 'linked'
}

Type

Object

This parameter can be used to override the default values of the query origin sent in the track query event.


scope

Default

'default'

Type

String

Device / App where the search is going to be performed, e.g. desktop or mobile


urlHandler

Default

{
label: {
query: 'q',
filters: 'filters',
sort: 'sort',
page: 'page',
top: 'top'
}
}

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

undefined

Type

Object

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.

  1. Base features (providers/base-features.js): these contain the default values for all features, see the table below.

  2. Custom features (providers/features.js): this can be used to set custom features on a custom Overlay instance, overriding the default values.


a2cButtonInResult

Default

false

Type

Boolean

Enable the add to cart button for each result in the results grid.


a2cProductPage

Add to cart on product page configuration.

a2cProductPage.enabled

Default

true

Type

Boolean

Enable add to cart tracking on product page.

a2cProductPage.urlStrategy

Default

'hash'

Options

'hash' | 'param'

Type

String

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

'id'

Type

String

The name of the param in case urlStrategy is 'param'. This is to prevent collisions with the client params.

a2cProductPage.rightClickTTL

Default

60000

Type

Number

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 localStorage. But this data is stored with a TTL, after which the add to cart event will not tracked.


bodyFacetsCategoriesSystem

Default

'hierarchical_category'

Type

String

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

true

Type

Boolean

If this config is true, each facet value will be render with a checkbox.


bodyFacetsClear

Default

false

Type

Boolean

Show control to clear all selected values in a facet.


bodyFacetsLimit

Default

6

Type

Number

Number of facets to show when the 'link' option is selected as bodyFacetsSeeMoreSystem.


bodyFacetsOpenClose

Default

false

Type

Boolean

This config makes facets collapsable.


bodyFacetsOpenCloseStartOpened

Default

false

Type

Boolean

If the bodyFacetsOpenClose configuration is set to true, then this configuration determines whether facets start opened.


bodyFacetsOverlap

Default

false

Type

Boolean

If the value is true, facets values will be overlapping, as a submenu.


bodyFacetsPriceFilter

Default

'priceSort'

Type

String

Name of the effective price facet.


bodyFacetsPriceRange

Default

true

Type

Boolean

Show price facet as a range slider.


bodyFacetsResponsiveOverlap

Default

true

Type

Boolean

Open a layer with facets from left when device is tablet/mobile.


bodyFacetsSeeMoreSystem

Default

'link'

Type

String

This configures the way in how the list of the facets’ values are shown.

Options


bodyResultsMotion

Default

true

Type

Boolean

Use motion service in all result grids.


bodyResultsScrollToTop

Default

true

Type

Boolean

Enables scroll to top button.


bodyTemplate

Default

'eb-body-template-columns'

Options

'eb-body-template-rows' | 'eb-body-template-columns'

Type

String

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

true

Type

Boolean

Show top clicked products on the no results page.


categoryFilterMultiSelect

Default

false

Type

Boolean

Makes the category facet multi-selectable when filterType value is 'multiSelect'.


components.empathize

Configuration for the Empathize component.

components.empathize.enabled

Default

false

Type

Boolean

Enable the Empathize.

components.empathize.realTime

Default

true

Type

Boolean

Provide immediate results in the Empathize (if false, wait for terms returned by the Search API call, EB only).

components.empathize.limit

Default

10

Type

Number

Maximum number of terms shown in the Empathize.

components.empathize.timeout

Default

100

Type

Number

Timeout to prevent sending every single request for fast typers (should be small so it does not harm UX).

components.empathize.openOnLoad

Default

true

Type

Boolean

Open Empathize on page load if query is not empty.


components.facets

Configuration for the facets component.

components.facets.filter

Default

[]

Type

Array

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

true

Type

Boolean

Include promoted links within the results grid.


components.sort

Configuration for the sort component.

components.sort.type

Default

'select'

Options

'list' | 'select'

Type

String

The way the sort control is shown: as a list or as a dropdown select.

components.sort.values

Default

[]

Type

Array

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

true

Type

Boolean

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

500

Type

Number

The delay time (in milliseconds) to launch the query search between user’s keystrokes.


domBody

Default

'body'

Type

String

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

'#eb-overlay'

Type

String

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

'click'

Options

'click' | 'input'

Type

String

Event to handle how the Overlay is opened.


filterType

Default

'selectedOnly'

Type

String

Makes facets multi-selectable, applying OR when multiple values for the same facet are selected.

Options


headerResponsiveTwoRows

Default

true

Type

Boolean

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

Array of scopes where history must be disabled. History will be disabled when the scope configured in the ebConfig (usually configured from the snippet) is contained in this array.

history.storageKey

Default

'history'

Type

String

The local storage key to store the history. This key will be prefixed with the prefix configured in the storage helper ('eb-' as default).

history.maxSize

Default

10

Type

Number

Maximum number of elements in the stored history.


infiniteScrollOffset

Default

800

Type

Number

Offset in pixels (starting from the bottom) to load the next page in the infinite scroll component.


instantSearchScopesBlacklist

Default

['mobile']

Type

Array

Array of scopes where instant search must be disabled. The instant search will be disabled when the scope configured in the ebConfig (usually configured from the snippet) is contained in this array.


needHash

Default

false

Type

Boolean

Use hash on url to save params instead of the query string. With this flag on, Overlay params such as query, filter, sort or page are stored in the hash.


orderFacetValuesBySelected

Default

false

Type

Boolean

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

'stats.priceSort'

Type

String

Name of the field containing the price stats that is returned by the Search API.


priceStatsShowCurrency

Default

false

Type

Boolean

This configuration shows the currency in the price slider.


showClientHeader

Default

false

Type

Boolean

If the client header is used on top, the Overlay has to be opened under it.


showFacets

Default

true

Type

Boolean

Whether to show the facets.


showSelectedFilters

Default

true

Type

Boolean

Show currently selected filters in a list


showSort

Default

true

Type

Boolean

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

true

Type

Boolean

Enable query debounce.

trackQueryDebounce.timeout

Default

2000

Type

Number

Debounce duration in milliseconds.


icons

This object contains the configurations of the default icons used in the Overlay.

Icon

Options

Default

checkbox

'default/default' | 'animated-stroke/animated-stroke'

'default/default'

close

'static-cross/static-cross' | 'animated-cross/animated-cross'

'animated-cross/animated-cross'

openFacet

'chevron/chevron'

'chevron/chevron'

collapseFacet

'chevron/chevron'

'chevron/chevron'

toggleFacetAnimation

'eb-rotate' | 'eb-invert' | 'eb-none'

'eb-rotate'

showFilterIcon

true | false

true

filter

'funnel/funnel'

'funnel/funnel'

showNoResultsIcon

true | false

true

error

'sad-magnifying/sad-magnifying'

'sad-magnifying/sad-magnifying'

defaultImage

camera/camera'

'camera/camera'

showSortIcon

true | false

true

sort

'sort-down'

'sort-down'

loading

'spinning-balls/spinning-balls'

'spinning-balls/spinning-balls'

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

bodyTemplate

'eb-body-template-rows'

To place the facets over the results

bodyFacetsOverlap

true

To make the facets overlap the results

bodyFacetsOpenClose

true

To make the facets able to open and close on click

bodyFacetsSeeMoreSystem

'default'

To show all the filters inside each facet


CSS

Description

.eb-nav-facet {
 width: initial | 100px | 25%;
}

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

showClientHeader

true

Makes the Overlay to place below the client header

eventTrigger

'input'

This makes the Overlay open when the user types in the search box in the client header


Config

Value

Description

domHeader

'#client-header'

It is necessary to set the selector of the client header in the config

domInput

'#client-searchbox'

It is necessary to set the selector of the client search box

domTrigger

'#client-searchbox'

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 eventTrigger is fired

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

search

To retrieve search results and links

fullsearch

To retrieve search results, links and Empathize terms

links

To retrieve just the links

conversion

To track the conversion event (backwards compatibility)

add2cart

To track the add to cart event

wishlist

To track the add to wish list event

click

To track the click on result event

query

To track the query event

autocomplete

To retrieve Empathize terms (backwards compatibility)

empathizeTo retrieve Empathize terms

topclicked

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

instance

The name of the instance (the customer name) used to replace the {instance} expression in the parameters

host

The host used in the URL. By default it is https://api(-env).empathybroker.com where -env is the optional environment

version

The version used in the URL

service

The part in the URL that identifies the service used

method

The part in the URL that identifies the final method used

url

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

{instance}

The name of the instance

{env}

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

instance

undefined

This param must be configured in the snippet

env

undefined

By default Live environment is used

host

https://api{-}{env}.empathybroker.com


version

v1


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: {
format: 'apiFormat',
instance: 'demo',
env: 'staging',
links: {
version: 'v2',
service: 'searchlinks'
},
empathize: {
instance: 'demo2',
env: ''
},
topclicked: {
url:  'https://presbdemo3.empathybroker.com/ebdemo3/services/autocomplete?lang=es_ES'
}
}

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:

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:

  1. Create a function named setController in the window.EmpathyOverlay object. This function receives the Overlay element as the first parameter, and its aim is to set the new controller name using the data-ng-controller directive.

  2. 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:

  1. Source code checkout

  2. npm install

  3. node_modules/bower/bin/bower install

  4. 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>