Skip to content

Instantly share code, notes, and snippets.

@husseyexplores
Created August 11, 2021 13:12
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save husseyexplores/2e6d18a934387cc9150c44c9085f708c to your computer and use it in GitHub Desktop.
Save husseyexplores/2e6d18a934387cc9150c44c9085f708c to your computer and use it in GitHub Desktop.
[Klaviyo Integration] Klaviyo signup form & BIS #klaviyo
<button type="button" class="btn klaviyo-bis-trigger-custom" {% if product.selected_or_first_available_variant.available %}style="display: none;"{% endif %}>Notify me when available</button>
<div
id="KlaviyoBIS-{{ product.id }}"
class="modal modal--square modal--mobile-friendly klaviyo-bis-popup"
data-product-id="{{ product.id }}"
>
<div class="modal__inner">
<div class="modal__centered medium-up--text-center">
<div class="modal__centered-content">
<div class="klaviyo-bis">
<h2>{{ product.title }}</h2>
<p>{{ 'products.back_in_stock.popup_body_content' | t }}</p>
<div class="form-vertical" style="padding-top: 15px;">
<form action="#" class="klaviyo-bis-form-custom">
<input type="hidden" name="subscribe_for_newsletter" value="true">
<input type="hidden" name="platform" value="shopify">
<input type="hidden" name="g" value="RzPhNs"> <!-- lst id -->
<input type="hidden" name="a" value="QQyd2Z"> <!-- pub api key -->
<input type="hidden" name="product" value="{{ product.id }}">
<div {% if product.variants.size == 1 %}style="display: none;"{% endif %}>
<label for="KlaviyoBIS-{{ product.id }}--variants">Options</label>
<select id="KlaviyoBIS-{{ product.id }}--variants" name="variant" class="variants" style="width: 100%;">
{%- for v in product.variants -%}
{%- unless v.available -%}
<option value="{{ v.id }}">{{ v.title }}</option>
{%- endunless -%}
{%- endfor -%}
</select>
</div>
<label for="KlaviyoBIS-{{ product.id }}--name">Name</label>
<input type="tel" id="KlaviyoBIS-{{ product.id }}--name" class="input-full" name="name" value="{% if customer %}{{ customer.first_name | default: '' }} {{ customer.last_name | default: '' }}{% endif %}">
<label for="KlaviyoBIS-{{ product.id }}--name">Email *</label>
<input type="email" id="KlaviyoBIS-{{ product.id }}--email" class="input-full" name="email" autocorrect="off" autocapitalize="off" value="" required>
<label for="KlaviyoBIS-{{ product.id }}--phone">Phone Number</label>
<input type="tel" id="KlaviyoBIS-{{ product.id }}--phone" class="input-full" name="phone_number" pattern="[0-9\-]*" value="{% if customer %}{{ customer.phone | default: '' }}{% endif %}">
<div class="completed_message alert" style="display: none;">{{ 'products.back_in_stock.subscription_success_label' | t }} <a href="#" role="button" class="text-close js-modal-close">Close</a></div>
<div class="error_message alert" style="display: none;"></div>
<button class="btn submit-btn" style="width: 100%;">{{ 'products.back_in_stock.popup_button_label' | t }}</button>
</form>
</div>
<!-- <button type="button" class="text-close js-modal-close">Close</button> -->
</div>
</div>
<button type="button" class="modal__close js-modal-close text-link">
<svg aria-hidden="true" focusable="false" role="presentation" class="icon icon-close" viewBox="0 0 64 64"><path d="M19 17.61l27.12 27.13m0-27.12L19 44.74"/></svg>
<span class="icon__fallback-text">{{ 'general.accessibility.close_modal' | t | json }}</span>
</button>
</div>
</div>
</div>
<script>
(function() {
function domReady(fn) {
// If we're early to the party
document.addEventListener("DOMContentLoaded", fn);
// If late; I mean on time.
if (document.readyState === "interactive" || document.readyState === "complete" ) {
fn();
}
}
let pid = {{ product.id }};
let currentVId = {{ product.selected_or_first_available_variant.id }};
domReady(function() {
let id = 'KlaviyoBIS-' + pid
let modal = new theme.Modals(id, id)
let modalElement = document.getElementById(id)
let productSection = document.getElementById('ProductSection-' + pid)
if (productSection) {
let trigger = productSection.querySelector('.klaviyo-bis-trigger-custom')
let bisForm = productSection.querySelector('.klaviyo-bis-form-custom')
let submitBtn = bisForm.querySelector('.submit-btn')
let bisFormVariants = bisForm.querySelector('.variants')
let succEl = bisForm.querySelector('.completed_message')
let errEl = bisForm.querySelector('.error_message')
let variantOptionsArray = Array.from(bisFormVariants.options)
productSection.addEventListener('variantChange', e => {
let variant = e.detail.variant
let available = variant.available
currentVId = variant.id
if (!available) {
trigger.style.display = 'block'
} else {
trigger.style.display = 'none'
}
})
trigger.addEventListener('click', function() {
bisFormVariants.selectedIndex = variantOptionsArray.findIndex(x => x.value == currentVId)
modal.open();
})
bisForm.addEventListener('submit', e => {
console.log('submit')
e.preventDefault();
e.stopPropagation();
if (bisForm.checkValidity()) {
postFormData()
}
})
function postFormData() {
succEl.style.display = 'none'
errEl.style.display = 'none'
var formData = new FormData(bisForm);
var data = {};
formData.forEach((value, key) => {
value = typeof value === 'string' ? value.trim() : value
if (value) {
if (key == 'name') {
let nameParts = value.split(' ')
if (nameParts[0]) {
data.$first_name = nameParts[0]
}
if (nameParts[1]) {
data.$last_name = nameParts[1]
}
} else {
data[key] = value;
}
}
});
console.log(data)
if (!data.email || !data.variant || !data.product) {
return
}
// data.variant = 1338281066525
// data.product = 114834341917
submitBtn.setAttribute('disabled', 'true')
fetch("https://a.klaviyo.com/onsite/components/back-in-stock/subscribe", {
"headers": {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9",
"content-type": "application/x-www-form-urlencoded",
},
"body": objToStrUrlEncode(data),
"method": "POST",
"mode": "cors",
"credentials": "omit"
})
.then(r => r.json())
.then(r => {
if (r.status == 200 || r.success) {
succEl.style.display = 'block'
submitBtn.style.display = 'none'
subscribeToList(data)
} else {
errEl.innerHTML = r.message
errEl.style.display = 'block'
submitBtn.removeAttribute('disabled')
}
})
}
function subscribeToList(data) {
let newData = { $email: data.email, a: data.a, g: data.g, $fields: [] }
if (data.$first_name) {
newData.$first_name = data.$first_name
newData.$fields.push('$first_name')
}
if (data.$last_name) {
newData.$last_name = data.$last_name
newData.$fields.push('$last_name')
}
if (data.phone_number) {
newData.$phone_number = data.phone_number
newData.$fields.push('$phone_number')
}
newData.$fields = newData.$fields.join(',')
fetch("https://a.klaviyo.com/ajax/subscriptions/subscribe", {
"headers": {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9",
"access-control-allow-headers": "*",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
},
"referrer": "https://help.klaviyo.com/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": objToStrUrlEncode(newData),
"method": "POST",
"mode": "cors",
"credentials": "omit"
})
}
function objToStrUrlEncode(obj) {
const str = [];
for (var key in obj) {
if (obj.hasOwnProperty(key) && typeof obj[key] !== 'undefined') {
str.push(encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]))
}
}
return str.join('&')
}
}
})
})();
</script>
@icantrank
Copy link

I was thinking about doing this for a pre-launch notify list and now i don't want to lol 😂
Thanks for illustrating how much work this is for literally no benefit (in my case). I will do something simpler instead lol.

@husseyexplores
Copy link
Author

husseyexplores commented Feb 1, 2023

Here's a simpler version:
The kaviyo api has been tucked away in a separate module.

<form id="custom_kaviyo_form">
  <input name="a" type="hidden" value="<KLAVIYO ACCOUNT ID>">
  <input name="g" type="hidden" value="<KLAVIYO LIST ID>">

  <input name="first_name" type="text">
  <input name="last_name" type="text">
  <input name="email" type="email" required>
  <button type="submit">Notify me</button>
</form>

<script type="module">
  import {
    post,
    postBackInStock,
    postNewsletter as subscribeToKlaviyoList,
    } from 'https://cdn.shopify.com/s/files/1/2388/0287/t/11/assets/back-in-stock.js?v=30684385657065969231675343227'


  const formElement = document.getElementById('custom_kaviyo_form')
  formElement.addEventListener('submit', async e => {
    e.preventDefault()
    try {
      const result = await subscribeToKlaviyoList(formElement)
      if (!result.ok) {
        throw new Error(result.error)
      }
      alert(`You're now subscribed!`)
    } catch(e) {
      // could not subscribe!
      alert(`Failed to subscribed. Reason: "${e.message}"`)
    }
    
  })
</script>

And if you want to use klaviyo back-in-stock feature, then make sure to add the product.id and variant.id in the form like so,

<input name="product" type="hidden" value="{{ product.id }}">
<input name="variant" type="hidden" value="{{ variant.id }}">

And use postBackInStock instead of subscribeToKlaviyoList

@icantrank
Copy link

damn that's 🔥 actually. I like this a lot more, thanks for sharing.

listen to my weird ass use case and see how ur second version is so perfect ❤ i wana set my unavailable product as a separate prod not a variant, and just reference it with it's id - user clicks a button to open mini form and register interest from the available prods page.

all examples used product form and extracting variables from page and i didn't want to write bare amounts of code to do it myself but i like this version much better. nice one 👍

@husseyexplores
Copy link
Author

Glad you found it useful! You can ping me if you get stuck or need any pointers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment