Last updated
Last updated
These examples show how Browser API can be leveraged to create custom widget implementations to display combos, bundles, and bundle builders.
Coming soon!
Coming soon!
For the purpose of this demo, we assume widget slots have been added to the store HTML as below:
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)
})
}
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)
})
}
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)
})
}
<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)
})()