Build the experience

In this article...

We'll cover the essential steps that you need to follow to build a Recommendations experience for your site.

Quick overview

Let’s start with a quick overview that applies to all of Qubit’s experiences.

Basic anatomy of a Recommendations experience

Each experience consists of the same basic anatomy:

  • triggers.js - code that controls when the experience should execute
  • variation.js - code that determines what happens once the experience has triggered
  • variation.css - css that will be injected into the page once the experience has triggered
  • utils.js - a convenient space for defining common functions and variables
  • fields.json - where you can define experience options that are configurable by non-developers
  • package.json - a place for metadata about your experience such as package dependencies

Execution flow within an experience

Once Qubit's script loads on the page, each experience goes through the following lifecycle:

  1. Segmentation and trigger rules are applied and triggers.js is executed
  2. If the trigger function calls the callback, variation.js is executed and variation.css is injected into the page

Steps to build the experience

  1. Use fields.json to allow non-technical team members to easily configure the experience once built
  2. Use package.json to declare the Qubit recommendations package dependencies
  3. Use triggers.js to make sure there are recommendations to show and ensure the element we wish to append the recommendations to exists
  4. Use variation.js to render the recommendations out and ensure that the required events that power the metrics in the Qubit dashboard are emitted
  5. Use variation.css to render the recommendations carousel correctly

Getting started

Step 1

▸ Select Experiences from the side menu, select New experience, Custom, then Choose

Step 2

▸ Enter a name for the experience and a description, if required, and then select Create

Building the experience

fields.json

Because we want to enable non-technical team members to be able to configure the experience once built, we always look to create a fields.json specification.

If you set your experience up as a template, defining a fields.json specification is a great way to allow non-technical team members to add the recommendations carousel where it’s needed and retain a degree of control over how the experience behaves on site.

In this example, we’re going to add a specification to enable non-technical team members to set several options:

  • Page - whether the experience is added to a basket or product page. In turn, this choice will define the API strategy used when we call the Qubit recommendations endpoint
  • Title - the heading text to appear above the recommendations
  • Number - the maximum number of recommendations we want to appear on page
  • Number - the number of products to have in view. Recommendations typically appear in a carousel format, you may therefore want fewer products visible than your maximum number
  • Number - how many slides to scroll. When a user swipes or select the left/right arrow, how many new products should enter the carousel
  • Checkbox - Should the user be able to scroll the carousel infinitely?
  • Text - What should the text look like for our social proof message?
  • Text - After which element should we place our recommendation carousel?

You can extend or reduce these options at leisure depending on how complex your recommendations setup and design are.

Let’s take a look at what we are aiming for, before discussing how to achieve this:

non-dev-building

▸ Select fields.json from the side menu and add the following lines:

{
  "fields": [
    {
      "key": "page",
      "type": "String",
      "label": "Page",
      "groupId": "settings",
      "footnote": "which page the recommendations will show on",
      "required": true,
      "constraints": {
        "values": [
          {
            "label": "Product",
            "value": "product"
          },
          {
            "label": "Basket",
            "value": "basket"
          }
        ]
      }
    },
    {
      "key": "title",
      "type": "String",
      "label": "Title",
      "groupId": "settings",
      "footnote": "e.g. You may also like..",
      "required": true
    },
    {
      "key": "limit",
      "type": "Number",
      "label": "Maximum number of recommendations",
      "groupId": "settings",
      "footnote": "the maximum amount of products to show in the carousel",
      "required": true
    },
    {
      "key": "slidesToShow",
      "type": "Number",
      "label": "Number of recommendations to display",
      "groupId": "settings",
      "footnote": "how many products are visible before scrolling",
      "required": true
    },
    {
      "key": "slidesToScroll",
      "type": "Number",
      "label": "Number of recommendations to slide upon scroll",
      "groupId": "settings",
      "footnote": "how many new products are shown when the user scrolls",
      "required": true
    },
    {
      "key": "infinite",
      "type": "Boolean",
      "label": "Infinite scroll",
      "groupId": "settings",
      "footnote": "Should the carousel scroll infinitely?",
      "required": true
    },
    {
      "key": "views",
      "type": "String",
      "label": "Social proof copy",
      "groupId": "settings",
      "footnote": "where {{count}} to be replaced by view count",
      "required": true
    },
    {
      "key": "element",
      "type": "String",
      "label": "Element to insert after",
      "groupId": "settings",
      "footnote": "e.g. #product .reviews",
      "required": true
    }
  ],
  "groups": [
    {
      "id": "settings",
      "title": "Recommendations settings"
    }
  ]
}

package.json

In the package.json file, we declare our package dependencies for the experience.

▸ Select packages.json from the side menu and add the following lines:

{
  "dependencies": {
    "slapdash": "^1.3.3",
    "@qubit/recommendations": "^1.1.3"
  }
}

For more information about packages, see Packages & Code Re-use.

triggers.js

In the triggers.js file, we must fulfill 2 requirements. Firstly, we must ensure we have some recommendations to show. Secondly, we must ensure the element we wish to append the recommendations to exists before we show recommendations.

Without making these checks, we risk firing the experience and counting a user as having seen the recommendations when really none were available and/or attempting to append the recommendations to an element that no longer exists on site or hasn’t yet loaded.

See Polling For Elements for more information about how to use Qubit’s poller module.

We initialize the @qubit/recommendations package as described in Standard example.

▸ Select triggers.js from the side menu, and add the following lines:

module.exports = function triggers (options, cb) {
  const $ = window.jQuery
  const { data, log, state } = options
  const { page, element } = data
  const strategy = page === 'product' ? 'pp1' : 'pp3'
  const recommendations = require('@qubit/recommendations')(options, { strategy })
  const slickUrl = '//d1m54pdnjzjnhe.cloudfront.net/js-libs/slick/slick_requirable.1.6.min.js'
  const isProduct = /\/products\//i.test(window.location.pathname)


  if (isProduct) {
    options.uv.on('ecProduct', (e) => {
      const { productId } = e.product


      if (productId) {
        recommendations.get({ seed: productId })
          .then(poll)
          .catch(log.info)
      }
    }).replay()
  }


  function poll (recsItems) {
    options.poll(element).then(el => {
      require([slickUrl], (slick) => {
        state.set('state', { recommendations, recsItems, slick, $el: $(el) })
        cb(true)
      })
    })
  }
}

This example assumes a Product page setup where all product pages contain /products/ in the URL. It is always wise to guard your trigger code so that it only runs where it is required.

Following the URL check, we listen for the ecProduct QProtocol event and extract the product Id so we can provide this context as the seed to the Recommendations API.

If you do not have a suitable QProtocol implementation, your seed will need to be retrieved from elsewhere, perhaps an element on the page or an alternative data layer. See Building The Catalog for more information.

WARNING: Failing to provide a seed will result in only popular items being shown.

In basket page/multiple item scenarios, all products Ids in the basket should be provided as an Array of Strings, e.g. ['a1', 'b2', 'c3'].

variation.js

Our variation code should be relatively straightforward - at this point our triggers should have verified we have recommendations to display and that an element exists to append them to.

So now, we simply need to render the recommendations out and ensure that events that power the recommendations reporting and metrics in the Qubit dashboard are emitted.

These events are shown in the following example where:

  • recommendations.shown is called for every recommendation rendered onto the page
  • recommendations.clicked is bound to each recommendation to track every click."

We also use some of the configuration made available to us through our fields.json specification. This should mean that very little code changes will be required in future.

Should you wish to extend or reduce the implementation, for example, removing the social proof views, these can simply be removed from the HTML markup and the associated field be removed from fields.json.

▸ Select variation.js from the side menu and add the following lines:

module.exports = function variation ({ data, state }) {
  const _ = require('slapdash')
  const $ = window.jQuery
  const { infinite, slidesToShow, slidesToScroll, title, views } = data
  const { recommendations, recsItems, $el, slick } = state.get('state')
  slick($)


  const $items = recsItems.map((item, i) => {
	const { details } = item
	const onSale = details.unit_price !== details.unit_sale_price


	recommendations.shown(item)


	return $(`
  	<div class="qubit-recommendation qubit-recommendation-onSale-${onSale}">
    	<a class="qubit-recommendation-link" href="${details.url}">
      	<img class="qubit-recommendation-image" src="${details.image_url}" alt="${details.name}" />
      	<div class="qubit-recommendation-name">${details.name}</div>
      	<div class="qubit-recommendation-wasPrice">${formatPrice(details.unit_price)}</div>
      	<div class="qubit-recommendation-nowPrice">${formatPrice(details.unit_sale_price)}</div>
      	<div class="qubit-recommendation-views">${views.replace('{{count}}', details.views)}</div>
    	</a>
  	</div>
	`).click(() => {
  	recommendations.clicked(_.assign({ position: i + 1 }, item))
	})
  })


  const $html = $(`
	<div class="qubit-recommendations">
  	<div class="qubit-recommendations-title">${title}</div>
  	<div class="qubit-recommendations-carousel"></div>
	</div>
  `)


  const $carousel = $html.find('.qubit-recommendations-carousel')
  $carousel.append($items)
  $el.after($html)
  $carousel.slick({ infinite, slidesToShow, slidesToScroll })


  function formatPrice (price) {
	return '£' + price.toLocaleString('en-GB', { minimumFractionDigits: 2 })
  }
}

If you have existing carousel functionality on-site, this can also be reused rather than pulling down the Slick library shown in our example.

variation.css

As we are using the Slick carousel package, a section of CSS is required for this to render correctly. Additionally, we have included some basic styles to help the content inside the carousel to render more appropriately.

By giving all of our elements appropriate class names, this CSS is easily extensible for adding your own branding in very little time.

▸ Select Variation.css from the side menu and add the following lines:

// wrapping element

.qubit-recommendations {

}

.qubit-recommendations-title {

  text-align: center;

  font-size: 20px;

  padding: 10px;

}

// each recommendation block

.qubit-recommendation {



}

.qubit-recommendation-link {

  text-decoration: none;

}

.qubit-recommendation-image {

  width: 100%;

}

.qubit-recommendation-name {



}

// both prices on or off sale

.qubit-recommendation-wasPrice, .qubit-recommendation-nowPrice {

  display: inline-block;

}

.qubit-recommendation-wasPrice {

  display: none;

  text-decoration: line-through;

}

.qubit-recommendation-nowPrice {



}

// recommendation on sale

.qubit-recommendation-onSale-true {

  .qubit-recommendation-wasPrice {

    display: inline-block;

  }



  .qubit-recommendation-nowPrice {

    color: #f42145;

  }

}

// recommendation not on sale

.qubit-recommendation-onSale-false {



}

/* carousel */

.qubit-recommendations-carousel {

  &.slick-slider {

    position: relative;

    display: block;

    box-sizing: border-box;

    user-select: none;

    touch-action: pan-y;

    -webkit-tap-highlight-color: transparent;

  }



  .slick-list {

    position: relative;

    overflow: hidden;

    display: block;

    margin: 0 -10px;

    padding: 0;

    &:focus {

        outline: none;

    }

    &.dragging {

        cursor: pointer;

        cursor: hand;

    }

  }



  &.slick-slider .slick-track,

  &.slick-slider .slick-list {

    transform: translate3d(0, 0, 0);

  }



  .slick-track {

    position: relative;

    left: 0;

    top: 0;

    display: block;

    &:before,

    &:after {

      content: "";

      display: table;

    }

    &:after {

      clear: both;

    }

  }



  .slick-loading.slick-track {

    visibility: hidden;

  }



  .slick-slide {

    float: left;

    height: 100%;

    min-height: 1px;

    display: none;

    margin: 0 10px;



    img {

      display: block;

    }



    &.slick-loading img {

      display: none;

    }

    &.dragging img {

      pointer-events: none;

    }

  }



  [dir="rtl"] .slick-slide {

    float: right;

  }



  &.slick-initialized .slick-slide {

    display: block;

  }

  .slick-loading .slick-slide {

    visibility: hidden;

  }

  .slick-vertical .slick-slide {

    display: block;

    height: auto;

    border: 1px solid transparent;

  }



  .slick-arrow {

    position: absolute;

    top: 50%;

    transform: translateY(-50%);

    background: transparent;

    cursor: pointer;

    border: 0;

    z-index: 1;

  }



  .slick-arrow.slick-hidden {

    display: none;

  }



  .slick-prev,

  .slick-next {

    font-size: 0;

  }



  .slick-prev:before,

  .slick-next:before {

    font-size: 20px;

    color: #000;

    opacity: 0.75;

  }



  .slick-prev:before {

    content: "\2190";

  }



  .slick-next:before {

    content: "\2192";

  }



  .slick-prev {

    left: 0px;

  }



  .slick-next {

    right: 0px;

  }

}

The results

We are now at a point where we can preview the experience. To do this, select preview experience.

Here's the result:

experience-results

Next steps

Once the experience has been completed, the next step will be to configure it. This can also be completed by non-technical members of your team and involves adding goals, deciding which segments to target with the experience, and what percentage of your traffic to allocate the experience to. You might also decide to schedule the experience. All this and more can be done in the Settings tab. See Configuring Recommendations Experiences for full details.

With the basic setup and configuration done, the final step will be to publish the experience. You can do this by selecting Publish experience. Before doing this, we recommend that you preview it on site to ensure that it fires correctly according to the designed triggers, displays correctly, and crucially that it functions according to your expectations. See Going Live With Your Experience.

When you publish an experience it will be pushed live onto your site or mobile platform. Visitors will be served the experience, of course depending on the conditions established for the experience, including triggers, segmentation, and traffic allocation.

Once an experience is live it will start to generate data, which you will use to interpret the success of the experience against its defined goals. See for details.

Last updated: May 2022
Did you find this article useful?