1/** 2 * Client-side Klaviyo Event Tracking for Praxis 3 * Handles: Viewed Product, Started Checkout 4 */ 5(function () { 6 'use strict'; 7 8 // Ensure official Klaviyo onsite proxy and queue exists 9 !function(){if(!window.klaviyo||Array.isArray(window.klaviyo)){var e=Array.isArray(window.klaviyo)?window.klaviyo:[];window._klOnsite=window._klOnsite||[];for(var t=0;t<e.length;t++)window._klOnsite.push(e[t]);try{window.klaviyo=new Proxy({},{get:function(e,t){return"push"===t?function(){var e;(e=window._klOnsite).push.apply(e,arguments)}:function(){for(var e=arguments.length,n=new Array(e),r=0;r<e;r++)n[r]=arguments[r];var o="function"==typeof n[n.length-1]?n.pop():void 0,i=new Promise(function(e){window._klOnsite.push([t].concat(n,[function(t){o&&o(t),e(t)}]))});return i}}})}catch(n){window.klaviyo=window.klaviyo||[],window.klaviyo.push=function(){var e;(e=window._klOnsite).push.apply(e,arguments)}}}}(); 10 11 let data = window.praxisKlaviyoData || {}; 12 13 let lastIdentifiedEmail = ''; 14 15 /** 16 * Helper: Validate simple email format 17 */ 18 function isValidEmail(email) { 19 return typeof email === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim()); 20 } 21 22 /** 23 * Identify an email with Klaviyo if valid and not already identified this pageview 24 */ 25 function identifyEmail(email, source) { 26 let trimmed = (email || '').trim(); 27 if (isValidEmail(trimmed) && trimmed.toLowerCase() !== lastIdentifiedEmail.toLowerCase()) { 28 lastIdentifiedEmail = trimmed; 29 window.klaviyo.identify({'$email': trimmed}); 30 } 31 } 32 33 /** 34 * Helper to detect if an element is an email input 35 */ 36 function isEmailInputElement(el) { 37 if (!el || el.tagName !== 'INPUT') { 38 return false; 39 } 40 let id = (el.id || '').toLowerCase(); 41 let name = (el.name || '').toLowerCase(); 42 let type = (el.type || '').toLowerCase(); 43 44 return id === 'praxiscet-login-form__email' || 45 id === 'edd-email' || 46 name === 'edd_email' || 47 type === 'email' || 48 name.indexOf('email') !== -1 || 49 id.indexOf('email') !== -1; 50 } 51 52 /** 53 * Set up listeners to auto-identify visitors when they type or submit their email 54 * Catches /checkout-login/ form (#praxiscet-login-form), EDD checkout inputs, etc. 55 */ 56 function setupEmailIdentification() { 57 // Event delegation for input blur (focusout) and change across the entire document 58 document.addEventListener('focusout', function (e) { 59 if (isEmailInputElement(e.target)) { 60 identifyEmail(e.target.value, 'blur'); 61 } 62 }, true); 63 64 document.addEventListener('change', function (e) { 65 if (isEmailInputElement(e.target)) { 66 identifyEmail(e.target.value, 'change'); 67 } 68 }, true); 69 70 // Also listen for form submit (e.g. #praxiscet-login-form or checkout forms) 71 document.addEventListener('submit', function (e) { 72 let form = e.target; 73 if (!form || typeof form.querySelector !== 'function') { 74 return; 75 } 76 77 let emailInput = form.querySelector('#praxiscet-login-form__email, #edd-email, input[name="edd_email"], input[type="email"], input[name*="email"]'); 78 if (emailInput && emailInput.value) { 79 identifyEmail(emailInput.value, 'submit'); 80 } 81 }, true); 82 } 83 84 /** 85 * 1. Track "Viewed Product" on single course / event pages 86 */ 87 function trackViewedProduct() { 88 if (!data.product || !data.product.id) { 89 return; 90 } 91 92 let prod = data.product; 93 let payload = { 94 "CourseTitle": prod.name || "", 95 "CourseCategories": Array.isArray(prod.categories) ? prod.categories : [], 96 "CourseID": String(prod.id), 97 "Type": prod.productType || "Live Training", 98 "URL": window.location.href 99 }; 100 101 window.klaviyo.track('Viewed Product', payload); 102 } 103 104 /** 105 * 2. Track "Started Checkout" on the checkout page 106 * Flattened top-level properties so Klaviyo flow trigger filters work seamlessly. 107 */ 108 function trackStartedCheckout() { 109 if (data.pageType !== 'checkout' || !data.cart || !Array.isArray(data.cart.items) || data.cart.items.length === 0) { 110 return; 111 } 112 113 let cart = data.cart; 114 let cartValue = typeof cart.value === 'number' ? cart.value : parseFloat(cart.value) || 0; 115 let firstItem = cart.items[0] || {}; 116 117 let unixTime = Math.floor(Date.now() / 1000); 118 let cartId = cart.cartHash || (firstItem.id ? 'cart_' + firstItem.id : 'cart'); 119 let eventId = cartId + '_' + unixTime; 120 121 let checkoutPayload = { 122 "$event_id": eventId, 123 "$value": cartValue, 124 "Currency": cart.currency || "USD", 125 "URL": cart.checkoutUrl || window.location.href, 126 "CourseTitle": firstItem.name || "", 127 "CourseCategories": Array.isArray(firstItem.categories) ? firstItem.categories : [], 128 "CourseID": String(firstItem.id || ""), 129 "Type": firstItem.productType || "On Demand" 130 }; 131 132 window.klaviyo.track('Started Checkout', checkoutPayload); 133 } 134 135 // Initialize on DOM ready
136 function init() { 137 // Always activate email identification listeners (login, registration, checkout) 138 setupEmailIdentification(); 139 140 if (data.pageType === 'checkout') { 141 trackStartedCheckout(); 142 return; 143 } 144 145 if (data.product && data.product.id) { 146 trackViewedProduct(); 147 return; 148 } 149 } 150 151 if (document.readyState === 'loading') { 152 document.addEventListener('DOMContentLoaded', init); 153 } else { 154 init(); 155 } 156})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.