Examples

These examples show how Browser API can be leveraged to create custom widget implementations to display combos, bundles, and bundle builders.

Combos

const renderCombo = (containerSelector) => {
  // Grab the API client, a location and a combo
  const { client } = window.pickystory
  const location = client.getLocations('combos').shift()
  const combo = location.deals.shift()

  // Find the designated slot in the page and add a title and product cards
  const container = document.querySelector(containerSelector)
  const header = document.createElement('h2')
  header.textContent = 'COMBO'
  container.append(header)
  for (let product of combo.products) {
    renderProductCard(product, container)
  }

  // Render the ATC button and specify its action
  renderAddToCart(container, async (selections) => {
    const variants = combo.getVariantsForSelections(selections)
    await combo.addVariantsToCart(variants, 1)
  })
}

Bundles

const renderBundle = (containerSelector) => {
  // Grab the API client, a location and a bundle
  const { client } = window.pickystory
  const location = client.getLocations('bundles').shift()
  const bundle = location.deals.shift()

  // Find the designated slot in the page and add a title and product cards
  const container = document.querySelector(containerSelector)
  const header = document.createElement('h2')
  header.textContent = 'BUNDLE'
  container.append(header)
  for (let product of bundle.products) {
    renderProductCard(product, container)
  }

  // Render the ATC button and specify its action
  renderAddToCart(container, async (selections) => {
    const variants = bundle.getVariantsForSelections(selections)
    await bundle.addVariantsToCart(variants, 1)
  })
}

Bundle builders

const renderBuilder = (containerSelector) => {
  // Grab the API client, a location and a builder
  const { client } = window.pickystory
  const location = client.getLocations('builders').shift()
  const builder = location.deals.shift()

  // Find the designated slot in the page and add a title and product cards
  const container = document.querySelector(containerSelector)
  const header = document.createElement('h2')
  header.textContent = 'BUILDER'
  container.append(header)
  for (let product of builder.products) {
    renderProductCard(product, container)
  }

  // Render the ATC button and specify its action
  renderAddToCart(container, async (selections) => {
    const variants = builder.getVariantsForSelections(selections)
    await builder.addVariantsToCart(variants, 1)
  })
}

Kits

Coming soon!

Looks

Coming soon!

Common

For the purpose of this demo, we assume widget slots have been added to the store HTML as below:

<div id="api-combo-container"></div>
<div id="api-bundle-container"></div>
<div id="api-builder-container"></div>
<div id="api-kit-container"></div>
<div id="api-look-container"></div>
/**
 * Render a card containing product title, image and options
 */
const renderProductCard = (product, container) => {
  // Show the title and the product image using simple <div>, <h3> and <img> tags
  const productDiv = document.createElement('div')
  container.append(productDiv)
  const title = document.createElement('h3')
  productDiv.append(title)
  title.textContent = product.title
  const image = document.createElement('img')
  productDiv.append(image)
  image.height = 100
  image.width = 100
  image.src = product.images[0].src

  // Show product options and their values as dropdowns using <select>
  for (let option of product.options) {
    const select = document.createElement('select')
    productDiv.append(select)
    select.name = option.name
    select.setAttribute('product-position', product.position)
    select.setAttribute('option-position', option.position)
    select.classList.add('picky-option')
    for (let value of option.values) {
      const option = document.createElement('option')
      select.append(option)
      option.value = value
      option.textContent = value
    }
  }
}

/**
 * Render a sample Add To Cart button
 */
const renderAddToCart = (container, onAddSelections, callToAction = 'ADD') => {
  // Create a simple button and style it a bit
  const button = document.createElement('button')
  container.append(button)
  button.type = 'button'
  button.textContent = callToAction
  Object.entries({
    width: '100%', 'text-align': 'center', background: 'black', color: 'white', height: '40px'
  }).forEach(([key, value]) => button.style.setProperty(key, value))

  // Collect product option values from the relevant <select> elements
  button.onclick = async () => {
    const selections = [...container.querySelectorAll('.picky-option')]
      .reduce((result, node) => {
        const productPosition = parseInt(node.getAttribute('product-position'), 10)
        const optionPosition = parseInt(node.getAttribute('option-position'), 10)
        let product = result.find(({ position }) => position === productPosition)
        if (!product) {
          product = { position: productPosition }
          result.push(product)
        }
        const optionAlias = 'option' + optionPosition
        product[optionAlias] = node.value
        return result
      }, [])

    // Use a naive opacity effect while cart addition is in progress
    button.disabled = true
    button.style.setProperty('opacity', '0.4')

    // An async click handler must be provided
    if (typeof onAddSelections === 'function') {
      await onAddSelections(selections)
    }

    button.disabled = false
    button.style.setProperty('opacity', '1')
  }
}

(() => {
// A simple way of activating the widget once the API becomes available
  let apiWaitCount = 0
  const interval = window.setInterval(() => {
    ++apiWaitCount
    if (window?.pickystory?.client) {
      clearInterval(interval)
      renderCombo('#api-combo-container')
      renderBundle('#api-bundle-container')
      renderBuilder('#api-builder-container')
    } else if (apiWaitCount > 50) clearInterval(interval)
  }, 50)
})()

Last updated