1. Cookie Management and Initialization:
You’ve set up the getCookie
and setCookie
functions globally, which is great for consistency. Just ensure that window.domain
(or singleWhiteLabelCookieDomain
) is correctly populated to handle cross-subdomain cookies.
function setCookie(name, value, days) { var expires = ""; if (days) { var date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); expires = "; expires=" + date.toUTCString(); } document.cookie = name + "=" + (value || "") + expires + "; path=/; domain=" + window.domain + ";max-age=31536000;secure;SameSite=Lax;";}
2. User ID Handling:
You’ve correctly defined the function to generate or retrieve the user_id
and set it in the cookie. You’re also attaching it to the window
object so it can be accessed globally. This is important when sending events to GTM or GA4.
function getOrCreateUserId() { var name = "user_id="; var decodedCookie = decodeURIComponent(document.cookie); var ca = decodedCookie.split(';'); for (var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') { c = c.substring(1); } if (c.indexOf(name) == 0) { return c.substring(name.length, c.length); } } // If user_id cookie does not exist, create a new one var userId = 'user_' + Math.random().toString(36).substr(2, 9); setCookie('user_id', userId, 365); return userId;}window.userId = getOrCreateUserId();console.log('user_id: ', window.userId);
3. NATS Decoding and Event Handling:
You’ve set up the decoding of NATS data into structured values (precomputedNatsValues
). These values are then stored globally for later use and sent during the form submission process.
- Form Submission Interception: You correctly intercept form submission, hash the email asynchronously, and push events to GTM/GA4. This is important to ensure the proper handling of user data and tracking.
document.getElementById('natsPreJoinForm').addEventListener('submit', async function(event) { console.log('Form submission intercepted'); event.preventDefault(); // Prevent the form from submitting immediately if (window.isSubmitting) { console.log('Form is already submitting'); return; } window.isSubmitting = true; try { const email = document.getElementById('usernameemail').value; const hashedEmail = await hashEmailAndStore(email); console.log('Hashed email: ', hashedEmail); document.getElementById('hashedEmailCustom9Input').value = hashedEmail; document.getElementById('userIdCustom10Input').value = window.userId; const selectedOption = document.querySelector('.option.pressed-button'); if (!selectedOption) throw new Error('No option selected'); const optionId = selectedOption.id.split('-')[1]; const priceInit = selectedOption.querySelector('.price-init').textContent.trim(); const priceCurrency = selectedOption.querySelector('.price-currency').textContent.trim() || 'USD'; let signUpValue; if (priceInit.toUpperCase() === "FREE") { signUpValue = 0; } else { const priceWhole = selectedOption.querySelector('.price-init').textContent.trim(); const priceCents = selectedOption.querySelector('.price-cents').textContent.trim(); signUpValue = parseFloat(`${priceWhole}${priceCents}`).toFixed(2); } const referrerValue = `${precomputedNatsValues.nats.nats_SiteName || 'default'} | Affiliate ${precomputedNatsValues.nats.nats_AffiliateID || 'default'} | From ${document.referrer || 'Direct/No Referrer'}`; setCookie('nats_affiliate_referrer', referrerValue, 7); // Set cookie with 7-day expiration sendGA4Event(precomputedNatsValues, { hashedEmail, optionId, priceCurrency, signUpValue }); console.log('About to submit the form'); event.target.submit(); // Submit the form after processing } catch (error) { console.error("Error processing form submission:", error); } finally { window.isSubmitting = false; document.getElementById('waiter').style.display = 'block'; }});
4. GA4 Event Sending:
You’ve set up the sendGA4Event
function to handle the necessary event tracking for GA4. This function packages up the event details and pushes them to the dataLayer
.
function sendGA4Event(precomputedNatsValues, optionDetails) { const { hashedEmail, optionId, priceCurrency, signUpValue } = optionDetails; const currentTime = new Date().toISOString(); const eventDetails = { "event": "sign_up", "user_id": window.userId, "hashed_email": hashedEmail, "time_sign_up": currentTime, "method": 'email', "hostname": window.location.hostname, "items": [ { "item_name": `${precomputedNatsValues.nats.nats_SiteName || 'default'} | OptionId ${optionId} - ${priceCurrency}${signUpValue}`, "item_id": `${precomputedNatsValues.nats.nats_SiteName || 'default'} | OptionId ${optionId}`, "item_brand": `${precomputedNatsValues.nats.nats_SiteName || 'default'} | Tour ${precomputedNatsValues.nats.nats_TourID || 'default'}`, "affiliation": `${precomputedNatsValues.nats.nats_SiteName || 'default'} | Affiliate ${precomputedNatsValues.nats.nats_AffiliateID || 'default'}`, "discount": 0, "item_category": 'Membership', "price": signUpValue, "quantity": 1 } ], "currency": signUpValue === 0 ? "USD" : priceCurrency, "value": signUpValue, "event_category": 'nats_pre_join', "event_action": optionId, "event_label": document.referrer || 'Direct/No Referrer', "event_currency": priceCurrency, "event_value": signUpValue, "nats": precomputedNatsValues.nats }; console.log("Sending GA4 event with details: ", eventDetails); // Set the time_sign_up cookie setCookie('time_sign_up', currentTime, 365); if (typeof window.dataLayer !== 'undefined') { window.dataLayer.push(eventDetails); console.log('sign_up event sent to dataLayer:', eventDetails); } else { console.error('dataLayer is not defined.'); }}
5. Minor Fixes and Suggestions:
- Variable Naming Consistency: Ensure that all references to
window.domain
,window.siteURL
, etc., are correctly passed through Smarty or another template engine. Double-check the variables populated by{#singleWhiteLabelCookieDomain#}
and other placeholders. - Avoid Duplicating Cookie Functions: You have defined
getCookie
andsetCookie
multiple times in different parts of the code. It’s more efficient to define these functions once globally and reuse them throughout the script. - GTM Script Load: You’ve added GTM dynamically, which is good. Just ensure that the external script is loading as expected without blocking the rest of the page.
Summary:
Your overall setup for handling cookies, user identification, form submission, and GA4 event sending is solid. You’ve correctly implemented asynchronous form handling and pushed event data to the dataLayer
. The suggestions are mainly around improving code efficiency and ensuring consistency in variable names.
Is there any specific area you would like me to focus on further or clarify in this setup?
Refdactored
document.addEventListener('DOMContentLoaded', function() { console.log('Script loaded: start decoding NATS'); function decodeNatsSMP() { console.log('decodeNatsSMP called'); var data = document.getElementById('nats').value; // Ensure 'nats' input exists in your HTML var chars = data.length; var add = 4 - (chars % 4); if (add < 4) { while (add) { data += '='; add -= 1; } } var decodedData = atob(data); var splitData = decodedData.split('.'); var labels = ['AffiliateID', 'ProgramID', 'SiteID', 'TourID', 'CampaignID', 'AdtoolID', 'AdToolSub1ID', 'AdToolSub2ID']; var decodedValues = { nats: {} }; splitData.forEach((value, index) => { if (index < labels.length) { decodedValues.nats[`nats_${labels[index]}`] = value; if (labels[index] === 'SiteID') { decodedValues.nats['nats_SiteName'] = siteNames[value] || 'Unknown Site'; } }});return decodedValues; } // Declare current time globally window.currentTime = new Date().toISOString(); console.log('Fetching NATS element'); let natsElement = document.getElementById('nats'); if (natsElement && natsElement.value) { let precomputedNatsValues = decodeNatsSMP(); // Compute NATS values if (precomputedNatsValues) { console.log('Precomputed NATS values:', JSON.stringify(precomputedNatsValues)); window.precomputedNatsValues = precomputedNatsValues; // Store globally for later use } else { console.error("Failed to decode NATS values."); } } else { console.error("NATS element or value not found."); } let precomputedNatsValues = window.precomputedNatsValues || {}; console.log('Preparing to hash email and store'); async function hashEmailAndStore(email) { console.log('hashEmailAndStore called'); const msgUint8 = new TextEncoder().encode(email.toLowerCase()); const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); localStorage.setItem('hashed_email', hashHex); return hashHex; } document.getElementById('natsPreJoinForm').addEventListener('submit', async function(event) { console.log('Form submission intercepted'); event.preventDefault(); // Prevent the form from submitting immediately
if (window.isSubmitting) { console.log('Form is already submitting'); return;}window.isSubmitting = true;try { const email = document.getElementById('usernameemail').value; const hashedEmail = await hashEmailAndStore(email); console.log('Hashed email: ', hashedEmail); document.getElementById('hashedEmailCustom9Input').value = hashedEmail; document.getElementById('userIdCustom10Input').value = window.userId; const selectedOption = document.querySelector('.option.pressed-button'); if (!selectedOption) throw new Error('No option selected'); const optionId = selectedOption.id.split('-')[1]; const priceInit = selectedOption.querySelector('.price-init').textContent.trim(); const priceCurrency = selectedOption.querySelector('.price-currency').textContent.trim() || 'USD'; let signUpValue; if (priceInit.toUpperCase() === "FREE") { signUpValue = 0; } else { const priceWhole = selectedOption.querySelector('.price-init').textContent.trim(); const priceCents = selectedOption.querySelector('.price-cents').textContent.trim(); signUpValue = parseFloat(`${priceWhole}${priceCents}`).toFixed(2); } if (!precomputedNatsValues.nats) { throw new Error("NATS values are not properly defined."); } const referrerValue = `${precomputedNatsValues.nats.nats_SiteName || 'default'} | Affiliate ${precomputedNatsValues.nats.nats_AffiliateID || 'default'} | From ${document.referrer || 'Direct/No Referrer'}`; setCookie('nats_affiliate_referrer', referrerValue, 7); // Set cookie with 7-day expiration console.log("nats_affiliate_referrer cookie set:", referrerValue); sendGA4Event(precomputedNatsValues, { hashedEmail, optionId, priceCurrency, signUpValue }); console.log('About to submit the form'); event.target.submit();} catch (error) { console.error("Error processing form submission:", error);} finally { window.isSubmitting = false; document.getElementById('waiter').style.display = 'block';} }); // Add the GTM script asynchronously var gtmScript = document.createElement('script'); gtmScript.async = true; gtmScript.src = `https://www.googletagmanager.com/gtag/js?id=${envGAMeasurementID}`; document.head.appendChild(gtmScript); // Define the gtag function window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); // Enable debug mode for GA4 gtag('config', envGAMeasurementID, { 'debug_mode': true }); function sendGA4Event(precomputedNatsValues, optionDetails) { console.log('sendGA4Event called'); const { hashedEmail, optionId, priceCurrency, signUpValue } = optionDetails;
const eventDetails = { "event": "sign_up", "user_id": window.userId, "hashed_email": hashedEmail, "time_sign_up": window.currentTime, // Use globally declared time "method": 'email', "hostname": window.location.hostname, "items": [ { "item_name": `${precomputedNatsValues.nats.nats_SiteName || 'default'} | OptionId ${optionId} - ${priceCurrency}${signUpValue}`, "item_id": `${precomputedNatsValues.nats.nats_SiteName || 'default'} | OptionId ${optionId}`, "item_brand": `${precomputedNatsValues.nats.nats_SiteName || 'default'} | Tour ${precomputedNatsValues.nats.nats_TourID || 'default'}`, "affiliation": `${precomputedNatsValues.nats.nats_SiteName || 'default'} | Affiliate ${precomputedNatsValues.nats.nats_AffiliateID || 'default'}`, "discount": 0, "item_category": 'Membership', "price": signUpValue, "quantity": 1 } ], "currency": signUpValue === 0 ? "USD" : priceCurrency, "value": signUpValue, "event_category": 'nats_pre_join', "event_action": optionId, "event_label": document.referrer || 'Direct/No Referrer', "event_currency": signUpValue === 0 ? "USD" : priceCurrency, "event_value": signUpValue, "nats": precomputedNatsValues.nats}; console.log("Sending GA4 event with details: ", eventDetails); // Set the 'time_sign_up' cookie globally setCookie('time_sign_up', window.currentTime, 365); // Send the event data to GTM if (typeof window.dataLayer !== 'undefined') { window.dataLayer.push(eventDetails); console.log('sign_up event sent to dataLayer with details:', eventDetails); } else { console.error('dataLayer is not defined.'); } }});