/** * 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