/**
* HubSpot Form Utilities
*
* Common utilities for HubSpot forms across multiple pages
* Includes: emoji removal, form submission handling, referrer population, dependency checking
*
* Usage:
*
*
* HubSpotFormUtils.setupEmojiRemoval($form);
* HubSpotFormUtils.setupFormSubmission($form, {
* formSelector: '#form1',
* thankYouSelector: '#thank-you',
* hideSelectors: ['.toggle-div'],
* addClassToSelector: '.form-holder',
* classToAdd: 'padding-bottom-0'
* });
* HubSpotFormUtils.populateReferrerField($form);
*/
class HubSpotFormUtils {
/**
* Remove emojis and zero-width characters from form fields
* @param {jQuery} $form - jQuery form element
* @param {Object} options - Configuration options
*/
static setupEmojiRemoval($form, options = {}) {
const config = {
onInput: true, // Remove emojis as user types
onSubmit: true, // Remove emojis on form submission
...options
};
if (config.onInput) {
$form.find('input, textarea').on('input', function () {
const noEmoji = this.value.replace(/[\p{Emoji_Presentation}\u200B\u200D\uFE0F]/gu, '');
if (this.value !== noEmoji) {
this.value = noEmoji;
}
});
}
if (config.onSubmit) {
$form.on('submit', function() {
$form.find('input, textarea').each(function () {
this.value = this.value.replace(/[\p{Emoji_Presentation}\u200B\u200D\uFE0F]/gu, '');
});
});
}
}
/**
* Setup form submission handler to hide form and show thank you message
* @param {jQuery} $form - jQuery form element
* @param {Object} config - Configuration object
* @param {string} config.formSelector - Selector for the form container to hide
* @param {string} config.thankYouSelector - Selector for the thank you message to show
* @param {Array} config.hideSelectors - Additional selectors to hide
* @param {string} config.addClassToSelector - Selector to add class to
* @param {string} config.classToAdd - Class name to add
* @param {boolean} config.scrollToTop - Whether to scroll to top after submission (default: true)
*/
static setupFormSubmission($form, config = {}) {
const defaultConfig = {
formSelector: '#form1',
thankYouSelector: '#thank-you',
hideSelectors: [],
addClassToSelector: null,
classToAdd: null,
scrollToTop: true
};
const finalConfig = { ...defaultConfig, ...config };
$form.on('submit', function() {
// Hide form
if (finalConfig.formSelector) {
$(finalConfig.formSelector).hide();
}
// Hide additional elements
finalConfig.hideSelectors.forEach(selector => {
$(selector).hide();
});
// Add class to element
if (finalConfig.addClassToSelector && finalConfig.classToAdd) {
$(finalConfig.addClassToSelector).addClass(finalConfig.classToAdd);
}
// Show thank you message
if (finalConfig.thankYouSelector) {
$(finalConfig.thankYouSelector).show();
}
// Scroll to top
if (finalConfig.scrollToTop) {
window.scrollTo(0, 0);
}
return false;
});
}
/**
* Populate referrer field with browser referrer
* @param {jQuery} $form - jQuery form element
* @param {Object} options - Configuration options
* @param {string} options.fieldName - Field name to populate (default: 'previous_page_url')
*/
static populateReferrerField($form, options = {}) {
const config = {
fieldName: 'previous_page_url',
...options
};
const referrerValue = document.referrer || '';
if (!referrerValue) {
return; // No referrer to populate
}
// Try multiple methods to find and set the field
let referrerField = $form.find(`input[name="${config.fieldName}"]`);
// If not found, try with different attribute selectors
if (!referrerField.length) {
referrerField = $form.find(`input[data-name="${config.fieldName}"]`);
}
if (!referrerField.length) {
referrerField = $form.find(`input[name*="previous_page"]`);
}
if (!referrerField.length) {
// Try finding by label text
$form.find('label').each(function () {
if ($(this).text().toLowerCase().includes('previous page')) {
const fieldId = $(this).attr('for');
if (fieldId) {
referrerField = $form.find('#' + fieldId);
if (referrerField.length) {
return false; // Break the loop
}
}
}
});
}
if (referrerField.length) {
referrerField.val(referrerValue);
referrerField.trigger('change');
}
}
/**
* Wait for dependencies to load before executing callback
* @param {Array} dependencies - Array of global variable names to wait for
* @param {Function} callback - Callback function to execute when all dependencies are loaded
* @param {Object} options - Configuration options
* @param {number} options.checkInterval - Interval in ms to check for dependencies (default: 100)
* @param {number} options.maxWaitTime - Maximum time to wait in ms (default: 10000)
*/
static waitForDependencies(dependencies, callback, options = {}) {
const config = {
checkInterval: 100,
maxWaitTime: 15000, // Increased to 15 seconds for async scripts
...options
};
const startTime = Date.now();
function checkDependencies() {
// Check if max wait time exceeded
if (Date.now() - startTime > config.maxWaitTime) {
// Check which dependencies are actually missing
const missingDeps = dependencies.filter(dep => {
const parts = dep.split('.');
let obj = window;
for (const part of parts) {
if (obj[part] === undefined) {
return true;
}
obj = obj[part];
}
return false;
});
// Only warn if critical dependencies are missing (not just hbspt which loads async)
if (missingDeps.length > 0 && !(missingDeps.length === 1 && missingDeps[0] === 'hbspt')) {
console.warn('Timeout waiting for dependencies:', missingDeps);
}
if (callback) callback();
return;
}
// Check if all dependencies are available
const allLoaded = dependencies.every(dep => {
// Handle nested properties (e.g., 'window.jQuery')
const parts = dep.split('.');
let obj = window;
for (const part of parts) {
if (obj[part] === undefined) {
return false;
}
obj = obj[part];
}
return true;
});
if (allLoaded) {
if (callback) callback();
} else {
setTimeout(checkDependencies, config.checkInterval);
}
}
checkDependencies();
}
/**
* Setup global error handlers for subscription errors
* Prevents console spam from expected subscription-related errors
*/
static setupErrorHandlers() {
// Global error handler to catch unhandled errors
window.addEventListener('error', function(event) {
// Filter out subscription errors - these are expected and already handled
const errorMsg = event.message || '';
const isSubscriptionError = errorMsg.includes('SUBSCRIPTION') ||
errorMsg.includes('SUBSCRIPTION ISSUE') ||
errorMsg.includes('subscription is inactive') ||
errorMsg.includes('subscription is expired');
if (isSubscriptionError) {
// Log as info, not error, and prevent default error logging
event.preventDefault();
return true;
}
});
// Handle unhandled promise rejections
window.addEventListener('unhandledrejection', function(event) {
// Filter out subscription errors - these are expected
const errorMsg = (event.reason && event.reason.message) ? event.reason.message : '';
const isSubscriptionError = errorMsg.includes('SUBSCRIPTION') ||
errorMsg.includes('SUBSCRIPTION ISSUE') ||
errorMsg.includes('subscription is inactive') ||
errorMsg.includes('subscription is expired') ||
(event.reason && (event.reason.isSubscriptionError || event.reason.name === 'SubscriptionError'));
if (isSubscriptionError) {
// Log as info, not error, and prevent default error logging
event.preventDefault();
return true;
}
});
}
/**
* Initialize form with common utilities
* @param {jQuery} $form - jQuery form element
* @param {Object} config - Configuration object
*/
static initForm($form, config = {}) {
const defaultConfig = {
removeEmojis: true,
populateReferrer: true,
setupSubmission: false, // Set to true and provide config if you want auto submission handling
submissionConfig: {}
};
const finalConfig = { ...defaultConfig, ...config };
// Setup emoji removal
if (finalConfig.removeEmojis) {
this.setupEmojiRemoval($form);
}
// Populate referrer field
if (finalConfig.populateReferrer) {
this.populateReferrerField($form, finalConfig.referrerOptions || {});
}
// Setup form submission handler
if (finalConfig.setupSubmission) {
this.setupFormSubmission($form, finalConfig.submissionConfig);
}
}
}
// Export for use in other files
if (typeof module !== 'undefined' && module.exports) {
module.exports = HubSpotFormUtils;
}