Badging (for PDPs)

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

For this tutorial, you’ll be adding a badge to a product image on its PDP (product description page), an example of which is the "HOT RIGHT NOW!" box in the image below:

Initial recommendations banner

Set up your triggers

Start by finding a way to target exactly the element that you want to replace. In this case, you can use its Id #shopify-featured-image:

element to replace on page

Since badging is currently only available in PDP pages, your Simple triggers page will already include a product page type.

Make sure you add that element on the Simple triggers page, in the Polling for section. If you have any questions about triggers, there’s a section on them here.

simple triggers page

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)

Apart from that, you can destructure the array of elements provided to you for accessing to the element you polled for in the beginning (the banner with Id #shopify-featured-image).

Note

If you had polled for more elements or global objects, they would also appear in this destructured array in the order they’re being polled for.

const [target] = elements

So far, our placement.js code looks like this:

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

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

  const [target] = elements

  const element = document.createElement('div')
  insertBefore(target, element)

  onEnterViewport(element, onImpression)

  if (content) {

  } else {

  }
}

Content

After setting up the Placement code and ensuring that only consistent impression events are being collected, you can have a look at rendering the badge.

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

// Note that you will use these classNames to style our banner in placement.css
React.render(
  <div className='Badge'>
    {image && <img className='Badge-image'src={image}>}
    {message && <p className='Badge-message'>{message}</p>}
  </div>,
  // You are rendering this badge inside the target, which is the product container
  target
)

Now, all you need to do is give it some styles in placement.css. Don’t worry about importing it, it’s all bundled together when you are serving the Placement.

#shopify-featured-image {
  position: relative;
}

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

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

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

No content (control)

Due to the fact that you are only tracking onImpression before checking for content, and are also not tracking clicks, there is no need to do anything for the control. This makes the else statement redundant which is why it should be removed.

So this is what the whole placement.js will look like:

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

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

  const element = document.createElement('div')
  insertBefore(target, element)

  onEnterViewport(element, onImpression)

  const [target] = elements

  if (content) {
    const { message, imageUrl } = content

    React.render(
      <div className='Badge'>
        {image && <img className='Badge-image'src={image}>}
        {message && <p className='Badge-message'>{message}</p>}
      </div>,
      target
    )
  }
}

And now your product has a badge!

After you are done, you can publish your Placement and your merchandiser will be able to target it with some campaigns!