Badging (for PLPs)

Set up your Placement either using the Experience Hub or Qubit CLI.

For this tutorial, you’ll be adding badges to the products displayed on a product listing page (PLP), an example of which is the "Popular!" text box in the image below:

initial banner

Select the page type and product source

In the Placement creation wizard, select category as your page type and Listing as your product source.

placement creation wizard

Set up your triggers

Next, find a way to target exactly the element which contains the listing results. In this case, you should use the CSS selector .CoveoResultList:

element to replace on page

Make sure you add that element on the Simple triggers page, in the Polling for section. For more information about triggers, see Placement settings.

simple triggers page

The event above has been set to ecProduct, eventType="listing" because the listing product source was selected in the first step. We expect these QProtocol events in the below format to identify products that are candidates for badging:

uv.emit('ecProduct', {
  eventType: 'listing',
  product: {
    productId: 'product1'
  }
})

Define your sample payload

The Placement schema already includes the message and an imageUrl fields, one of which will need to be defined for a campaign to run.

Note

You can add custom fields to your schema to give merchandisers additional control over the Placement’s design. For more details, see Schemas.

You can set your sample payload by filling out the fields on the Placement Content page:

schema

The sample payload will look like this:

{
  "message": "HOT RIGHT NOW!",
  "imageUrl": "https://image.flaticon.com/icons/png/512/91/91381.png"
}
Note

If you’re using Qubit CLI to develop locally, you’ll need to save the Placement in the UI and pull its new structure to your local project at this point.

Write some code!

This is what your placement.js file will look like at first:

module.exports = function renderPlacement ({ content, onImpression, onClickthrough }) {
  if (content) {

  } else {

  }
}

To start with, you have to import preact and @qubit/utils, a library that offers some helpful tools for manipulating the DOM. Next, you should use onRemove and elements (two other arguments available for us in the renderPlacement function) which you can read more about in placement.js arguments.

const React = require('preact')
const {
  style,
  insertBefore,
  onEvent,
  onEnterViewport,
  restoreAll
} = require('@qubit/utils/dom')()

module.exports = function renderPlacement ({
  content,
  onImpression,
  onClickthrough,
  onRemove,
  elements
}) {
  // ...
}

As you can see in the boilerplate code, you will divide our code into two parts, content and no-content. However, there are a couple of things you need to do before getting into that split to avoid creating bias in our test:

// The functions provided by @qubit/utils are all linked to the `restoreAll` function,
// which means that when you remove the Placement on the page with `onRemove` (particularly useful for single-page applications),
// it will clean it up after itself and avoid leaking side effects.
onRemove(restoreAll)

// Appending this element to the same spot on the dom for both control and variant will ensure the test is fair.
// Check out the Impressions and clickthroughs guide in the docs to learn more about this.
const element = document.createElement('div')
insertBefore(target, element)

// Emitting onImpression before branching into control/variant prevents bad splits
onEnterViewport(element, onImpression)

You can now implement the placement itself. You have to start with the following skeleton template for placement.js, which will suit most use-cases, and each missing part will be filled for you:

const React = require('preact')
const {
  style,
  appendChild,
  onEvent,
  onEnterViewport,
  restoreAll
} = require('@qubit/utils/dom')()

function renderPlacement({
  content,
  elements: [plpContainerElement],     // use `plpContainerElement` for the impression event
  onImpression,
  onClickthrough,
  onRemove,
  meta: { placementId }                // Use `placementId` for your class names
 }) {
  onRemove(restoreAll)
  onEnterViewport(plpContainerElement, onImpression)

  // 1. Scrape for the list of all products on the page
  const productElements = [] // for example, document.querySelectorAll('.my-products')

  // 2. Register clickthrough events
  for (const productEl of productElements) {
    // 2.1 Scrape for all the clickables within the product card that constitute a clickthrough
    const productLinks = [] // for example, productEl.querySelectorAll('a, button')

    // 2.2 Register events
    for (const link of productLinks) {
      onEvent(link, "click", onClickthrough)
    }
  }

  if (content) {
    for (const productEl of productElements) {
      // 3. Extract the badge content
      const badgeContent = null
      if (badgeContent) {
        // 4.1 Inject placement into the DOM
        // 4.2 Also, register product impressions and product clickthough
      }
    }
  }
}

Step 1: Scrape for the list of all products on the page

In order to apply the badges returned by the {Content API}, you’ll need to find the elements containing the products displayed in the PLP. It’s important to replace the declaration of productElements so that it contains all the relevant elements, like in the code sample below:

const productElements = document.querySelectorAll('.my-products')

Step 2: Register clickthrough events

In order to track conversion, you’ll need to register clickthrough events. Here is an example of how to implement this:

for (const productEl of productElements) {
  // 2.1 Scrape for all the clickables within the product card
  // Note: sometimes, you will want to exclude certain buttons or links, for example
  // in the case of "Quick view" buttons which do not constitute a clickthrough when clicked.
  const productLinks = productEl.querySelectorAll('a, button')

  // 2.2 Register the events
  for (const link of productLinks) {
    onEvent(link, "click", onClickthrough)
  }
}

Step 3: Extract the badge content

While iterating over the products displayed on the page, you have to check if the {Content API} has returned content to be badged for each product. It’s recommended to create a utility function to do this, like in the example below:

// This function takes in the `content` object and the product ID to check.
// If there's a badge associated with the given product, the badge content will be returned.
// Otherwise, `null` is returned.
function getBadgeContent(content, productID) {
  const arr = content.badges[productID]
  return arr && arr.length
    ? arr[0]
    : null
}

You can then use this to determine the badge content associated to each product, for example:

// Adapt this line so that it extracts the product ID of the given product element
const productID = productEl.getAttribute("product-id")
const badgeContent = getBadgeContent(content, productID)

Step 4: Render the placement!

If the {Content API} successfully returns badge content for the current product, you can now render a badge for that product. This will vary depending on the DOM of your website and on the specifics of your use-case, but here is a general example of the JavaScript code you can use to deploy the badge:

// Here you use destructuring to get our content from the `content` object
const { message, imageUrl } = content

const target = document.createElement('div')
React.render(
  // Note that you will use these classNames to style our badge in placement.css
  <div className="PLP-badge">
    {image && <img className="PLP-badge-image" src={image}/>}
    {message && <p className="PLP-badge-message">{message}</p>}
  </div>,
  // You are rendering this badge inside the newly created target element
  target
)

// Insert the target element in the DOM. Change this so it appears in the correct location in your page.
appendChild(productEl, target)

Step 5: Add styling

Lastly, you have to modify your placement.css to style the badge according to your needs. Don’t worry about importing it since it’s already bundled together with your placement.js file.

.PLP-Badge {
  position: absolute;
  display: grid;
  grid-template-columns: 30px 1fr;
  top: 80%;
  left: 50%;
  transform: translateX(-50%);
  padding: 10px;
  background-color: white;
}

.PLP-Badge-image {
  margin-right: 13px;
}

.PLP-Badge-message {
  color: black;
  margin: auto;
  margin-left: 13px;
}