1// Client-side debug switch â see application-shortcode/script.js for the full 2// rationale. Duplicated per shortcode folder since each is enqueued independently 3// and this file loads first (no dependencies) within this folder. Debug state is 4// re-checked on every call (not cached at load time) since ParkerSettings may be 5// localized to a script tag that hasn't run yet at this point in page load. 6(function () { 7 try { 8 var params = new URLSearchParams(window.location.search); 9 if (params.has('parker_debug')) { 10 if (params.get('parker_debug') === '1') { 11 localStorage.setItem('parker_debug', '1'); 12 } else { 13 localStorage.removeItem('parker_debug'); 14 } 15 } 16 } catch (e) { 17 // localStorage/URLSearchParams unavailable. 18 } 19})(); 20 21function parkerIsDebugEnabled() { 22 try { 23 if (localStorage.getItem('parker_debug') === '1') { 24 return true; 25 } 26 } catch (e) { 27 // fall through to the admin-wide setting below 28 } 29 return !!(window.ParkerSettings && window.ParkerSettings.clientDebug); 30} 31 32function parkerDebugLog(...args) { 33 if (parkerIsDebugEnabled()) { 34 console.log(...args); 35 } 36} 37 38function parkerDebugWarn(...args) { 39 if (parkerIsDebugEnabled()) { 40 console.warn(...args); 41 } 42} 43 44document.addEventListener("DOMContentLoaded", function () { 45 parkerDebugLog("Parker App Modal script loaded"); 46 47 const triggerButton = document.getElementById("parker-appmodal-trigger"); 48 const modal = document.getElementById("parker-appmodal-modal"); 49 const modalOverlay = document.querySelector(".parker-appmodal-modal-overlay"); 50 const closeButton = document.querySelector(".parker-appmodal-modal-close"); 51 const loadingContainer = document.getElementById("parker-appmodal-loading"); 52 // Content box (white card). It gets its height from the stage; while the stage is hidden 53 // during load we toggle .is-loading on it so it keeps a real height and the centered 54 // spinner is actually visible (otherwise the box collapses to ~0 and the spinner vanishes). 55 const modalContent = document.querySelector(".parker-appmodal-modal-content"); 56 // #parker-appmodal-stage is the 3-column container (background + intro + form). 57 // The form itself lives inside #parker-appmodal-form-container, but show/hide is at 58 // the stage level so the background image and intro panel come in together. 59 const stage = document.getElementById("parker-appmodal-stage"); 60 61 if (!modal) { 62 parkerDebugWarn("â ï¸ Parker App Modal: Modal element not found on page. Make sure [parker_appmodal] shortcode is present."); 63 return; 64 } 65 66 parkerDebugLog("â Parker App Modal: Modal found on page"); 67 68 let formLoaded = false; 69 70 // Function to open modal 71 function openModal(e) { 72 if (e) e.preventDefault(); 73 parkerDebugLog("Opening app modal"); 74 75 // Close any Elementor dialogs/lightboxes first 76 const dialogCloseButton = document.querySelector('.dialog-close-button, .dialog-lightbox-close-button'); 77 if (dialogCloseButton) { 78 parkerDebugLog("Closing Elementor dialog first"); 79 dialogCloseButton.click(); 80 // Wait a moment for the dialog to close before opening modal 81 setTimeout(() => { 82 modal.classList.add("active"); 83 document.body.style.overflow = "hidden"; // Prevent background scroll 84 }, 100); 85 } else { 86 modal.classList.add("active"); 87 document.body.style.overflow = "hidden"; // Prevent background scroll 88 } 89 90 if (!formLoaded) { 91 // Show spinner; stage stays hidden until step-1 content is ready. .is-loading keeps 92 // the content box at a real height so the centered spinner is visible during the wait. 93 if (loadingContainer) loadingContainer.style.display = "block"; 94 if (modalContent) modalContent.classList.add("is-loading"); 95 if (stage) { 96 stage.style.display = "none"; 97 stage.classList.remove("is-entered"); 98 } 99 100 // Fetch step-1 HTML (program/term selector + program data) in parallel with 101 // the background-image preload. The form HTML was removed from the page 102 // footer to avoid loading FormGenerator + DB terms on every page load. 103 const bgUrl = extractStageBgUrl(stage); 104 const ajaxUrl = window.parkerAppmodalAjaxUrl || '/wp-admin/admin-ajax.php'; 105 const step1Body = new URLSearchParams(); 106 step1Body.set('action', 'render_parker_appmodal_step1'); 107 108 const MIN_DELAY_MS = 500; 109 Promise.all([ 110 preloadImage(bgUrl), 111 new Promise(function (r) { setTimeout(r, MIN_DELAY_MS); }), 112 fetch(ajaxUrl, { 113 method: 'POST', 114 credentials: 'same-origin', 115 headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, 116 body: step1Body.toString(), 117 }).then(function (r) { return r.json().catch(function () { return null; }); }), 118 ]).then(function (results) { 119 var data = results[2]; 120 121 if (!data || !data.success || !data.html) { 122 console.error('Parker App Modal: step-1 AJAX failed', data); 123 if (loadingContainer) loadingContainer.style.display = "none"; 124 if (modalContent) modalContent.classList.remove("is-loading"); 125 var formContainer = document.getElementById('parker-appmodal-form-container'); 126 if (formContainer) { 127 formContainer.innerHTML = '<p style="color:#a00;padding:20px;">Unable to load the application form. Please refresh and try again.</p>'; 128 } 129 if (stage) { 130 stage.style.display = ""; 131 requestAnimationFrame(function () { requestAnimationFrame(function () { stage.classList.add("is-entered"); }); }); 132 } 133 return; 134 } 135 136 // Inject the step-1 HTML into the (currently empty) form container. 137 var formContainer = document.getElementById('parker-appmodal-form-container'); 138 if (formContainer) { 139 formContainer.innerHTML = data.html; 140 141 // innerHTML does not execute embedded <script> tags. Clone/replace each 142 // so the inline window.programLegend / window.availableProgramTerms / 143 // window.jsonDecodedAvailableProgramTerms blocks actually run â same 144 // technique used by swapInApplicationForm() for step-2. 145 var scripts = formContainer.querySelectorAll('script');
146 scripts.forEach(function (oldScript) { 147 var newScript = document.createElement('script'); 148 if (oldScript.src) { 149 newScript.src = oldScript.src; 150 } else { 151 newScript.textContent = oldScript.textContent; 152 } 153 oldScript.parentNode.replaceChild(newScript, oldScript); 154 }); 155 156 // Reveal the freshly-injected form (clear the FOUC inline style, dismiss the 157 // section-loader overlay, activate the first section). Centralized in 158 // initParkerApplicationForm so the lazy-load path doesn't re-discover each 159 // hidden layer one bug at a time. Falls back to a minimal inline clear if the 160 // helper somehow isn't loaded yet. 161 if (typeof window.initParkerApplicationForm === 'function') { 162 window.initParkerApplicationForm(formContainer); 163 } else { 164 var fallbackForm = formContainer.querySelector('.form-container'); 165 if (fallbackForm) { 166 fallbackForm.classList.add('loaded'); 167 fallbackForm.style.opacity = '1'; 168 fallbackForm.style.visibility = 'visible'; 169 } 170 } 171 } 172
173 // Wire up the step-1 dropdown/submit logic now that its DOM exists. 174 if (typeof window.initParkerAppmodalStep1 === 'function') { 175 window.initParkerAppmodalStep1(); 176 } else { 177 parkerDebugWarn('Parker App Modal: initParkerAppmodalStep1 not available. Did appconfig.js load?'); 178 } 179 180 formLoaded = true; 181 parkerDebugLog("Form loaded and displayed"); 182 183 if (loadingContainer) loadingContainer.style.display = "none"; 184 if (modalContent) modalContent.classList.remove("is-loading"); 185 if (stage) { 186 stage.style.display = ""; 187 // The .is-entered class triggers CSS transitions: 188 // - stage opacity 0â1, translateY 8pxâ0 189 // - intro panel translateX(-30px) opacity 0 â 0 opacity 1 190 // One extra rAF so the browser registers the initial hidden state 191 // before flipping the class â without it the transition can be skipped. 192 requestAnimationFrame(function () { 193 requestAnimationFrame(function () { 194 stage.classList.add("is-entered"); 195 }); 196 }); 197 } 198 }).catch(function (err) { 199 console.error('Parker App Modal: step-1 load error', err); 200 if (loadingContainer) loadingContainer.style.display = "none"; 201 if (modalContent) modalContent.classList.remove("is-loading"); 202 var formContainer = document.getElementById('parker-appmodal-form-container'); 203 if (formContainer) { 204 formContainer.innerHTML = '<p style="color:#a00;padding:20px;">Network error loading the form. Please refresh and try again.</p>'; 205 } 206 if (stage) { 207 stage.style.display = ""; 208 requestAnimationFrame(function () { requestAnimationFrame(function () { stage.classList.add("is-entered"); }); }); 209 } 210 }); 211 } else { 212 // Step-1 already injected on a previous open; just show the stage. 213 if (loadingContainer) loadingContainer.style.display = "none"; 214 if (modalContent) modalContent.classList.remove("is-loading"); 215 if (stage) stage.style.display = ""; 216 } 217 } 218 219 /** 220 * Read the stage's inline background-image URL so we can preload it before showing the 221 * stage. Falls back gracefully if no URL is found (the entry animation still plays â 222 * just without the preload guarantee). 223 */ 224 function extractStageBgUrl(el) { 225 if (!el) return null; 226 const inline = el.getAttribute("style") || ""; 227 const m = inline.match(/background-image:\s*url\(\s*['"]?([^'"\)]+)['"]?\s*\)/i); 228 return m ? m[1] : null; 229 } 230 231 /** 232 * Resolves when the image is loaded (or errors). Always resolves â never rejects â so a 233 * 404 or network failure doesn't block the modal from opening. 234 */ 235 function preloadImage(url) { 236 return new Promise(function (resolve) { 237 if (!url) { resolve(); return; } 238 const img = new Image(); 239 img.onload = function () { resolve(); }; 240 img.onerror = function () { resolve(); }; 241 img.src = url; 242 }); 243 } 244 245 // Attach to shortcode button if it exists 246 if (triggerButton) { 247 triggerButton.addEventListener("click", openModal); 248 } 249 250 // Use event delegation so ANY element with class="parker-appmodal-trigger" opens the modal â 251 // anchor tags, list items (WordPress menu items get class added via the menu editor), buttons, 252 // Elementor widgets, or custom HTML. Works for triggers injected dynamically after page load 253 // (e.g., Elementor lightbox content). 254 document.body.addEventListener("click", function(e) { 255 const trigger = e.target.closest('.parker-appmodal-trigger'); 256 if (!trigger) return; 257 // Skip the built-in shortcode button â it has its own dedicated handler attached above 258 // via #parker-appmodal-trigger ID, and we don't want to double-fire it. 259 if (trigger.id === 'parker-appmodal-trigger') return; 260 261 // If the standalone [parker_application] form is already on this page (e.g. the user is
262 // on /application?record_id=... and clicked a site-wide menu trigger), don't stack a 263 // modal on top of the existing form. Two copies of the form would collide on shared 264 // IDs (#submit-btn, #formData) and break listener wiring. Scroll to the existing form 265 // instead â the user is already where they need to be. 266 const standaloneForm = findStandaloneApplicationForm(); 267 if (standaloneForm) { 268 e.preventDefault(); 269 parkerDebugLog('Standalone application form detected; scrolling to it instead of opening modal.'); 270 standaloneForm.scrollIntoView({ behavior: 'smooth', block: 'start' }); 271 return; 272 } 273 274 parkerDebugLog('ð Parker App Modal: Trigger clicked on', trigger.tagName.toLowerCase()); 275 openModal(e); 276 }); 277 278 /** 279 * Returns the standalone application form-container element if one exists on the page 280 * outside the modal (i.e., [parker_application] was rendered directly into page content). 281 * Returns null if the only .form-container on the page is inside the modal â i.e., we're 282 * on a normal page that just has [parker_appmodal] sitting in a footer widget. 283 */ 284 function findStandaloneApplicationForm() { 285 const modalRoot = document.getElementById('parker-appmodal-modal'); 286 const candidates = document.querySelectorAll('.form-container'); 287 for (let i = 0; i < candidates.length; i++) { 288 if (!modalRoot || !modalRoot.contains(candidates[i])) { 289 return candidates[i]; 290 } 291 } 292 return null; 293 } 294 295 // Log discovered triggers at load time for debugging. 296 const initialTriggers = document.querySelectorAll('.parker-appmodal-trigger'); 297 parkerDebugLog(`â Parker App Modal: Found ${initialTriggers.length} trigger element(s) on initial load`); 298 initialTriggers.forEach(el => { 299 parkerDebugLog(' - Trigger:', el.tagName.toLowerCase(), el.id || el.textContent.trim().slice(0, 40)); 300 }); 301 302 // Close modal on overlay click 303 if (modalOverlay) { 304 modalOverlay.addEventListener("click", function () { 305 parkerDebugLog("Closing modal via overlay click"); 306 closeModal(); 307 }); 308 } 309 310 // Close modal on close button click 311 if (closeButton) { 312 closeButton.addEventListener("click", function (e) { 313 e.preventDefault(); 314 parkerDebugLog("Closing modal via close button"); 315 closeModal(); 316 }); 317 } 318 319 // Close modal on ESC key 320 document.addEventListener("keydown", function (e) { 321 if (e.key === "Escape" && modal.classList.contains("active")) { 322 parkerDebugLog("Closing modal via ESC key"); 323 closeModal(); 324 } 325 }); 326 327 function closeModal() { 328 modal.classList.remove("active"); 329 document.body.style.overflow = ""; // Restore background scroll 330 } 331 332 // Reset modal state when page is navigated back to (browser back button) 333 window.addEventListener("pageshow", function(event) { 334 if (event.persisted || (window.performance && window.performance.navigation.type === 2)) { 335 parkerDebugLog("Page restored from cache - resetting modal"); 336 337 // Close the modal 338 modal.classList.remove("active"); 339 document.body.style.overflow = ""; 340 341 // Reset form loaded state 342 formLoaded = false; 343 344 // Remove any shimmer elements 345 const shimmerWrapper = document.getElementById("shimmer-wrapper"); 346 if (shimmerWrapper) { 347 shimmerWrapper.remove(); 348 } 349 350 // Reset loading and stage visibility 351 if (loadingContainer) loadingContainer.style.display = "block"; 352 if (stage) { 353 stage.style.display = "none"; 354 stage.classList.remove("is-entered"); 355 stage.classList.remove("is-step2"); 356 } 357 358 // Reset submit button 359 const submitBtn = document.getElementById("submit-btn"); 360 if (submitBtn) { 361 submitBtn.disabled = false; 362 submitBtn.textContent = "Next"; 363 } 364 } 365 });
366});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.