/**
* Agile-Ed Form Integration Module
*
* Reusable module for integrating Agile-Ed API with HubSpot forms
* Provides email autocomplete, school/district autocomplete, and form population
*
* CRITICAL: API Failures Never Block Form Functionality
* ======================================================
* This module is designed with fail-safe principles:
* - All API errors are caught and handled gracefully
* - Form fields are NEVER disabled or made readonly due to API failures
* - Form submission is NEVER blocked by API errors
* - Users can ALWAYS manually fill in all form fields, even if all API calls fail
* - API failures result in silent degradation - form works normally without autocomplete
*
* Dependencies:
* - jQuery
* - agile-ed-api-proxy.js (AgileEdAPI class)
* - institution-lookup.js (InstitutionLookupWidget class)
*
* Usage:
* // In your HTML file, include the scripts:
*
*
*
*
* // Then initialize:
* const integration = new AgileEdFormIntegration({
* proxyUrl: '/api/agile-ed-proxy.php',
* fieldMapping: {
* institution_name: 'company',
* state: 'us_states',
* country: 'country__new_',
* firstname: 'firstname',
* lastname: 'lastname',
* student_count: 'connectlink_district_student_count',
* building_students: 'connectlink_building_student_count',
* institution_id: 'account_connectlink_key',
* phone: 'phone',
* jobtitle: 'jobtitle',
* // purchase_level removed - district_or_building field no longer used
* building_type: 'connectlink_institution_type', // Maps "BLDG" -> "Building", "DIST" -> "District"
* file_type_text: 'connectlink_type_of_school', // Maps "Public" or "Private" (Type of School)
* institution_type: 'connectlink_type_of_school' // Alternative mapping for Type of School
* },
* portalId: '20180325',
* hubspotFormId: '0c5252b0-043f-406e-97a8-3290aff02866'
* });
*
* // In HubSpot form's onFormReady callback:
* hbspt.forms.create({
* // ... form config ...
* onFormReady: function($form, ctx) {
* integration.initForm($form, ctx);
* }
* });
*/
// Production mode - all logging disabled
// Debug logging is completely disabled for production
// All debugLog, debugWarn, and logError calls are no-ops
const DEBUG_MODE = false;
// Debug logging utilities - all disabled in production
const debugLog = function() {}; // No-op - all logging disabled
const debugWarn = function() {}; // No-op - all logging disabled
const logError = function() {}; // No-op - all logging disabled
// Debug instrumentation - disabled in production
// All debugInstrument calls are no-ops in production
const debugInstrument = function(location, message, data, hypothesisId) {
// Production: All debug logging disabled
// No-op function - does nothing
};
// Global error handler to prevent HubSpot DOM errors from breaking the form
// This catches insertBefore/removeChild errors from HubSpot's v2.js
if (typeof window !== 'undefined') {
const originalErrorHandler = window.onerror;
window.onerror = function(message, source, lineno, colno, error) {
// Check error message string
const errorMessage = message ? String(message) : '';
// Check error object properties
const errorObjMessage = error && error.message ? String(error.message) : '';
const errorObjName = error && error.name ? String(error.name) : '';
const errorObjString = error && error.toString ? String(error.toString()) : '';
const errorStack = error && error.stack ? String(error.stack) : '';
// Combine all error information
const fullErrorText = errorMessage + ' ' + errorObjMessage + ' ' + errorObjName + ' ' + errorObjString + ' ' + errorStack;
// Check if error is from HubSpot's v2.js
const isFromHubSpot = source && (source.includes('v2.js') || source.includes('hubspot'));
// Suppress HubSpot's DOM manipulation errors that don't actually break functionality
if (isFromHubSpot ||
fullErrorText.includes('insertBefore') ||
fullErrorText.includes('removeChild') ||
fullErrorText.includes('appendChild') ||
fullErrorText.includes('NotFoundError') ||
fullErrorText.includes('Failed to execute') ||
fullErrorText.includes('not a child of this node') ||
fullErrorText.includes('The node before which') ||
fullErrorText.includes('The node to be removed') ||
(errorObjName === 'NotFoundError' && (fullErrorText.includes('Node') || fullErrorText.includes('DOM')))) {
// These are typically timing issues with HubSpot's DOM manipulation
// They don't actually break the form, so we suppress them
debugWarn('Suppressed HubSpot DOM manipulation error:', fullErrorText.substring(0, 200));
// CRITICAL: Ensure form is still visible after error
// Sometimes HubSpot errors can cause form to be hidden
try {
if (typeof $ !== 'undefined' && $.fn) {
// Check all HubSpot forms and ensure they're visible
setTimeout(function() {
try {
$('.hs-form, .hs-form-container, form[id*="form"], #form1').each(function() {
const $form = $(this);
if ($form.length && !$form.is(':visible')) {
debugLog('Form was hidden after error - making visible');
$form.show();
}
});
} catch (e) {
// Ignore errors in form visibility check
}
}, 100);
}
} catch (e) {
// Ignore errors in form visibility restoration
}
return true; // Prevent error from propagating
}
// For other errors, call original handler if it exists
if (originalErrorHandler) {
return originalErrorHandler.apply(this, arguments);
}
return false; // Let error propagate
};
// Also catch unhandled promise rejections
window.addEventListener('unhandledrejection', function(event) {
if (event.reason) {
const reason = event.reason;
const reasonMessage = reason.message ? String(reason.message) : '';
const reasonName = reason.name ? String(reason.name) : '';
const reasonString = reason.toString ? String(reason.toString()) : '';
const reasonStack = reason.stack ? String(reason.stack) : '';
const fullReasonText = reasonMessage + ' ' + reasonName + ' ' + reasonString + ' ' + reasonStack;
if (fullReasonText.includes('insertBefore') ||
fullReasonText.includes('removeChild') ||
fullReasonText.includes('appendChild') ||
fullReasonText.includes('NotFoundError') ||
fullReasonText.includes('Failed to execute') ||
fullReasonText.includes('not a child of this node') ||
fullReasonText.includes('The node before which') ||
fullReasonText.includes('The node to be removed') ||
(reasonName === 'NotFoundError' && (fullReasonText.includes('Node') || fullReasonText.includes('DOM')))) {
debugWarn('Suppressed HubSpot DOM manipulation promise rejection:', fullReasonText.substring(0, 200));
event.preventDefault(); // Prevent error from propagating
event.stopPropagation(); // Also stop propagation
}
}
});
// Also catch errors from addEventListener error events
window.addEventListener('error', function(event) {
if (event.error) {
const error = event.error;
const errorMessage = error.message ? String(error.message) : '';
const errorName = error.name ? String(error.name) : '';
const errorString = error.toString ? String(error.toString()) : '';
const errorStack = error.stack ? String(error.stack) : '';
const eventSource = event.filename || event.source || '';
const fullErrorText = errorMessage + ' ' + errorName + ' ' + errorString + ' ' + errorStack;
// Check if error is from HubSpot's v2.js
const isFromHubSpot = eventSource.includes('v2.js') || eventSource.includes('hubspot');
if (isFromHubSpot ||
fullErrorText.includes('insertBefore') ||
fullErrorText.includes('removeChild') ||
fullErrorText.includes('appendChild') ||
fullErrorText.includes('NotFoundError') ||
fullErrorText.includes('Failed to execute') ||
fullErrorText.includes('not a child of this node') ||
fullErrorText.includes('The node before which') ||
fullErrorText.includes('The node to be removed') ||
(errorName === 'NotFoundError' && (fullErrorText.includes('Node') || fullErrorText.includes('DOM')))) {
debugWarn('Suppressed HubSpot DOM manipulation error event:', fullErrorText.substring(0, 200));
event.preventDefault(); // Prevent error from propagating
event.stopPropagation(); // Also stop propagation
return true; // Suppress the error
}
} else if (event.message) {
// Handle errors without error object
const errorMessage = String(event.message);
const eventSource = event.filename || event.source || '';
const isFromHubSpot = eventSource.includes('v2.js') || eventSource.includes('hubspot');
if (isFromHubSpot ||
errorMessage.includes('insertBefore') ||
errorMessage.includes('removeChild') ||
errorMessage.includes('appendChild') ||
errorMessage.includes('NotFoundError') ||
errorMessage.includes('Failed to execute')) {
debugWarn('Suppressed HubSpot DOM manipulation error event (no error object):', errorMessage.substring(0, 200));
event.preventDefault(); // Prevent error from propagating
event.stopPropagation(); // Also stop propagation
return true; // Suppress the error
}
}
}, true); // Use capture phase to catch errors early
}
class AgileEdFormIntegration {
constructor(config = {}) {
// Validate required dependencies
if (typeof AgileEdAPI === 'undefined') {
throw new Error('AgileEdAPI class is required. Please include agile-ed-api-proxy.js');
}
if (typeof InstitutionLookupWidget === 'undefined') {
throw new Error('InstitutionLookupWidget class is required. Please include institution-lookup.js');
}
if (typeof $ === 'undefined') {
throw new Error('jQuery is required');
}
// Required configuration
this.proxyUrl = config.proxyUrl || '/api/agile-ed-proxy.php';
this.fieldMapping = config.fieldMapping || {};
this.portalId = config.portalId;
this.hubspotFormId = config.hubspotFormId;
// Optional configuration
this.emailSelectors = config.emailSelectors || [
'input[type="email"]',
'input[name*="email"]',
'input[name*="Email"]',
'input[id*="email"]',
'input[id*="Email"]',
'input[placeholder*="email"]',
'input[placeholder*="Email"]',
'input[aria-label*="email"]',
'input[aria-label*="Email"]'
];
this.companySelectors = config.companySelectors || [
'input[name*="company"]',
'input[name*="Company"]',
'input[name*="school"]',
'input[name*="School"]',
'input[name*="district"]',
'input[name*="District"]',
'input[id*="company"]',
'input[id*="Company"]'
];
// Initialize API and Widget
this.agileEdAPI = new AgileEdAPI({
proxyUrl: this.proxyUrl
});
this.institutionWidget = new InstitutionLookupWidget({
api: this.agileEdAPI,
hubspotFormId: this.hubspotFormId,
portalId: this.portalId,
fieldMapping: this.fieldMapping
});
// State management
this.activeTimeouts = [];
this.emailSearchTimeout = null;
this.lastApiCallTime = 0;
this.MIN_API_CALL_INTERVAL = 1000;
this.isSearching = false;
this.lastSearchedEmail = null;
this.searchTriggeredFromInput = false;
this.currentRequestAbortController = null;
// Cache
this.emailSearchCache = new Map();
this.CACHE_DURATION = 15 * 60 * 1000; // 15 minutes (increased for better performance)
this.MAX_CACHE_SIZE = 100;
// Global state
if (!window.SecurlyForm) {
window.SecurlyForm = {};
}
// Fix: Don't set global emailSearchSucceeded - it should be per-form instance
// This will be set per form in initForm
}
/**
* Initialize form with Agile-Ed integration
* @param {jQuery} $form - jQuery form element
* @param {Object} ctx - HubSpot form context (optional)
*/
initForm($form, ctx) {
// CRITICAL: Wrap all initialization in try-catch to ensure form works even if API fails
// Never let API initialization errors prevent form from being usable
try {
this._initFormInternal($form, ctx);
} catch (error) {
// Log error but don't break form - allow manual data entry
// Form should work normally even if integration fails completely
if (typeof console !== 'undefined' && console.error) {
console.error('Agile-Ed form integration initialization error (form will work manually):', error);
}
// Form remains fully functional for manual data entry
}
}
_initFormInternal($form, ctx) {
debugLog('AgileEdFormIntegration.initForm called', {
formId: $form.attr('id'),
hasAgileEdAPI: !!this.agileEdAPI,
hasInstitutionWidget: !!this.institutionWidget
});
if (!this.agileEdAPI || !this.institutionWidget) {
debugWarn('AgileEdFormIntegration.initForm: Missing dependencies', {
agileEdAPI: !!this.agileEdAPI,
institutionWidget: !!this.institutionWidget
});
// Don't return - allow form to work manually even without dependencies
return;
}
// Fix: Create form-specific instance ID to isolate multiple forms
const formInstanceId = 'form_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
// CRITICAL: Initialize global form instances storage
if (typeof window !== 'undefined') {
if (!window.agileEdFormInstances) {
window.agileEdFormInstances = {};
}
}
// CRITICAL: Store original form HTML and context for restoration
const originalFormHTML = $form[0] ? $form[0].outerHTML : null;
const originalFormContext = ctx;
// CRITICAL: Extract HubSpot form configuration from context for re-creation
let hubspotFormConfig = null;
if (ctx) {
try {
hubspotFormConfig = {
formId: ctx.formId || (ctx.data && ctx.data.formId) || null,
portalId: ctx.portalId || (ctx.data && ctx.data.portalId) || null,
region: ctx.region || (ctx.data && ctx.data.region) || 'na1',
target: ctx.target || null
};
debugLog('Extracted HubSpot form config:', hubspotFormConfig);
} catch (e) {
debugWarn('Error extracting HubSpot form config:', e);
}
}
// CRITICAL: Store form's exact position in DOM for immediate restoration
let formParent = null;
let formNextSibling = null;
let formParentSelector = null;
let formContainerSelector = null;
try {
if ($form && $form[0] && $form[0].parentNode) {
formParent = $form[0].parentNode;
formNextSibling = $form[0].nextSibling;
// Store parent selector for recovery if reference is lost
try {
if (formParent.id) {
formParentSelector = '#' + formParent.id;
} else if (formParent.className) {
const classes = formParent.className.split(' ').filter(c => c).slice(0, 2).join('.');
if (classes) {
formParentSelector = '.' + classes;
}
}
// Also try to find a container (like iframe or form container)
let container = formParent;
for (let i = 0; i < 5 && container; i++) {
if (container.tagName === 'IFRAME' || container.id || (container.className && container.className.includes('hs-form'))) {
if (container.id) {
formContainerSelector = '#' + container.id;
} else if (container.className) {
const classes = container.className.split(' ').filter(c => c).slice(0, 2).join('.');
if (classes) {
formContainerSelector = '.' + classes;
}
}
break;
}
container = container.parentNode;
}
} catch (e) {
// Ignore selector generation errors
}
}
} catch (e) {
debugWarn('Error storing form position:', e);
}
const formData = {
instanceId: formInstanceId,
$form: $form,
originalFormHTML: originalFormHTML,
originalFormContext: originalFormContext,
hubspotFormConfig: hubspotFormConfig, // Store HubSpot config for re-creation
formParent: formParent,
formNextSibling: formNextSibling,
formParentSelector: formParentSelector, // Store selector for recovery
formContainerSelector: formContainerSelector, // Store container selector for recovery
formCloneWithStyles: null, // Will be cloned right before removal
computedStylesMap: null, // Will store computed styles before removal
autocompleteContainer: null,
cssMonitorInterval: null, // Interval ID for CSS monitoring
lastCssCheck: 0, // Timestamp of last CSS check
cssPreservedStyles: null, // Store preserved CSS styles for restoration
interactingWithAutocomplete: false,
emailSearchSucceeded: false,
emailInputActive: false, // Flag to prevent DOM manipulation during email input (shared between initEmailField and initCompanyField)
hubspotManipulating: false, // Flag to track when HubSpot is manipulating DOM
lastEmailInputTime: 0, // Track when email input last occurred
isRestoring: false, // Flag to prevent restoration loops
lastRestoreTime: 0, // Track when form was last restored
restoreCount: 0 // Track number of restorations to prevent infinite loops
};
// CRITICAL: Store form instance globally
if (typeof window !== 'undefined' && window.agileEdFormInstances) {
window.agileEdFormInstances[formInstanceId] = formData;
}
// CRITICAL: Function to clone form with computed styles right before removal
const cloneFormWithComputedStyles = function() {
try {
if ($form && $form[0] && $form[0].isConnected) {
// Clone the form element with all its children (deep clone)
const clonedForm = $form[0].cloneNode(true);
// Store computed styles for all elements in the form
const stylesMap = new Map();
const allElements = $form[0].querySelectorAll('*');
const clonedElements = clonedForm.querySelectorAll('*');
// Store computed styles for the form itself
const formComputedStyle = window.getComputedStyle($form[0]);
const formStyles = {};
for (let i = 0; i < formComputedStyle.length; i++) {
const prop = formComputedStyle[i];
formStyles[prop] = formComputedStyle.getPropertyValue(prop);
}
stylesMap.set(clonedForm, formStyles);
// Store computed styles for all child elements
allElements.forEach(function(originalEl, index) {
const clonedEl = clonedElements[index];
if (originalEl && clonedEl) {
const computedStyle = window.getComputedStyle(originalEl);
const styles = {};
for (let i = 0; i < computedStyle.length; i++) {
const prop = computedStyle[i];
styles[prop] = computedStyle.getPropertyValue(prop);
}
stylesMap.set(clonedEl, styles);
}
});
formData.formCloneWithStyles = clonedForm;
formData.computedStylesMap = stylesMap;
debugLog('Form cloned with computed styles for restoration');
}
} catch (e) {
debugWarn('Error cloning form with computed styles:', e);
}
};
// Store the clone function for use in MutationObserver
formData.cloneFormWithComputedStyles = cloneFormWithComputedStyles;
// CRITICAL: Set up periodic CSS monitoring to detect and fix CSS loss
const setupCssMonitor = function() {
// Function to re-apply CSS styles if they've been lost
const reapplyCssStyles = function() {
try {
if (!$form || !$form[0] || !$form[0].isConnected) {
return; // Form not available
}
// Check if form has lost critical styles
const formElement = $form[0];
const computedStyle = window.getComputedStyle(formElement);
const currentDisplay = computedStyle.display;
const currentVisibility = computedStyle.visibility;
const currentOpacity = computedStyle.opacity;
// If form is hidden or has lost visibility, restore it
if (currentDisplay === 'none' || currentVisibility === 'hidden' || parseFloat(currentOpacity) === 0) {
// Form has lost visibility - restore it
if (formData.cssPreservedStyles) {
// Restore from preserved styles
formElement.setAttribute('style', formData.cssPreservedStyles.formStyle || '');
} else {
// Apply default visible styles
formElement.style.display = 'block';
formElement.style.visibility = 'visible';
formElement.style.opacity = '1';
}
}
// Check and restore input field styles
const formInputs = $form.find('input, select, textarea, label');
formInputs.each(function() {
const el = this;
if (el.isConnected) {
const elComputed = window.getComputedStyle(el);
const elDisplay = elComputed.display;
const elVisibility = elComputed.visibility;
// If element is hidden, restore it
if (elDisplay === 'none' || elVisibility === 'hidden') {
if (formData.cssPreservedStyles && formData.cssPreservedStyles.elements && formData.cssPreservedStyles.elements[el]) {
el.setAttribute('style', formData.cssPreservedStyles.elements[el]);
} else {
el.style.display = '';
el.style.visibility = '';
}
}
}
});
} catch (e) {
// Silently handle errors - don't break form
}
};
// Store preserved CSS styles on first run
const preserveCssStyles = function() {
try {
if (!$form || !$form[0] || !$form[0].isConnected) {
return;
}
if (!formData.cssPreservedStyles) {
formData.cssPreservedStyles = {
formStyle: $form[0].getAttribute('style') || '',
elements: {},
originalHiddenState: {} // Store which fields were originally hidden
};
}
// Preserve styles for all form elements AND their original hidden state
const formInputs = $form.find('input, select, textarea, label, .hs-form-field, .hs-input');
const targetWindow = formData.iframeWindow || window;
formInputs.each(function() {
const el = this;
formData.cssPreservedStyles.elements[el] = el.getAttribute('style') || '';
// CRITICAL: Store original hidden state to prevent restoring intentionally hidden fields
const originalComputed = targetWindow.getComputedStyle(el);
const originalDisplay = originalComputed.display;
const originalVisibility = originalComputed.visibility;
const originalOpacity = parseFloat(originalComputed.opacity);
const originalRect = el.getBoundingClientRect();
const originalStyle = el.getAttribute('style') || '';
// Mark as originally hidden if:
// 1. Has hidden attribute or aria-hidden
// 2. Has hidden classes
// 3. Computed style shows hidden
// 4. Inline style explicitly sets display:none
const wasOriginallyHidden = el.hasAttribute('hidden') ||
el.getAttribute('aria-hidden') === 'true' ||
el.classList.contains('hs-hidden') ||
el.classList.contains('hidden') ||
el.classList.contains('sr-only') ||
el.classList.contains('visually-hidden') ||
originalDisplay === 'none' ||
originalVisibility === 'hidden' ||
originalOpacity === 0 ||
(originalRect.width === 0 && originalRect.height === 0) ||
originalStyle.includes('display:none') ||
originalStyle.includes('display: none');
formData.cssPreservedStyles.originalHiddenState[el] = wasOriginallyHidden;
});
} catch (e) {
// Silently handle errors
}
};
// Preserve styles initially
preserveCssStyles();
// Set up periodic monitoring (check every 2 seconds normally, every 500ms during email editing)
if (formData.cssMonitorInterval) {
clearInterval(formData.cssMonitorInterval);
}
const runCssCheck = function() {
// Only check if form is still connected
if ($form && $form[0] && $form[0].isConnected) {
reapplyCssStyles();
// Re-preserve styles periodically to capture any updates
preserveCssStyles();
// If email input is active, check more frequently
if (formData && formData.emailInputActive) {
// During email editing, check every 500ms
if (formData.cssMonitorInterval) {
clearInterval(formData.cssMonitorInterval);
}
formData.cssMonitorInterval = setTimeout(runCssCheck, 500);
} else {
// Normal monitoring - check every 2 seconds
if (formData.cssMonitorInterval) {
clearInterval(formData.cssMonitorInterval);
}
formData.cssMonitorInterval = setTimeout(runCssCheck, 2000);
}
} else {
// Form removed - clear interval
if (formData.cssMonitorInterval) {
clearInterval(formData.cssMonitorInterval);
formData.cssMonitorInterval = null;
}
}
};
// Start monitoring
formData.cssMonitorInterval = setTimeout(runCssCheck, 500); // Start with frequent check
// Also monitor for style attribute changes via MutationObserver
if ($form && $form[0]) {
const styleObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
// Style attribute changed - check if it was removed or if field became hidden
const target = mutation.target;
if (target === $form[0] || $form.find(target).length) {
const currentStyle = target.getAttribute('style');
if (!currentStyle || currentStyle.trim() === '') {
// Style was removed - restore it
if (formData.cssPreservedStyles) {
if (target === $form[0] && formData.cssPreservedStyles.formStyle) {
target.setAttribute('style', formData.cssPreservedStyles.formStyle);
} else if (formData.cssPreservedStyles.elements && formData.cssPreservedStyles.elements[target]) {
target.setAttribute('style', formData.cssPreservedStyles.elements[target]);
}
}
} else {
// Style was changed - check if field became hidden
// Only check form fields, not the form element itself
const isFormField = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' ||
target.tagName === 'SELECT' || target.tagName === 'LABEL' ||
target.classList.contains('hs-form-field') || target.classList.contains('hs-input');
if (isFormField && target.type !== 'hidden') {
const targetWindow = formData.iframeWindow || window;
const computedStyle = targetWindow.getComputedStyle(target);
if (computedStyle.display === 'none' || computedStyle.visibility === 'hidden' || parseFloat(computedStyle.opacity) === 0) {
// Field became hidden - restore it immediately
let fieldStyle = target.getAttribute('style') || '';
if (fieldStyle && !fieldStyle.endsWith(';')) {
fieldStyle += ';';
}
if (computedStyle.display === 'none') {
fieldStyle += 'display: block !important; ';
}
if (computedStyle.visibility === 'hidden') {
fieldStyle += 'visibility: visible !important; ';
}
if (parseFloat(computedStyle.opacity) === 0) {
fieldStyle += 'opacity: 1 !important; ';
}
if (fieldStyle) {
target.setAttribute('style', fieldStyle);
// #region agent log
debugInstrument('agile-ed-form-integration.js:680', 'Form field hidden detected by MutationObserver - restored', {
fieldTag:target.tagName,
fieldType:target.type,
fieldClass:target.className
}, 'CSS1');
// #endregion
}
}
}
}
}
}
// Handle class changes - HubSpot might be adding classes that hide fields
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const target = mutation.target;
if (target === $form[0] || $form.find(target).length) {
const isFormField = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' ||
target.tagName === 'SELECT' || target.tagName === 'LABEL' ||
target.classList.contains('hs-form-field') || target.classList.contains('hs-input');
if (isFormField && target.type !== 'hidden') {
const targetWindow = formData.iframeWindow || window;
const computedStyle = targetWindow.getComputedStyle(target);
if (computedStyle.display === 'none' || computedStyle.visibility === 'hidden' || parseFloat(computedStyle.opacity) === 0) {
// Field became hidden via class change - restore it immediately
let fieldStyle = target.getAttribute('style') || '';
if (fieldStyle && !fieldStyle.endsWith(';')) {
fieldStyle += ';';
}
if (computedStyle.display === 'none') {
fieldStyle += 'display: block !important; ';
}
if (computedStyle.visibility === 'hidden') {
fieldStyle += 'visibility: visible !important; ';
}
if (parseFloat(computedStyle.opacity) === 0) {
fieldStyle += 'opacity: 1 !important; ';
}
if (fieldStyle) {
target.style.setProperty('display', 'block', 'important');
target.style.setProperty('visibility', 'visible', 'important');
target.style.setProperty('opacity', '1', 'important');
// #region agent log
debugInstrument('agile-ed-form-integration.js:720', 'Form field hidden via class change - restored', {
fieldTag:target.tagName,
fieldType:target.type,
fieldClass:target.className
}, 'CSS1');
// #endregion
}
}
}
}
}
// Handle childList changes - check if form fields were added/removed
if (mutation.type === 'childList') {
// Check all added nodes
mutation.addedNodes.forEach(function(node) {
if (node.nodeType === 1) { // Element node
const isFormField = node.tagName === 'INPUT' || node.tagName === 'TEXTAREA' ||
node.tagName === 'SELECT' || node.tagName === 'LABEL' ||
node.classList.contains('hs-form-field') || node.classList.contains('hs-input');
if (isFormField && node.type !== 'hidden' && node.isConnected) {
const targetWindow = formData.iframeWindow || window;
const computedStyle = targetWindow.getComputedStyle(node);
if (computedStyle.display === 'none' || computedStyle.visibility === 'hidden' || parseFloat(computedStyle.opacity) === 0) {
// Newly added field is hidden - restore it
node.style.setProperty('display', 'block', 'important');
node.style.setProperty('visibility', 'visible', 'important');
node.style.setProperty('opacity', '1', 'important');
// #region agent log
debugInstrument('agile-ed-form-integration.js:740', 'Newly added form field was hidden - restored', {
fieldTag:node.tagName,
fieldType:node.type,
fieldClass:node.className
}, 'CSS1');
// #endregion
}
}
}
});
}
// Handle class changes - HubSpot might be adding classes that hide fields
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const target = mutation.target;
if (target === $form[0] || $form.find(target).length) {
const isFormField = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' ||
target.tagName === 'SELECT' || target.tagName === 'LABEL' ||
target.classList.contains('hs-form-field') || target.classList.contains('hs-input');
if (isFormField && target.type !== 'hidden') {
const targetWindow = formData.iframeWindow || window;
const computedStyle = targetWindow.getComputedStyle(target);
if (computedStyle.display === 'none' || computedStyle.visibility === 'hidden' || parseFloat(computedStyle.opacity) === 0) {
// Field became hidden via class change - restore it immediately
target.style.setProperty('display', 'block', 'important');
target.style.setProperty('visibility', 'visible', 'important');
target.style.setProperty('opacity', '1', 'important');
// #region agent log
debugInstrument('agile-ed-form-integration.js:720', 'Form field hidden via class change - restored', {
fieldTag:target.tagName,
fieldType:target.type,
fieldClass:target.className
}, 'CSS1');
// #endregion
}
}
}
}
// Handle childList changes - check if form fields were added/removed
if (mutation.type === 'childList') {
// Check all added nodes
mutation.addedNodes.forEach(function(node) {
if (node.nodeType === 1) { // Element node
const isFormField = node.tagName === 'INPUT' || node.tagName === 'TEXTAREA' ||
node.tagName === 'SELECT' || node.tagName === 'LABEL' ||
node.classList.contains('hs-form-field') || node.classList.contains('hs-input');
if (isFormField && node.type !== 'hidden' && node.isConnected) {
const targetWindow = formData.iframeWindow || window;
const computedStyle = targetWindow.getComputedStyle(node);
if (computedStyle.display === 'none' || computedStyle.visibility === 'hidden' || parseFloat(computedStyle.opacity) === 0) {
// Newly added field is hidden - restore it
node.style.setProperty('display', 'block', 'important');
node.style.setProperty('visibility', 'visible', 'important');
node.style.setProperty('opacity', '1', 'important');
// #region agent log
debugInstrument('agile-ed-form-integration.js:740', 'Newly added form field was hidden - restored', {
fieldTag:node.tagName,
fieldType:node.type,
fieldClass:node.className
}, 'CSS1');
// #endregion
}
}
}
});
}
});
});
styleObserver.observe($form[0], {
attributes: true,
attributeFilter: ['style', 'class'], // Also watch for class changes
childList: true, // Watch for elements being added/removed
subtree: true
});
// Store observer for cleanup
formData.styleObserver = styleObserver;
}
};
// Initialize CSS monitoring
setupCssMonitor();
// CRITICAL: Inject minimal CSS protection to prevent form from losing visibility
// Only inject essential protection rules, NOT duplicate all stylesheets
// This prevents CSS duplication and maintains original form design
const injectHubSpotFormCSS = function() {
try {
if (!$form || !$form[0] || !$form[0].isConnected) {
return;
}
const formElement = $form[0];
let iframeWindow = null;
let iframeDocument = null;
let targetHead = null;
let targetDocument = null;
// Detect if form is in iframe
if (formElement.ownerDocument !== document) {
// Form is in a different document (likely iframe)
iframeDocument = formElement.ownerDocument;
iframeWindow = iframeDocument.defaultView || iframeDocument.parentWindow;
targetHead = iframeDocument.head;
targetDocument = iframeDocument;
} else {
// Check if form's parent is an iframe
let parent = formElement.parentElement;
while (parent && parent !== document.body) {
if (parent.tagName === 'IFRAME') {
try {
iframeWindow = parent.contentWindow;
iframeDocument = parent.contentDocument;
if (iframeDocument && iframeDocument.head) {
targetHead = iframeDocument.head;
targetDocument = iframeDocument;
}
break;
} catch (e) {
// Cross-origin iframe - can't access
break;
}
}
parent = parent.parentElement;
}
// If not in iframe, use main document
if (!targetHead) {
targetHead = document.head;
targetDocument = document;
}
}
if (!targetHead || !targetDocument) {
return; // Can't access head
}
// CRITICAL: Only inject MINIMAL protection CSS
// Do NOT duplicate all stylesheets - this causes duplicate styling and design issues
// Only protect against visibility/display loss, not override all styles
// Use :not() selectors to avoid affecting elements that are intentionally hidden
const protectionCSS = `
/* Minimal CSS protection - only prevent visibility loss for visible elements */
/* CRITICAL: Protect form element itself - must come first to override any other rules */
form.hs-form,
.hs-form,
form[id*="hsForm"],
form[class*="hs-form"] {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
/* Additional protection for form elements that might be hidden by other CSS */
.hs-form:not([style*="display: none"]):not([style*="display:none"]) {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
/* CRITICAL: Protect all form fields - must be strong to override HubSpot's hiding CSS */
/* Use high specificity selectors to override HubSpot's CSS */
/* EXCLUDE checkbox inputs to preserve their flexbox layout */
form.hs-form input:not([type="hidden"]):not([type="checkbox"]):not([style*="display: none"]):not([style*="display:none"]),
form.hs-form select:not([style*="display: none"]):not([style*="display:none"]),
form.hs-form textarea:not([style*="display: none"]):not([style*="display:none"]),
.hs-form input:not([type="hidden"]):not([type="checkbox"]):not([style*="display: none"]):not([style*="display:none"]),
.hs-form select:not([style*="display: none"]):not([style*="display:none"]),
.hs-form textarea:not([style*="display: none"]):not([style*="display:none"]),
form[id*="hsForm"] input:not([type="hidden"]):not([type="checkbox"]):not([style*="display: none"]):not([style*="display:none"]),
form[id*="hsForm"] select:not([style*="display: none"]):not([style*="display:none"]),
form[id*="hsForm"] textarea:not([style*="display: none"]):not([style*="display:none"]) {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
/* EXCLUDE checkbox labels to preserve flexbox layout */
.hs-form label:not(.hs-form-checkbox-display):not([style*="display: none"]):not([style*="display:none"]):not(:has(input[type="checkbox"])),
form.hs-form label:not(.hs-form-checkbox-display):not([style*="display: none"]):not([style*="display:none"]):not(:has(input[type="checkbox"])) {
display: block !important;
visibility: visible !important;
}
/* CRITICAL: Protect form field containers - these wrap the inputs */
.hs-form-field:not([style*="display: none"]):not([style*="display:none"]):not(:has(input[type="hidden"])),
form.hs-form .hs-form-field:not([style*="display: none"]):not([style*="display:none"]):not(:has(input[type="hidden"])) {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
/* EXCLUDE checkbox inputs from .hs-input rule to preserve flexbox layout */
.hs-input:not([type="hidden"]):not([type="checkbox"]):not([style*="display: none"]):not([style*="display:none"]),
form.hs-form .hs-input:not([type="hidden"]):not([type="checkbox"]):not([style*="display: none"]):not([style*="display:none"]) {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
/* CRITICAL: Protect submit buttons and buttons in general */
.hs-form button:not([style*="display: none"]):not([style*="display:none"]),
.hs-form input[type="submit"]:not([style*="display: none"]):not([style*="display:none"]),
.hs-button:not([style*="display: none"]):not([style*="display:none"]),
form.hs-form button:not([style*="display: none"]):not([style*="display:none"]),
form.hs-form input[type="submit"]:not([style*="display: none"]):not([style*="display:none"]) {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
/* CRITICAL: Ensure hidden fields and hidden form fields stay hidden */
input[type="hidden"],
.hs-form input[type="hidden"],
.hs-form-field:has(input[type="hidden"]),
.hs-form-field[style*="display: none"],
.hs-form-field[style*="display:none"] {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
height: 0 !important;
width: 0 !important;
padding: 0 !important;
margin: 0 !important;
border: none !important;
overflow: hidden !important;
}
`;
// Store references
formData.iframeDocument = iframeDocument;
formData.iframeWindow = iframeWindow;
formData.targetHead = targetHead;
formData.targetDocument = targetDocument;
formData.hubspotFormCSS = protectionCSS;
// Function to update CSS in target
const updateCSSInTarget = function() {
try {
if (!targetHead || !targetDocument) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:734', 'updateCSSInTarget - no target head/document', {hasTargetHead:!!targetHead,hasTargetDocument:!!targetDocument,isIframe:!!iframeDocument}, 'CSS1');
// #endregion
return;
}
// Check current state of CSS tag
let cssStyleTag = targetDocument.getElementById('agile-ed-hubspot-form-css');
const cssTagExists = !!cssStyleTag;
const cssTagInHead = cssStyleTag && targetHead.contains(cssStyleTag);
// #region agent log
debugInstrument('agile-ed-form-integration.js:739', 'updateCSSInTarget - checking CSS tag', {cssTagExists,cssTagInHead,isIframe:!!iframeDocument,headChildCount:targetHead.children.length}, 'CSS1');
// #endregion
// Create or update style tag
if (!cssStyleTag) {
cssStyleTag = targetDocument.createElement('style');
cssStyleTag.id = 'agile-ed-hubspot-form-css';
cssStyleTag.type = 'text/css';
targetHead.appendChild(cssStyleTag);
// #region agent log
debugInstrument('agile-ed-form-integration.js:744', 'updateCSSInTarget - created new CSS tag', {isIframe:!!iframeDocument}, 'CSS1');
// #endregion
}
// Check form computed styles before injection
const formComputedBefore = formElement ? (iframeWindow || window).getComputedStyle(formElement) : null;
const formDisplayBefore = formComputedBefore ? formComputedBefore.display : 'unknown';
const formVisibilityBefore = formComputedBefore ? formComputedBefore.visibility : 'unknown';
// Only inject minimal protection CSS
cssStyleTag.textContent = protectionCSS;
// CRITICAL: Set up MutationObserver to watch for CSS tag removal
// This ensures CSS is immediately re-injected if HubSpot removes it
if (!formData.cssTagObserver && targetHead) {
formData.cssTagObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList') {
// Check if CSS tag was removed
mutation.removedNodes.forEach(function(node) {
if (node.nodeType === 1 && (node.id === 'agile-ed-hubspot-form-css' || node === cssStyleTag)) {
// CSS tag was removed - re-inject immediately
// #region agent log
debugInstrument('agile-ed-form-integration.js:1000', 'CSS tag removed - re-injecting immediately', {
tagId:node.id,
tagInHead:targetHead.contains(node)
}, 'CSS11');
// #endregion
// Re-inject CSS immediately
if (formData && formData.injectHubSpotFormCSS && typeof formData.injectHubSpotFormCSS === 'function') {
try {
formData.injectHubSpotFormCSS();
} catch (e) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:1010', 'Error re-injecting CSS after removal', {errorMessage:e?.message}, 'CSS11');
// #endregion
}
}
}
});
// Also check if stylesheets were removed
mutation.removedNodes.forEach(function(node) {
if (node.nodeType === 1 && (node.tagName === 'LINK' || node.tagName === 'STYLE')) {
// A stylesheet was removed - re-inject CSS to ensure protection is still active
if (formData && formData.injectHubSpotFormCSS && typeof formData.injectHubSpotFormCSS === 'function') {
setTimeout(function() {
if (formData && formData.injectHubSpotFormCSS) {
formData.injectHubSpotFormCSS();
// #region agent log
debugInstrument('agile-ed-form-integration.js:1025', 'CSS re-injected after stylesheet removal', {
removedTag:node.tagName,
removedId:node.id,
removedHref:node.href
}, 'CSS11');
// #endregion
}
}, 50);
}
}
});
}
});
});
// Observe head for childList changes (when tags are added/removed)
formData.cssTagObserver.observe(targetHead, {
childList: true,
subtree: false
});
// #region agent log
debugInstrument('agile-ed-form-integration.js:1040', 'CSS tag MutationObserver set up', {hasTargetHead:!!targetHead}, 'CSS11');
// #endregion
}
// CRITICAL: After injecting CSS, immediately check and restore form field visibility
// Use requestAnimationFrame to ensure this runs after HubSpot's CSS is applied
const checkAndRestoreFormFields = function() {
let hiddenAfterInjection = 0;
if ($form && $form[0]) {
const formFields = $form.find('input:not([type="hidden"]), textarea, select, label, .hs-form-field, .hs-input');
formFields.each(function() {
const field = this;
if (field.isConnected) {
// CRITICAL: Check if field was originally hidden - if so, skip restoration
const wasOriginallyHidden = formData.cssPreservedStyles &&
formData.cssPreservedStyles.originalHiddenState &&
formData.cssPreservedStyles.originalHiddenState[field] === true;
if (wasOriginallyHidden) {
// Field was originally hidden - keep it hidden, don't restore
return;
}
// CRITICAL: Skip hidden input fields - they should stay hidden
if (field.tagName === 'INPUT' && field.type === 'hidden') {
return; // Skip this field
}
// CRITICAL: Check if field's original inline style had display:none
// This indicates it was intentionally hidden by design
const originalStyle = formData.cssPreservedStyles &&
formData.cssPreservedStyles.elements &&
formData.cssPreservedStyles.elements[field];
if (originalStyle && (originalStyle.includes('display:none') || originalStyle.includes('display: none'))) {
// Original style had display:none - keep it hidden
return;
}
const fieldComputed = (iframeWindow || window).getComputedStyle(field);
if (fieldComputed.display === 'none' || fieldComputed.visibility === 'hidden' || parseFloat(fieldComputed.opacity) === 0) {
// Field is hidden - force it visible with inline styles
const beforeDisplay = fieldComputed.display;
const beforeVisibility = fieldComputed.visibility;
const beforeOpacity = fieldComputed.opacity;
let fieldStyle = field.getAttribute('style') || '';
if (fieldStyle && !fieldStyle.endsWith(';')) {
fieldStyle += ';';
}
if (fieldComputed.display === 'none') {
fieldStyle += 'display: block !important; ';
}
if (fieldComputed.visibility === 'hidden') {
fieldStyle += 'visibility: visible !important; ';
}
if (parseFloat(fieldComputed.opacity) === 0) {
fieldStyle += 'opacity: 1 !important; ';
}
if (fieldStyle) {
field.setAttribute('style', fieldStyle);
// Force a reflow
field.offsetHeight;
// Verify the style was applied
const afterComputed = (iframeWindow || window).getComputedStyle(field);
if (afterComputed.display === 'none' || afterComputed.visibility === 'hidden' || parseFloat(afterComputed.opacity) === 0) {
// setAttribute didn't work - use style property directly
if (afterComputed.display === 'none') {
field.style.setProperty('display', 'block', 'important');
}
if (afterComputed.visibility === 'hidden') {
field.style.setProperty('visibility', 'visible', 'important');
}
if (parseFloat(afterComputed.opacity) === 0) {
field.style.setProperty('opacity', '1', 'important');
}
// Force another reflow
field.offsetHeight;
}
}
hiddenAfterInjection++;
}
}
});
if (hiddenAfterInjection > 0) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:880', 'Form fields hidden after CSS injection - restored', {
hiddenAfterInjection,
totalFields:formFields.length
}, 'CSS1');
// #endregion
}
}
return hiddenAfterInjection;
};
// Check immediately
let hiddenAfterInjection = checkAndRestoreFormFields();
// Also check after multiple delays to catch any delayed CSS application by HubSpot
// HubSpot may hide fields after our initial restoration
setTimeout(function() {
const hidden = checkAndRestoreFormFields();
if (hidden > 0) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:890', 'Form fields hidden after 50ms delay - restored', {
hiddenAfterDelay:hidden
}, 'CSS1');
// #endregion
}
}, 50);
// Check again after 150ms to catch any further HubSpot manipulation
setTimeout(function() {
const hidden = checkAndRestoreFormFields();
if (hidden > 0) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:900', 'Form fields hidden after 150ms delay - restored', {
hiddenAfterDelay:hidden
}, 'CSS1');
// #endregion
}
}, 150);
// Check again after 300ms to catch any final HubSpot manipulation
setTimeout(function() {
const hidden = checkAndRestoreFormFields();
if (hidden > 0) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:910', 'Form fields hidden after 300ms delay - restored', {
hiddenAfterDelay:hidden
}, 'CSS1');
// #endregion
}
}, 300);
// Check form computed styles after injection
const formComputedAfter = formElement ? (iframeWindow || window).getComputedStyle(formElement) : null;
const formDisplayAfter = formComputedAfter ? formComputedAfter.display : 'unknown';
const formVisibilityAfter = formComputedAfter ? formComputedAfter.visibility : 'unknown';
// #region agent log
debugInstrument('agile-ed-form-integration.js:748', 'updateCSSInTarget - CSS injected', {
isIframe:!!iframeDocument,
formDisplayBefore,
formVisibilityBefore,
formDisplayAfter,
formVisibilityAfter,
cssTagExists:!!cssStyleTag,
cssTagInHead:targetHead.contains(cssStyleTag),
hiddenFieldsAfterInjection:hiddenAfterInjection
}, 'CSS1');
// #endregion
debugLog('Minimal HubSpot form CSS protection injected into ' + (iframeDocument ? 'iframe' : 'document') + ' head');
// Monitor for removal
if (!formData.hubspotCssObserver) {
const cssObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.removedNodes.forEach((node) => {
if (node === cssStyleTag || (node.id === 'agile-ed-hubspot-form-css')) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:758', 'CSS tag removed - re-injecting', {nodeId:node?.id,isIframe:!!iframeDocument,emailInputActive:formData?.emailInputActive}, 'CSS2');
// #endregion
// CSS tag removed - re-inject immediately
setTimeout(() => {
updateCSSInTarget();
}, 50);
}
});
}
});
});
cssObserver.observe(targetHead, {
childList: true,
subtree: false
});
formData.hubspotCssObserver = cssObserver;
// #region agent log
debugInstrument('agile-ed-form-integration.js:774', 'CSS observer set up', {isIframe:!!iframeDocument}, 'CSS1');
// #endregion
}
} catch (e) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:777', 'updateCSSInTarget - error', {errorMessage:e?.message,isIframe:!!iframeDocument}, 'CSS1');
// #endregion
debugWarn('Error updating CSS in target:', e);
}
};
// Initial injection
updateCSSInTarget();
} catch (e) {
debugWarn('Error injecting HubSpot form CSS:', e);
}
};
// Inject CSS on form initialization
injectHubSpotFormCSS();
// Store function for re-injection during email editing
formData.injectHubSpotFormCSS = injectHubSpotFormCSS;
// CRITICAL: Capture HubSpot stylesheets on initialization
const captureHubSpotStylesheets = function() {
try {
const targetDoc = formData.targetDocument || document;
const targetHead = formData.targetHead || document.head;
if (!targetHead || !targetDoc) {
return;
}
// Capture all stylesheets
const stylesheets = [];
const styleTags = targetHead.querySelectorAll('style, link[rel="stylesheet"]');
styleTags.forEach(function(tag, index) {
if (tag.id !== 'agile-ed-hubspot-form-css') { // Don't capture our own CSS
// Generate a unique identifier for stylesheets without IDs/hrefs
let uniqueId = tag.id || tag.href || null;
if (!uniqueId && tag.tagName === 'STYLE') {
// For style tags without ID, use textContent hash or position
const textContent = tag.textContent || '';
if (textContent.trim()) {
// Generate a simple hash from textContent (first 50 chars + length)
uniqueId = 'style-' + textContent.substring(0, 50).replace(/[^a-zA-Z0-9]/g, '') + '-' + textContent.length;
} else {
// Empty style tag - use position as identifier
uniqueId = 'style-empty-' + index;
}
}
stylesheets.push({
tag: tag.tagName,
id: tag.id || null,
href: tag.href || null,
textContent: tag.textContent || '',
uniqueId: uniqueId, // Always have a unique identifier
index: index // Store position for matching
});
}
});
formData.capturedStylesheets = stylesheets;
// #region agent log
debugInstrument('agile-ed-form-integration.js:800', 'Captured HubSpot stylesheets', {stylesheetsCount:stylesheets.length,isIframe:!!formData.iframeDocument}, 'CSS6');
// #endregion
} catch (e) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:805', 'Error capturing stylesheets', {errorMessage:e?.message}, 'CSS6');
// #endregion
}
};
// Capture stylesheets on initialization
captureHubSpotStylesheets();
formData.captureHubSpotStylesheets = captureHubSpotStylesheets;
// CRITICAL: Set up continuous CSS monitoring during email editing
// This proactively prevents CSS loss instead of reactively fixing it
let cssMonitorInterval = null;
const startCssMonitoring = function() {
// #region agent log
debugInstrument('agile-ed-form-integration.js:820', 'startCssMonitoring called', {alreadyMonitoring:!!cssMonitorInterval,emailInputActive:formData?.emailInputActive}, 'CSS7');
// #endregion
if (cssMonitorInterval) {
return; // Already monitoring
}
// #region agent log
debugInstrument('agile-ed-form-integration.js:826', 'Starting CSS monitoring interval', {emailInputActive:formData?.emailInputActive}, 'CSS7');
// #endregion
// Monitor every 50ms during email editing (faster to catch HubSpot hiding fields)
cssMonitorInterval = setInterval(function() {
try {
// #region agent log
debugInstrument('agile-ed-form-integration.js:890', 'CSS monitoring interval tick', {hasFormData:!!formData,emailInputActive:formData?.emailInputActive,hasForm:!!$form,formConnected:$form?.[0]?.isConnected}, 'CSS5');
// #endregion
if (!formData || !formData.emailInputActive) {
// Email editing stopped - stop monitoring
if (cssMonitorInterval) {
clearInterval(cssMonitorInterval);
cssMonitorInterval = null;
}
return;
}
if (!$form || !$form[0]) {
return;
}
const formElement = $form[0];
// Track form connection state to detect reconnection
const wasConnected = formData.formWasConnected !== false; // Default to true on first check
const isConnected = formElement.isConnected;
// #region agent log
debugInstrument('agile-ed-form-integration.js:908', 'CSS monitoring - checking form connection', {wasConnected,isConnected,hasFormParent:!!formData.formParent}, 'CSS5');
// #endregion
// CRITICAL: If form was disconnected and is now reconnected, restore CSS immediately
if (!wasConnected && isConnected) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:914', 'Form reconnected - restoring CSS immediately', {}, 'CSS5');
// #endregion
// Form was reconnected - restore CSS immediately
if (formData.injectHubSpotFormCSS && typeof formData.injectHubSpotFormCSS === 'function') {
formData.injectHubSpotFormCSS();
}
// Restore form styles
if (formData.cssPreservedStyles && formData.cssPreservedStyles.formStyle) {
formElement.setAttribute('style', formData.cssPreservedStyles.formStyle);
} else if (formData.originalFormStyle) {
formElement.setAttribute('style', formData.originalFormStyle);
}
// Also restore all form input styles
const formInputs = $form.find('input:not([type="hidden"]), textarea, select, label, .hs-form-field, .hs-input');
formInputs.each(function() {
const el = this;
if (el.isConnected && formData.cssPreservedStyles && formData.cssPreservedStyles.elements && formData.cssPreservedStyles.elements[el]) {
el.setAttribute('style', formData.cssPreservedStyles.elements[el]);
}
});
}
// Update connection state
formData.formWasConnected = isConnected;
// CRITICAL: If form is disconnected, re-append it immediately
if (!formElement.isConnected) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:947', 'Form disconnected during email editing - re-appending', {hasFormParent:!!formData.formParent,hasFormNextSibling:!!formData.formNextSibling,hasParentSelector:!!formData.formParentSelector}, 'CSS5');
// #endregion
// Try to recover formParent if it's lost
let formParent = formData.formParent;
if (!formParent || !formParent.isConnected) {
// Try to find parent using selector
if (formData.formParentSelector) {
try {
const targetDoc = formData.targetDocument || document;
const foundParent = targetDoc.querySelector(formData.formParentSelector);
if (foundParent && foundParent.isConnected) {
formParent = foundParent;
formData.formParent = formParent; // Update stored reference
// #region agent log
debugInstrument('agile-ed-form-integration.js:960', 'Recovered formParent using selector', {selector:formData.formParentSelector}, 'CSS5');
// #endregion
}
} catch (e) {
// Ignore
}
}
// If still not found, try container selector
if (!formParent && formData.formContainerSelector) {
try {
const targetDoc = formData.targetDocument || document;
const foundContainer = targetDoc.querySelector(formData.formContainerSelector);
if (foundContainer && foundContainer.isConnected) {
// Try to find form within container
const foundForm = foundContainer.querySelector('form');
if (foundForm && foundForm.parentNode) {
formParent = foundForm.parentNode;
formData.formParent = formParent;
// #region agent log
debugInstrument('agile-ed-form-integration.js:978', 'Recovered formParent using container selector', {selector:formData.formContainerSelector}, 'CSS5');
// #endregion
}
}
} catch (e) {
// Ignore
}
}
// Last resort: try to find form's iframe and use its body
if (!formParent) {
try {
const targetDoc = formData.targetDocument || document;
const iframe = targetDoc.defaultView ? targetDoc.defaultView.frameElement : null;
if (iframe && iframe.contentDocument && iframe.contentDocument.body) {
formParent = iframe.contentDocument.body;
formData.formParent = formParent;
// #region agent log
debugInstrument('agile-ed-form-integration.js:995', 'Recovered formParent using iframe body', {}, 'CSS5');
// #endregion
}
} catch (e) {
// Ignore
}
}
}
// Re-append form to DOM immediately
// CRITICAL: Check if form is already connected before re-appending to prevent duplicates
if (formParent && formParent.appendChild && !formElement.isConnected) {
try {
// Double-check: form might have been reconnected by HubSpot between check and re-append
if (formElement.isConnected) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:1046', 'Form already reconnected by HubSpot - skipping re-append', {isConnected:formElement.isConnected}, 'CSS5');
// #endregion
return; // Form is already connected, don't re-append
}
// Temporarily disconnect any observers to prevent loops
if (formData.formObserver) {
try {
formData.formObserver.disconnect();
} catch (e) {
// Ignore
}
}
// CRITICAL: Check if form is already a child of formParent to prevent duplicates
const isAlreadyChild = formParent.contains && formParent.contains(formElement);
if (isAlreadyChild) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:1058', 'Form already a child of formParent - skipping re-append', {isConnected:formElement.isConnected}, 'CSS5');
// #endregion
// Re-connect observer and return
if (formData.formObserver && formParent) {
try {
formData.formObserver.observe(formParent, {
childList: true,
subtree: true
});
} catch (e) {
// Ignore
}
}
return; // Form is already in the right place
}
// CRITICAL: Check if HubSpot has already created a duplicate form in the parent
// If form has an ID, check for existing form with same ID
const doc = formData.targetDocument || document;
const formId = formElement.id;
let existingForm = null;
// First, check if form is already in the document somewhere (even if not in formParent)
if (formId) {
const existingFormById = doc.getElementById(formId);
if (existingFormById && existingFormById !== formElement && existingFormById.isConnected) {
existingForm = existingFormById;
}
}
// If not found by ID, check in formParent for forms with same class/structure
if (!existingForm) {
const allFormsInParent = formParent.querySelectorAll('form');
if (allFormsInParent.length > 0) {
// Check if any form has the same structure/attributes
for (let i = 0; i < allFormsInParent.length; i++) {
const f = allFormsInParent[i];
// If forms have same class or similar structure, it might be a duplicate
if (f.className === formElement.className && f !== formElement) {
existingForm = f;
break;
}
}
}
}
// If duplicate form exists, remove it first
if (existingForm && existingForm !== formElement) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:1087', 'Duplicate form detected - removing before re-append', {
formId:formId,
existingFormId:existingForm.id,
existingFormConnected:existingForm.isConnected,
existingFormParent:existingForm.parentNode ? existingForm.parentNode.nodeName : 'none'
}, 'CSS5');
// #endregion
try {
if (existingForm.parentNode) {
existingForm.parentNode.removeChild(existingForm);
}
} catch (e) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:1105', 'Error removing duplicate form', {errorMessage:e?.message}, 'CSS5');
// #endregion
}
}
// Re-append form to exact same position
if (formData.formNextSibling && formData.formNextSibling.parentNode === formParent) {
formParent.insertBefore(formElement, formData.formNextSibling);
} else {
formParent.appendChild(formElement);
}
// Re-connect observer
if (formData.formObserver && formParent) {
try {
formData.formObserver.observe(formParent, {
childList: true,
subtree: true
});
} catch (e) {
// Ignore
}
}
// Update stored formParent reference
formData.formParent = formParent;
// CRITICAL: Restore CSS immediately after re-appending
// Re-inject CSS FIRST (before restoring inline styles) to ensure stylesheets are available
if (formData.injectHubSpotFormCSS && typeof formData.injectHubSpotFormCSS === 'function') {
formData.injectHubSpotFormCSS(); // Immediate, no delay
}
// Restore form styles
if (formData.cssPreservedStyles && formData.cssPreservedStyles.formStyle) {
formElement.setAttribute('style', formData.cssPreservedStyles.formStyle);
} else if (formData.originalFormStyle) {
formElement.setAttribute('style', formData.originalFormStyle);
}
// Ensure form visibility with !important
const targetDoc = formData.targetDocument || document;
// #region agent log
// Check for duplicate forms/buttons after re-appending
const allForms = targetDoc.querySelectorAll('form');
const formButtons = formElement.querySelectorAll('button[type="submit"], input[type="submit"], .hs-button, [class*="button"]');
const allButtons = targetDoc.querySelectorAll('button[type="submit"], input[type="submit"], .hs-button, [class*="button"]');
debugInstrument('agile-ed-form-integration.js:1081', 'Form re-appended successfully', {
isConnected:formElement.isConnected,
totalFormsInDoc:allForms.length,
buttonsInForm:formButtons.length,
totalButtonsInDoc:allButtons.length
}, 'CSS5');
// #endregion
const targetWindow = formData.iframeWindow || window;
const computedStyle = targetWindow.getComputedStyle(formElement);
let formStyle = formElement.getAttribute('style') || '';
if (formStyle && !formStyle.endsWith(';')) {
formStyle += ';';
}
if (computedStyle.display === 'none') {
formStyle += 'display: block !important; ';
}
if (computedStyle.visibility === 'hidden') {
formStyle += 'visibility: visible !important; ';
}
if (parseFloat(computedStyle.opacity) === 0) {
formStyle += 'opacity: 1 !important; ';
}
if (formStyle) {
formElement.setAttribute('style', formStyle);
}
// CRITICAL: Restore all form input styles and ensure visibility
// Use multiple passes to ensure all fields are restored
const formInputs = $form.find('input:not([type="hidden"]):not([type="submit"]):not([type="button"]), textarea, select, label, .hs-form-field, .hs-input');
let hiddenFieldsCount = 0;
let visibleFieldsCount = 0;
// First pass: Restore preserved styles and basic visibility
formInputs.each(function() {
const el = this;
if (el.isConnected) {
// CRITICAL: Check if field was originally hidden - if so, skip restoration
const wasOriginallyHidden = formData.cssPreservedStyles &&
formData.cssPreservedStyles.originalHiddenState &&
formData.cssPreservedStyles.originalHiddenState[el] === true;
if (wasOriginallyHidden) {
// Field was originally hidden - keep it hidden, don't restore
return;
}
// Skip intentionally hidden fields (current state check)
if (el.hasAttribute('hidden') ||
el.getAttribute('aria-hidden') === 'true' ||
el.classList.contains('hs-hidden') ||
el.classList.contains('hidden') ||
el.classList.contains('sr-only') ||
el.classList.contains('visually-hidden')) {
return;
}
// Skip .hs-form-field containers that only contain hidden inputs
if (el.classList.contains('hs-form-field')) {
const hiddenInputs = el.querySelectorAll('input[type="hidden"]');
if (hiddenInputs.length > 0) {
const visibleInputs = el.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]), textarea, select');
if (visibleInputs.length === 0) {
return;
}
}
}
// Check if parent is hiding the field
let parentElement = el.parentElement;
while (parentElement && parentElement !== formElement) {
if (parentElement.hasAttribute('hidden') ||
parentElement.getAttribute('aria-hidden') === 'true' ||
parentElement.classList.contains('hs-hidden') ||
parentElement.classList.contains('hidden')) {
return;
}
parentElement = parentElement.parentElement;
}
// CRITICAL: Check original inline style - if it had display:none, don't restore
const originalStyle = formData.cssPreservedStyles &&
formData.cssPreservedStyles.elements &&
formData.cssPreservedStyles.elements[el];
if (originalStyle && (originalStyle.includes('display:none') || originalStyle.includes('display: none'))) {
// Original style had display:none - keep it hidden
return;
}
// Restore preserved styles
if (formData.cssPreservedStyles && formData.cssPreservedStyles.elements && formData.cssPreservedStyles.elements[el]) {
el.setAttribute('style', formData.cssPreservedStyles.elements[el]);
}
// Ensure visibility - CRITICAL: Use style.setProperty with important flag
const elComputed = targetWindow.getComputedStyle(el);
// Check for all hiding methods
const isHidden = elComputed.display === 'none' ||
elComputed.visibility === 'hidden' ||
parseFloat(elComputed.opacity) === 0;
// Check for zero dimensions
const hasZeroDimensions = (parseFloat(elComputed.height) === 0 && parseFloat(elComputed.width) === 0) ||
parseFloat(elComputed.maxHeight) === 0 ||
parseFloat(elComputed.maxWidth) === 0;
// Check bounding box
const rect = el.getBoundingClientRect();
const isBoundingBoxHidden = rect.width === 0 && rect.height === 0;
if (isHidden || hasZeroDimensions || isBoundingBoxHidden) {
// Use style.setProperty with important flag for maximum priority
if (elComputed.display === 'none') {
el.style.setProperty('display', 'block', 'important');
}
if (elComputed.visibility === 'hidden') {
el.style.setProperty('visibility', 'visible', 'important');
}
if (parseFloat(elComputed.opacity) === 0) {
el.style.setProperty('opacity', '1', 'important');
}
// Restore dimensions if zero
if (hasZeroDimensions || isBoundingBoxHidden) {
if (parseFloat(elComputed.height) === 0) {
el.style.setProperty('height', 'auto', 'important');
}
if (parseFloat(elComputed.width) === 0) {
el.style.setProperty('width', 'auto', 'important');
}
if (parseFloat(elComputed.maxHeight) === 0) {
el.style.setProperty('max-height', 'none', 'important');
}
if (parseFloat(elComputed.maxWidth) === 0) {
el.style.setProperty('max-width', 'none', 'important');
}
}
hiddenFieldsCount++;
} else {
visibleFieldsCount++;
}
}
});
// Second pass: Verify and re-restore if needed (after a brief delay for DOM to settle)
setTimeout(function() {
let secondPassHidden = 0;
let secondPassVisible = 0;
formInputs.each(function() {
const el = this;
if (el.isConnected) {
// CRITICAL: Check if field was originally hidden - if so, skip restoration
const wasOriginallyHidden = formData.cssPreservedStyles &&
formData.cssPreservedStyles.originalHiddenState &&
formData.cssPreservedStyles.originalHiddenState[el] === true;
if (wasOriginallyHidden) {
// Field was originally hidden - keep it hidden, don't restore
return;
}
// Skip intentionally hidden fields (current state check)
if (el.hasAttribute('hidden') ||
el.getAttribute('aria-hidden') === 'true' ||
el.classList.contains('hs-hidden') ||
el.classList.contains('hidden') ||
el.classList.contains('sr-only') ||
el.classList.contains('visually-hidden')) {
return;
}
// CRITICAL: Check original inline style - if it had display:none, don't restore
const originalStyle = formData.cssPreservedStyles &&
formData.cssPreservedStyles.elements &&
formData.cssPreservedStyles.elements[el];
if (originalStyle && (originalStyle.includes('display:none') || originalStyle.includes('display: none'))) {
// Original style had display:none - keep it hidden
return;
}
const elComputed = targetWindow.getComputedStyle(el);
const isHidden = elComputed.display === 'none' ||
elComputed.visibility === 'hidden' ||
parseFloat(elComputed.opacity) === 0;
const rect = el.getBoundingClientRect();
const isBoundingBoxHidden = rect.width === 0 && rect.height === 0;
if (isHidden || isBoundingBoxHidden) {
// Force restore again
if (elComputed.display === 'none') {
el.style.setProperty('display', 'block', 'important');
}
if (elComputed.visibility === 'hidden') {
el.style.setProperty('visibility', 'visible', 'important');
}
if (parseFloat(elComputed.opacity) === 0) {
el.style.setProperty('opacity', '1', 'important');
}
if (isBoundingBoxHidden) {
el.style.setProperty('height', 'auto', 'important');
el.style.setProperty('width', 'auto', 'important');
}
secondPassHidden++;
} else {
secondPassVisible++;
}
}
});
// #region agent log
if (secondPassHidden > 0) {
debugInstrument('agile-ed-form-integration.js:1700', 'Second pass restoration after re-append', {
secondPassHidden,
secondPassVisible
}, 'CSS5');
}
// #endregion
}, 50);
// #region agent log
debugInstrument('agile-ed-form-integration.js:1250', 'Form fields visibility restored', {
totalFields:formInputs.length,
hiddenFieldsCount,
visibleFieldsCount
}, 'CSS5');
// #endregion
// Check CSS state after restoration
const finalComputedStyle = targetWindow.getComputedStyle(formElement);
const cssTagExists = targetDoc.querySelector('#agile-ed-hubspot-form-css') !== null;
const targetHead = formData.targetHead || targetDoc.head;
const currentStyleTags = targetHead ? targetHead.querySelectorAll('style, link[rel="stylesheet"]') : [];
const currentStylesheetCount = currentStyleTags.length;
const capturedCount = formData.capturedStylesheets ? formData.capturedStylesheets.length : 0;
// Check which stylesheets are missing
const missingAfterReappend = [];
if (formData.capturedStylesheets && capturedCount > 0) {
formData.capturedStylesheets.forEach(function(captured) {
let found = false;
currentStyleTags.forEach(function(current) {
// Match by ID first
if (captured.id && current.id === captured.id) {
found = true;
}
// Match by href for link tags
else if (captured.href && current.href === captured.href) {
found = true;
}
// Match style tags by textContent (even if empty)
else if (captured.tag === 'STYLE' && current.tagName === 'STYLE') {
const capturedText = (captured.textContent || '').trim();
const currentText = (current.textContent || '').trim();
if (capturedText === currentText) {
found = true;
}
}
});
if (!found) {
missingAfterReappend.push(captured);
}
});
}
// #region agent log
debugInstrument('agile-ed-form-integration.js:1135', 'CSS restored after re-appending', {
formDisplay:finalComputedStyle.display,
formVisibility:finalComputedStyle.visibility,
formOpacity:finalComputedStyle.opacity,
cssTagExists:cssTagExists,
hasPreservedStyles:!!formData.cssPreservedStyles,
formHasStyle:!!formElement.getAttribute('style'),
stylesheetCount:currentStylesheetCount,
capturedStylesheetCount:capturedCount,
missingStylesheets:missingAfterReappend.length,
missingHrefs:missingAfterReappend.map(s => s.href || s.id || s.uniqueId || 'unknown')
}, 'CSS5');
// #endregion
// Re-inject CSS again after a short delay to ensure it persists
if (formData.injectHubSpotFormCSS && typeof formData.injectHubSpotFormCSS === 'function') {
setTimeout(function() {
formData.injectHubSpotFormCSS();
}, 100);
}
// CRITICAL: If HubSpot stylesheets are missing, try to restore them
if (missingAfterReappend.length > 0 && targetHead) {
// Check form visibility before restoring stylesheets
const formElement = $form && $form[0] ? $form[0] : null;
const targetWindow = formData.iframeWindow || window;
const formDisplayBeforeRestore = formElement ? targetWindow.getComputedStyle(formElement).display : 'unknown';
const formVisibilityBeforeRestore = formElement ? targetWindow.getComputedStyle(formElement).visibility : 'unknown';
// #region agent log
debugInstrument('agile-ed-form-integration.js:1168', 'Restoring missing HubSpot stylesheets', {
missingCount:missingAfterReappend.length,
missingHrefs:missingAfterReappend.map(s => s.href || s.id || s.uniqueId || 'unknown'),
formDisplayBeforeRestore,
formVisibilityBeforeRestore
}, 'CSS5');
// #endregion
// Try to restore missing stylesheet links and style tags
missingAfterReappend.forEach(function(captured) {
if (captured.tag === 'LINK' && captured.href) {
// Restore stylesheet
const existingLink = targetHead.querySelector('link[href="' + captured.href + '"]');
if (!existingLink) {
try {
const linkTag = targetDoc.createElement('link');
linkTag.rel = 'stylesheet';
linkTag.href = captured.href;
if (captured.id) {
linkTag.id = captured.id;
}
targetHead.appendChild(linkTag);
// #region agent log
debugInstrument('agile-ed-form-integration.js:1183', 'Restored missing stylesheet link', {href:captured.href,id:captured.id}, 'CSS5');
// #endregion
} catch (e) {
// #region agent log
debugInstrument('agile-ed-form-integration.js:1188', 'Error restoring stylesheet link', {href:captured.href,errorMessage:e?.message}, 'CSS5');
// #endregion
}
}
} else if (captured.tag === 'STYLE') {
// Restore