PageSourceSearch

https://www.parker.edu/wp-content/plugins/parker-salesforce-integr…/script.js?ver=1790564791

js parker.edu collected 2026-10-01 12:06:02 UTC 17,501 bytes, 366 lines download raw bytes

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.