1/** 2 * App Modal â step 1 (program/term picker) logic. 3 * 4 * Hoisted out of a DOMContentLoaded handler so it can be invoked on demand. Step 1's DOM is 5 * NOT torn down when swapping to step 2 â its nodes are detached into an in-memory 6 * DocumentFragment (parkedStep1Nodes) and reattached on Back. This preserves all listeners, 7 * dropdown selections, and DOM state natively. The earlier approach of caching innerHTML and 8 * re-binding from scratch had too many timing-sensitive edge cases (when does `.loaded` 9 * class get added, when do listeners attach vs. when do we re-dispatch change, etc.). 10 */ 11 12// DocumentFragment holding step 1's detached children while step 2 is shown. Null when step 1 13// is currently in the DOM. Re-creating per round-trip is fine â the actual node references 14// (with their listeners and state) are what we care about preserving. 15var parkedStep1Nodes = null; 16 17function initParkerAppmodalStep1() { 18 parkerDebugLog("App Modal - AppConfig loaded"); 19 20 // CRITICAL: all queries below MUST be scoped to inside the modal element. Otherwise on 21 // the /application page (which has [parker_application] in page content AND 22 // [parker_appmodal] in the footer via wp_footer site-wide hook), document.querySelector 23 // returns the standalone page's form elements (which appear first in DOM order). That 24 // makes the modal's submit handler hijack the page's submit button â with preventDefault 25 // and stopImmediatePropagation â and the standalone form never gets to call 26 // handleAppConfigSection() to redirect to /application?record_id=â¦&program_id=â¦& 27 // program_term=â¦. Step 2 never loads. User sees "still on step 1". 28 const modalEl = document.getElementById('parker-appmodal-modal'); 29 if (!modalEl) { 30 parkerDebugLog("App Modal: no modal element on page, skipping appmodal init"); 31 return; 32 } 33 34 const urlParams = new URLSearchParams(window.location.search); 35 const useUnmanagedData = urlParams.get('use_unmanaged_data') === '1'; 36 parkerDebugLog('ð DATA SOURCE:', useUnmanagedData ? 'SALESFORCE (Direct API)' : 'DATABASE (Managed)'); 37 38 const programsDropdown = modalEl.querySelector('#TargetX_SRMb__Program__c'); 39 const programTermsDropdown = modalEl.querySelector('#custom_program_term'); 40 const submitButton = modalEl.querySelector('#submit-btn'); 41 42 if (!programsDropdown || !programTermsDropdown || !submitButton) { 43 parkerDebugWarn("App modal form elements not found"); 44 return; 45 } 46 47 submitButton.textContent = 'Next'; 48 // Start disabled. The updateStep1ButtonState() helper below enables it only when 49 // BOTH a program AND a term are picked. All queries here are already scoped to 50 // modalEl (the #parker-appmodal-modal root) so this never touches anything outside 51 // the modal â important because the modal lives in the footer on every site page 52 // and we previously had bugs from broad submit-button selectors. 53 submitButton.disabled = true; 54 55 // Form reveal (clear FOUC, dismiss the #section-loader overlay, activate the first section) 56 // is handled by window.initParkerApplicationForm(), called from the modal's openModal() 57 // orchestration right after this form is injected. Kept out of here so reveal lives in one 58 // place for every lazy-load path. Defensive: if that helper wasn't available for some 59 // reason, fall back to revealing step 1 here so the modal never shows a dead spinner. 60 if (typeof window.initParkerApplicationForm !== 'function') { 61 const firstSection = modalEl.querySelector('.form-section'); 62 if (firstSection) firstSection.classList.add('active'); 63 const sectionLoaderEl = modalEl.querySelector('#section-loader'); 64 if (sectionLoaderEl) sectionLoaderEl.classList.add('hidden'); 65 } 66 67 const programTermsFormGroup = programTermsDropdown.closest('.form-group'); 68 69 if (programTermsFormGroup) { 70 programTermsFormGroup.style.display = 'none'; 71 } 72 73 // Capture step-1's program data NOW, while window.* still holds it. When the user
74 // advances to step 2, ApplicationShortcode's embedded render emits inline scripts that 75 // OVERWRITE window.programLegend / window.availableProgramTerms / 76 // window.jsonDecodedAvailableProgramTerms with just the ONE chosen program's data. 77 // After clicking "Back to program selection" the step-1 DOM is restored (parked nodes) 78 // but these globals are gone â so the submit handler's label->SF-id lookup and the 79 // term-repopulation must use these captured copies, not the live (clobbered) globals. 80 // Captured here because the injected step-1 inline script (which sets these) runs just 81 // before this init function is called. 82 const step1ProgramLegend = window.programLegend || window.jsonDecodedprogramLegend || {}; 83 const step1ProgramTerms = window.jsonDecodedAvailableProgramTerms || window.availableProgramTerms || {}; 84 85 // Toggle submit button based on whether both selects have values. Called from the 86 // change listeners on both dropdowns AND from inside the term-population branch 87 // below (after we fill the term dropdown, the selected value defaults to empty, 88 // so the button correctly stays disabled until the user picks one). 89 const updateStep1ButtonState = function () { 90 const hasProgram = programsDropdown.value && programsDropdown.value.trim() !== ''; 91 const hasTerm = programTermsDropdown.value && programTermsDropdown.value.trim() !== ''; 92 submitButton.disabled = !(hasProgram && hasTerm); 93 }; 94 programTermsDropdown.addEventListener('change', updateStep1ButtonState); 95 96 let debounceTimer; 97 programsDropdown.addEventListener("change", function () { 98 clearTimeout(debounceTimer); 99 debounceTimer = setTimeout(() => { 100 const selectedProgramProcess = programsDropdown.value; 101 102 parkerDebugLog("=== PROGRAM SELECTED ==="); 103 parkerDebugLog("Selected Program Value:", selectedProgramProcess); 104 105 if (programTermsFormGroup) { 106 programTermsFormGroup.style.display = selectedProgramProcess ? 'block' : 'none'; 107 } 108 109 if (!selectedProgramProcess) { 110 return; 111 } 112 113 // Use the captured step-1 terms (see step1ProgramTerms above) â window.* may have 114 // been clobbered by a step-2 render the user has since backed out of. 115 const terms = step1ProgramTerms?.[selectedProgramProcess]; 116 parkerDebugLog("Terms for this program:", terms); 117 118 if (!terms || terms.length === 0) { 119 parkerDebugWarn("No terms available for this program."); 120 programTermsDropdown.innerHTML = '<option value="">No terms available</option>'; 121 return; 122 } 123 124 const activeTerms = getActiveTerms(terms); 125 if (!activeTerms || activeTerms.length === 0) { 126 parkerDebugWarn("No active terms available."); 127 programTermsDropdown.innerHTML = '<option value="">No active terms available</option>'; 128 return; 129 } 130 131 const sortedTerms = sortTermsChronologically(activeTerms); 132 133 if (typeof FieldHandler !== 'undefined' && FieldHandler.populateDropdown) { 134 FieldHandler.populateDropdown(programTermsDropdown, sortedTerms, 'Select a term'); 135 } else { 136 programTermsDropdown.innerHTML = '<option value="">Select a term</option>'; 137 sortedTerms.forEach(term => { 138 const option = document.createElement('option'); 139 option.value = term.name; 140 option.textContent = term.name; 141 programTermsDropdown.appendChild(option); 142 }); 143 } 144 145 // Term dropdown was just repopulated; its value is the empty placeholder. 146 // Force the button to reflect that â without this, the button could remain 147 // enabled from a previous program's term selection that no longer applies. 148 updateStep1ButtonState(); 149 }, 300); 150 }); 151 152 submitButton.addEventListener("click", function (e) { 153 e.preventDefault(); 154 e.stopPropagation(); 155 e.stopImmediatePropagation(); 156 157 const selectedProgramProcess = programsDropdown.value; 158 const selectedProgramTerm = programTermsDropdown.value; 159 160 parkerDebugLog('=== APP MODAL SUBMIT CLICKED ==='); 161 parkerDebugLog('Selected Program Process:', selectedProgramProcess); 162 parkerDebugLog('Selected Term:', selectedProgramTerm); 163 164 if (!selectedProgramProcess || !selectedProgramTerm) { 165 showInlineError('Please select both a program and a term.'); 166 return; 167 } 168 169 // Resolve the program's Salesforce Id by matching the dropdown's selected label 170 // against programLegend (same lookup the old redirect flow used). Use the captured 171 // step-1 legend â window.programLegend is overwritten once step 2 renders, which 172 // broke this lookup after "Back to program selection". 173 // 174 // Normalize whitespace on BOTH sides before comparing. HTMLOptionElement.text 175 // collapses internal runs of whitespace per spec, but the programLegend JSON label 176 // does not â so a program whose source label has a double space (e.g. 177 // "Certificate - Computed Tomography") would never match (option.text = single space, 178 // legend = double). .trim() only strips the ends, not the middle. normalizeLabel 179 // collapses internal whitespace too so these line up regardless of data-entry quirks. 180 const normalizeLabel = function (s) { 181 return String(s == null ? '' : s).replace(/\s+/g, ' ').trim().toLowerCase(); 182 }; 183 const programLegend = step1ProgramLegend; 184 const selectedProgramLabel = normalizeLabel(programsDropdown.options[programsDropdown.selectedIndex].text); 185 let programId = ''; 186 const matchedProgram = Object.values(programLegend).find(p => 187 normalizeLabel(p.label) === selectedProgramLabel 188 ); 189 if (matchedProgram && matchedProgram.value) { 190 programId = matchedProgram.value; 191 } 192 193 // Hard-stop: refuse to advance without a real SF program id. Previously a label-match 194 // failure silently set programId='' and proceeded, which corrupted lead rows 195 // (program_id column stored the label instead of an SF id, breaking 196 // resolve_program_label and causing some submissions to fail at /registeruser).
197 if (!programId) { 198 parkerDebugWarn('Program label did not match any programLegend entry:', selectedProgramLabel); 199 showInlineError("We couldn't match the selected program to its Salesforce record. Please pick a different program or refresh the page and try again."); 200 return; 201 } 202 203 const waiverCode = urlParams.get('waiver') || urlParams.get('waiver_code') || ''; 204 205 submitButton.disabled = true; 206 clearInlineError(); 207 208 // Scope to modal â #section-loader exists in both the standalone /application form 209 // and the modal's form, so document.getElementById would return the wrong one. 210 const sectionLoader = modalEl.querySelector('#section-loader'); 211 if (sectionLoader) sectionLoader.classList.remove('hidden'); 212 startLoadingSequence([ 213 "Loading your selected program...", 214 "Setting your selected term...", 215 "Preparing application setup...", 216 ]); 217 218 // Kick off the intro-panel-slides-out animation BEFORE the AJAX completes so the 219 // motion plays in parallel with the network round-trip. But we ALSO need to ensure 220 // the swap waits for the animation to finish, so a fast AJAX doesn't interrupt the 221 // panel mid-slide. animationStartTime is referenced later in the .then() to compute 222 // any remaining delay before swapInApplicationForm runs. 223 const stage = document.getElementById('parker-appmodal-stage'); 224 if (stage) stage.classList.add('is-step2'); 225 const animationStartTime = Date.now(); 226 // Must match the longer of (panel transition 0.4s + 0s delay) and (grid transition 227 // 0.4s + 0.4s delay) in styles.css. 800ms covers both phases of the Forward 228 // sequence: panel slides out (0â400ms), then grid collapses (400â800ms). 229 const STEP2_ANIMATION_MS = 800; 230 231 // TODO: the appconfig.js getActiveTerms() filter (date-range) and the server-side 232 // is_active column on wp_parker_terms can disagree about whether a term is "active". 233 // Today that mismatch surfaces as a friendly inline error instead of the old 234 // "bounce back to step 1" trap â but the underlying semantic mismatch should still 235 // be unified eventually. 236 const ajaxUrl = window.parkerAppmodalAjaxUrl || '/wp-admin/admin-ajax.php'; 237 const payload = new URLSearchParams(); 238 payload.set('action', 'render_parker_application'); 239 payload.set('record_id', selectedProgramProcess); 240 payload.set('program_id', programId); 241 payload.set('program_term', selectedProgramTerm); 242 payload.set('waiver', waiverCode); 243 244 fetch(ajaxUrl, { 245 method: 'POST', 246 credentials: 'same-origin', 247 headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, 248 body: payload.toString(), 249 }) 250 .then(function (res) { return res.json().catch(function () { return { success: false, message: 'Unexpected server response.' }; }); }) 251 .then(function (data) { 252 if (!data || !data.success) { 253 var msg = (data && data.message) ? data.message : 'Unable to load the application form. Please try again.'; 254 showInlineError(msg); 255 submitButton.disabled = false; 256 if (sectionLoader) sectionLoader.classList.add('hidden'); 257 var shimmer = modalEl.querySelector('#shimmer-wrapper'); 258 if (shimmer) shimmer.remove(); 259 // Reverse the intro-out animation so the user isn't left staring at a 260 // collapsed-intro step 1 after a server error. 261 if (stage) stage.classList.remove('is-step2'); 262 return; 263 } 264 265 // Wait for the slide-out / grid-collapse animation to finish before swapping 266 // in step 2. Without this, a fast AJAX (~200ms) would interrupt the panel 267 // mid-flight, making the motion look broken.
268 var elapsed = Date.now() - animationStartTime; 269 var remaining = Math.max(0, STEP2_ANIMATION_MS - elapsed); 270 setTimeout(function () { swapInApplicationForm(data.html); }, remaining); 271 }) 272 .catch(function (err) { 273 console.error('AppModal AJAX render failed:', err); 274 showInlineError('Network error while loading the application. Please try again.'); 275 submitButton.disabled = false; 276 if (sectionLoader) sectionLoader.classList.add('hidden'); 277 var shimmer = modalEl.querySelector('#shimmer-wrapper'); 278 if (shimmer) shimmer.remove(); 279 if (stage) stage.classList.remove('is-step2'); 280 }); 281 }, true); // capture phase 282 283 /** 284 * Stash step 1's DOM nodes into an in-memory fragment (preserving listeners, dropdown 285 * selections, and all state) and inject step 2 + a back link in their place. On Back the 286 * fragment is reattached to formContainer â step 1 picks up exactly where the user left 287 * off with no re-init needed. 288 */ 289 function swapInApplicationForm(html) { 290 var formContainer = document.getElementById('parker-appmodal-form-container'); 291 if (!formContainer) { 292 console.error('App modal form container not found, cannot swap in step 2.'); 293 return; 294 } 295 296 // Detach step 1 into an in-memory DocumentFragment. The DOM nodes themselves are 297 // moved (not cloned), so all attached event listeners and form state ride along. 298 parkedStep1Nodes = document.createDocumentFragment(); 299 while (formContainer.firstChild) { 300 parkedStep1Nodes.appendChild(formContainer.firstChild); 301 } 302 303 var step2Wrapper = document.createElement('div'); 304 step2Wrapper.id = 'parker-appmodal-step2'; 305 step2Wrapper.innerHTML = html; 306 formContainer.appendChild(step2Wrapper); 307 308 // Inject the back link INSIDE FormGenerator's .form-container (the white card) so it 309 // (a) shares the same opacity/visibility transition as the form â no more "back link 310 // sits alone then jumps once the form loads" jarring effect â and (b) aligns with the 311 // form fields' content start instead of overlapping the stage background image that 312 // bleeds through col-form at narrow widths. 313 var backLink = document.createElement('a'); 314 backLink.href = '#'; 315 backLink.className = 'parker-appmodal-back-link'; 316 backLink.textContent = 'â Back to program selection'; 317 var formGenContainer = step2Wrapper.querySelector('.form-container'); 318 if (formGenContainer) { 319 formGenContainer.insertBefore(backLink, formGenContainer.firstChild); 320 } else { 321 // Fallback if FormGenerator's wrapper isn't found for some reason â better to show 322 // the link in a slightly off position than to lose the Back UX entirely. 323 formContainer.insertBefore(backLink, formContainer.firstChild); 324 } 325 326 // innerHTML does not execute embedded <script> tags. Clone/replace each script node 327 // so the inline window.parkerEmbeddedContext block (and the inlined token_manager.js) 328 // actually runs before initParkerApplication() reads window globals. 329 var scripts = step2Wrapper.querySelectorAll('script'); 330 scripts.forEach(function (oldScript) { 331 var newScript = document.createElement('script'); 332 if (oldScript.src) { 333 newScript.src = oldScript.src; 334 } else { 335 newScript.textContent = oldScript.textContent; 336 } 337 for (var i = 0; i < oldScript.attributes.length; i++) { 338 var attr = oldScript.attributes[i]; 339 if (attr.name !== 'src') newScript.setAttribute(attr.name, attr.value); 340 } 341 oldScript.parentNode.replaceChild(newScript, oldScript); 342 }); 343 344 // Init functions are run in separate try/catch blocks so a failure in one doesn't 345 // prevent the other from running. We surface the actual error.message in the inline 346 // banner so we can see what's failing â generic "please refresh" hides real bugs. 347 try { 348 if (typeof window.initParkerApplication === 'function') { 349 window.initParkerApplication(); 350 } else { 351 parkerDebugWarn('initParkerApplication is not available. Did script.js fail to load?'); 352 } 353 } catch (e) { 354 console.error('initParkerApplication threw:', e); 355 console.error(' message:', e && e.message); 356 console.error(' stack:', e && e.stack); 357 showInlineError('Application init error: ' + (e && e.message ? e.message :
357String(e))); 358 } 359 360 try { 361 if (typeof window.initParkerApplicationUi === 'function') { 362 window.initParkerApplicationUi(); 363 } 364 } catch (e) { 365 console.error('initParkerApplicationUi threw:', e); 366 console.error(' message:', e && e.message); 367 console.error(' stack:', e && e.stack); 368 showInlineError('Application UI init error: ' + (e && e.message ? e.message : String(e))); 369 } 370 371 // validation.js's DOMContentLoaded ran at initial page load â when the password modal 372 // (#pwSubmitButton / #Password / #pwLetter etc.) wasn't in the DOM yet â and bailed 373 // out at its early-return guard. Re-run it now so the keyup â validatePassword 374 // listener gets attached and the password-strength hints actually turn green. 375 try { 376 if (typeof window.initParkerApplicationValidation === 'function') { 377 window.initParkerApplicationValidation(); 378 } 379 } catch (e) { 380 console.error('initParkerApplicationValidation threw:', e); 381 console.error(' message:', e && e.message); 382 console.error(' stack:', e && e.stack); 383 showInlineError('Application validation init error: ' + (e && e.message ? e.message : String(e))); 384 } 385 } 386 387 /** 388 * Inline error helper â keeps the user in the modal with their selections intact rather 389 * than ever bouncing them back to a previous step or navigating away. 390 */ 391 function showInlineError(message) { 392 // Scope to modal so we never target the standalone /application page's .form-container. 393 var formContainer = modalEl.querySelector('.form-container') 394 || modalEl.querySelector('#parker-appmodal-form-container'); 395 if (!formContainer) { 396 alert(message); 397 return; 398 } 399 clearInlineError(); 400 var banner = document.createElement('div'); 401 banner.id = 'parker-appmodal-inline-error'; 402 banner.setAttribute('role', 'alert'); 403 banner.style.cssText = 'background:#fdecea;color:#a00;border:1px solid #e0b4b4;border-radius:4px;padding:10px 14px;margin:12px 0;font-size:14px;'; 404 banner.textContent = message; 405 formContainer.insertBefore(banner, formContainer.firstChild); 406 } 407 408 function clearInlineError() { 409 var existing = modalEl.querySelector('#parker-appmodal-inline-error'); 410 if (existing) existing.remove(); 411 } 412 413 function getActiveTerms(terms) { 414 const today = new Date(); 415 const activeTerms = terms.filter(term => { 416 const startDate = new Date(term.start_date); 417 const endDate = new Date(term.end_date); 418 const isActive = startDate <= today && endDate >= today; 419 return isActive; 420 }); 421 parkerDebugLog("Filtered Active Terms:", activeTerms); 422 return activeTerms; 423 } 424 425 function sortTermsChronologically(terms) { 426 const monthOrder = { 427 'january': 0, 'february': 1, 'march': 2, 'april': 3, 428 'may': 4, 'june': 5, 'july': 6, 'august': 7, 429 'september': 8, 'october': 9, 'november': 10, 'december': 11 430 }; 431 432 const seasonOrder = { 433 'winter': 0, 434 'spring': 3, 435 'summer': 4, 436 'fall': 8 437 }; 438 439 function getTermSortKey(termName) { 440 const lower = termName.toLowerCase(); 441 const yearMatch = termName.match(/\b(\d{4})\b/); 442 const year = yearMatch ? parseInt(yearMatch[1], 10) : 9999; 443 444 let month = -1; 445 for (const [name, idx] of Object.entries(monthOrder)) { 446 if (lower.includes(name)) { 447 month = idx; 448 break; 449 } 450 } 451 452 if (month === -1) { 453 for (const [name, idx] of Object.entries(seasonOrder)) { 454 if (lower.includes(name)) { 455 month = idx; 456 break; 457 } 458 } 459 } 460 461 if (month === -1) month = 99; 462 return year * 100 + month; 463 } 464 465 return [...terms].sort((a, b) => getTermSortKey(a.name) - getTermSortKey(b.name)); 466 } 467 468 function startLoadingSequence(lines = []) { 469 const visibleLines = 3; 470 // Scope to modal â .spinner and .form-container exist in both the standalone
471 // /application page form and the modal's form. Without scoping, the shimmer would 472 // be appended to the wrong element on the /application page. 473 const spinner = modalEl.querySelector(".spinner"); 474 const formContainer = modalEl.querySelector(".form-container"); 475 476 if (!formContainer) return; 477 478 const existingWrapper = modalEl.querySelector("#shimmer-wrapper"); 479 if (existingWrapper) existingWrapper.remove(); 480 481 const shimmerWrapper = document.createElement("div"); 482 shimmerWrapper.id = "shimmer-wrapper"; 483 shimmerWrapper.style.height = "4.8em"; 484 shimmerWrapper.style.overflow = "hidden"; 485 shimmerWrapper.style.position = "relative"; 486 487 const shimmerContainer = document.createElement("div"); 488 shimmerContainer.id = "loading-lines"; 489 shimmerContainer.style.transition = "transform 0.4s ease-in-out"; 490 491 shimmerWrapper.appendChild(shimmerContainer); 492 formContainer.appendChild(shimmerWrapper); 493 494 if (spinner) { 495 spinner.insertAdjacentElement('afterend', shimmerWrapper); 496 spinner.classList.add("hidden"); 497 } 498 499 const tempLine = document.createElement("div"); 500 tempLine.className = "loading-line"; 501 tempLine.textContent = lines[0]; 502 shimmerContainer.appendChild(tempLine); 503 504 requestAnimationFrame(() => { 505 const lineHeight = tempLine.getBoundingClientRect().height; 506 const containerWidth = shimmerWrapper.getBoundingClientRect().width; 507 shimmerContainer.style.minWidth = `${tempLine.getBoundingClientRect().width}px`; 508 shimmerWrapper.style.height = `${lineHeight * visibleLines}px`; 509 shimmerWrapper.style.width = `${containerWidth}px`; 510 shimmerContainer.removeChild(tempLine); 511 512 showNextLine(0, lineHeight); 513 }); 514 515 function showNextLine(index, lineHeight) { 516 if (index < lines.length) { 517 const line = document.createElement("div"); 518 line.className = "loading-line"; 519 line.textContent = lines[index]; 520 shimmerContainer.appendChild(line); 521 setTimeout(() => line.classList.add("visible"), 10); 522 523 if (index >= visibleLines) { 524 shimmerContainer.style.transform = `translateY(-${(index - visibleLines + 1) * lineHeight}px)`; 525 } 526 527 setTimeout(() => showNextLine(index + 1, lineHeight), 2100); 528 } 529 } 530 } 531} 532 533window.initParkerAppmodalStep1 = initParkerAppmodalStep1; 534 535document.addEventListener("DOMContentLoaded", initParkerAppmodalStep1); 536 537/** 538 * Document-level click delegation for the "â Back to program selection" link injected by 539 * swapInApplicationForm. Lives outside initParkerAppmodalStep1 so it's only registered once 540 * per page â registering it inside the init function would stack up duplicate handlers each 541 * time step 1 is re-initialized. 542 */ 543document.addEventListener('click', function (e) { 544 var link = e.target.closest('.parker-appmodal-back-link'); 545 if (!link) return; 546 547 e.preventDefault(); 548 549 var formContainer = document.getElementById('parker-appmodal-form-container'); 550 if (!formContainer || !parkedStep1Nodes) { 551 parkerDebugWarn('Cannot go back: step 1 nodes not parked.'); 552 return; 553 } 554 555 // Tear down step 2. Its listeners die with its DOM â fine, they were specific to that 556 // step's form fields and IDs. 557 formContainer.innerHTML = ''; 558 559 // Reattach the parked step 1 nodes. Because we're moving the SAME node references back 560 // (not cloning), every event listener attached during the first init is still wired up, 561 // and the dropdowns retain their .value selections natively. No re-init needed. 562 formContainer.appendChild(parkedStep1Nodes); 563 parkedStep1Nodes = null; 564 565 // Reverse the intro-panel-slides-out animation. Removing the class triggers the CSS 566 // transition the other direction: grid template expands back to 3 columns and the 567 // intro panel slides in from the left. 568 var stage = document.getElementById('parker-appmodal-stage'); 569 if (stage) stage.classList.remove('is-step2'); 570 571 // Step 1 was parked mid-submit â the click handler had already kicked off the loading 572 // shimmer, disabled the submit button, swapped the spinner, and shown the section loader 573 // before the AJAX response came back. All that transient state was inside the parked DOM 574 // and rode along into the fragment. Now that step 1 is visible again we reset those four 575 // pieces so the form is interactive and visually idle, ready for another Next click. 576 var leftoverShimmer = formContainer.querySelector('#shimmer-wrapper'); 577 if (leftoverShimmer) leftoverShimmer.remove(); 578 579 var step1SubmitBtn = formContainer.querySelector('#submit-btn'); 580 if (step1SubmitBtn) step1SubmitBtn.disabled = false;
581 582 var step1SectionLoader = formContainer.querySelector('#section-loader'); 583 if (step1SectionLoader) step1SectionLoader.classList.add('hidden'); 584 585 var step1Spinner = formContainer.querySelector('.spinner'); 586 if (step1Spinner) step1Spinner.classList.remove('hidden'); 587});
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.