PageSourceSearch

https://www.lenovosalesportal.com/SpecialFunctions/Forms/form-validation.js

js lenovosalesportal.com collected 2026-10-04 05:22:10 UTC 11,639 bytes, 256 lines download raw bytes

1/**
2 * validate-forms.js
3 * 8-17-26
4 *
5 * Handles client-side validation and submission for all forms marked
6 * with [data-form-wrapper] on a page (main-page forms and modal forms).
7 *
8 * Requirements for each form:
9 *   - Wrapper element has the `data-form-wrapper` attribute.
10 *   - Wrapper contains a hidden input with id="processid" (required —
11 *     forms without this are skipped entirely).
12 *   - Wrapper contains a hidden input with id="postLocation" (optional;
13 *     defaults to /SpecialFunctions/Forms/MasterForm.aspx).
14 *   - Wrapper contains a submit <button> or <input type="submit">.
15 *
16 * Multi-form pages (e.g. several modals on one page):
17 *   - On submit, every OTHER [data-form-wrapper] on the page is removed
18 *     from the DOM before the active form is processed. This prevents
19 *     duplicate id/name collisions between forms — most notably
20 *     reCAPTCHA's g-recaptcha-response textarea, which api.js renders
21 *     with the same name (and near-duplicate id) for every widget on
22 *     the page.
23 *   - Any fields that must survive outside of every wrapper (e.g. a
24 *     sitewide widget) are NOT touched by this script and need their
25 *     own cleanup — see beforeSubmit() in the page/control markup.
26 *
27 * Optional global hooks (defined elsewhere, called if present):
28 *   - getMessage()   — reCAPTCHA include; verifies the captcha, then
29 *                      calls submitThisForm() with no args. Only
30 *                      called when the active wrapper contains a
31 *                      .g-recaptcha-response element.
32 *   - beforeSubmit() — page-specific cleanup, run just before the
33 *                      form actually submits.
34 *   - ga()           — Google Analytics event tracking.
35 */
36
37// Grab the main form tag (usually just one in ASP.NET environments)
38const mainForm = document.querySelector("form");
39
40function submitThisForm(wrapper, contactformSession, postValue, contactformID) {
41    // Fall back to window-stored values when called with no args (e.g. from getMessage in recaptcha include)
42    wrapper = wrapper || window._activeFormWrapper;
43    contactformSession = contactformSession || window._activeFormSession;
44    postValue = postValue || window._activeFormPostValue;
45    contactformID = contactformID || window._activeFormId;
46
47    sessionStorage.setItem(contactformSession, `${contactformSession}Sent`);
48
49    // Set form.action
50    mainForm.action = postValue || "/SpecialFunctions/Forms/MasterForm.aspx";
51
52    // GA tracking Event
53    if (typeof ga === 'function') {
54        ga('send', 'event', 'Form ID:' + contactformID + ' Submitted', 'Submitted', 'Contact Form', contactformID);
55    } else {
56        console.warn('Google Analytics is not defined.');
57    }
58
59    if (typeof beforeSubmit === 'function') {
60        beforeSubmit();
61    }
62
63    // Safety net: disable inputs outside the active form in case this was
64    // called without the removal step having already run.
65    const allFormWrappers = document.querySelectorAll('[data-form-wrapper]');
66    allFormWrappers.forEach(otherWrapper => {
67        if (otherWrapper !== wrapper) {
68            const inputsToDisable = otherWrapper.querySelectorAll('input, textarea, select');
69            inputsToDisable.forEach(el => el.disabled = true);
70        }
71    });
72
73    // Submit the main form
74    mainForm.submit();
75}
76
77function updateLegacyForms(wrapper) {
78    // Scoped to look ONLY inside the current wrapper
79    const legacyInputs = wrapper.querySelectorAll('[class^="validate"]');
80
81    legacyInputs.forEach(input => {
82        if (!input.hasAttribute('required')) {
83            input.setAttribute('required', true);
84
85            let labelText = '';
86            const label = input.closest('label');
87            if (label) {
88                labelText = label.textContent.trim();
89            } else {
90                labelText = input.getAttribute('name') || '';
91            }
92
93            input.setAttribute('oninvalid', `this.setCustomValidity('Please enter your ${labelText}')`);
94            input.setAttribute('oninput', 'this.setCustomValidity("")');
95
96            const inputId = input.getAttribute('id');
97            if (labelText.toLowerCase().includes('email') || (inputId && inputId.toLowerCase().includes('email'))) {
98                input.setAttribute('pattern', '[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$');
99                input.setAttribute('oninvalid', 'this.setCustomValidity("Please enter a valid email address")');
100            }
101
102            if (labelText.toLowerCase().includes('phone') || (inputId && inputId.toLowerCase().includes('phone'))) {
103                input.setAttribute('pattern', '^(?:\\d{3}-\\d{3}-\\d{4}|\\(\\d{3}\\)-\\d{3}-\\d{4}|\\d{10}|[0-9]-\\d{3}-\\d{3}-\\d{4}|[0-9]-\\(\\d{3}\\)-\\d{3}-\\d{4}|\\d{11})$');
104                input.setAttribute('oninvalid', 'this.setCustomValidity("Please enter a valid phone number")');
105            }
106        }
107    });
108}
109
110// Stamps formnovalidate on any submit button that is NOT inside a managed
111// form wrapper (i.e. a [data-form-wrapper] containing a processid).
112// Accepts an optional root element to scope the search (for MutationObserver).
113function stampUnmanagedButtons(root) {
114    root = root || document;
115    const candidates = root.querySelectorAll('input[type="submit"], button[type="submit"]');
116
117    candidates.forEach(btn => {
118        const parentWrapper = btn.closest('[data-form-wrapper]');
119        const isManaged = parentWrapper && parentWrapper.querySelector('[id="processid"]');
120
121        if (!isManaged) {
122            btn.setAttribute('formnovalidate', '');
123        }
124    });
125}
126
127function validateForm() {
128    // Find EVERY logical form wrapper on the page (main page and modals).
129    // Falls back to the single page-wide <form> when no [data-form-wrapper]
130    // markup exists at all (legacy pages). Pages with a MIX of wrapped and
131    // unwrapped forms are not supported — the unwrapped ones need the
132    // attribute added.
133    let formWrappers = document.querySelectorAll('[data-form-wrapper]');
134    if (formWrappers.length === 0 && mainForm) {
135        formWrappers = [mainForm];
136    }
137
138    formWrappers.forEach(wrapper => {
139        const processIdElement = wrapper.querySelector('[id="processid"]');
140        const postInput = wrapper.querySelector('[id="postLocation"]');
141
142        if (!processIdElement) return; // Skip if this wrapper doesn't have a process id
143
144        const contactformID = processIdElement.value;
145        const contactformSession = contactformID ? `contactForm${contactformID}` : "";
146        const postValue = postInput ? postInput.value : "";
147
148        // See if form already sent
149        if (sessionStorage.getItem(contactformSession)) {
150            wrapper.innerHTML = "<div class='thank-you-message'><h4>Thank you for contacting us!</h4><p>We will be in touch.</p></div>";
151            sessionStorage.removeItem(contactformSession);
152            return;
153        }
154
155        updateLegacyForms(wrapper);
156
157        const submitButton = wrapper.querySelector('button, input[type="submit"]');
158
159        if (submitButton) {
160            submitButton.addEventListener('click', (event) => {
161                event.preventDefault();
162
163                let isFormValid = true;
164
165                // Validate ONLY the inputs inside this specific wrapper
166                const inputsToValidate = wrapper.querySelectorAll('input, textarea, select');
167
168                inputsToValidate.forEach(element => {
169                    let errorMessageDiv = element.parentNode.querySelector('.input-error-msg');
170
171                    element.addEventListener('input', () => {
172                        if (element.checkValidity()) {
173                            if (errorMessageDiv) errorMessageDiv.remove();
174                            element.classList.remove("input-error");
175                        }
176                    });
177
178                    if (!element.checkValidity()) {
179                        isFormValid = false;
180                        if (errorMessageDiv) {
181                            errorMessageDiv.textContent = element.validationMessage;
182                        } else {
183                            errorMessageDiv = document.createElement("div");
184                            errorMessageDiv.className = "input-error-msg";
185                            errorMessageDiv.textContent = element.validationMessage;
186                            element.parentNode.appendChild(errorMessageDiv);
187                        }
188                        element.classList.add("input-error");
189                    }
190                });
191
192                if (isFormValid) {
193                    // Store scoped variables on window so getMessage's no-arg
194                    // submitThisForm() call can retrieve them from the recaptcha include
195                    window._activeFormWrapper = wrapper;
196                    window._activeFormSession = contactformSession;
197                    window._activeFormPostValue = postValue;
198                    window._activeFormId = contactformID;
199
200                    // Remove other form wrappers so duplicate IDs (e.g. reCAPTCHA's
201                    // g-recaptcha-response) can't collide during submission.
202                    document.querySelectorAll('[data-form-wrapper]').forEach(otherWrapper => {
203                        if (otherWrapper !== wrapper) {
204                            otherWrapper.remove();
205                        }
206                    });
207
208                    // Only run the captcha flow if this wrapper actually has one.
209                    const captchaResponseEl = wrapper.querySelector('.g-recaptcha-response');
210
211                    if (captchaResponseEl && typeof getMessage === 'function') {
212                        // Normalize the id so getMessage()'s hardcoded
213                        // getElementById('g-recaptcha-response') finds THIS
214                        // wrapper's textarea, regardless of the -1/-2 suffix
215                        // reCAPTCHA's api.js may have assigned it.
216                        captchaResponseEl.id = 'g-recaptcha-response';
217                        getMessage();
218                    } else {
219                        submitThisForm(wrapper, contactformSession, postValue, contactformID);
220                    }
221                }
222            });
223        }
224    });
225
226    // Stamp all unmanaged submit buttons present at init time
227    stampUnmanagedButtons();
228
229    // Watch for dynamically added content (e.g. UpdatePanel re-renders, AJAX loads)
230    // and re-stamp any new unmanaged submit buttons as they appear
231    const observer = new MutationObserver(mutations => {
232        mutations.forEach(mutation => {
233            mutation.addedNodes.forEach(node => {
234                if (node.nodeType !== Node.ELEMENT_NODE) return;
235
236                if (
237                    (node.tagName === 'INPUT' && node.type === 'submit') ||
238                    (node.tagName === 'BUTTON' && (node.type === 'submit' || !node.type))
239                ) {
240                    stampUnmanagedButtons(node.parentNode);
241                    return;
242                }
243
244                const hasSubmits = node.querySelector('input[type="submit"], button[type="submit"]');
245                if (hasSubmits) {
246                    stampUnmanagedButtons(node);
247                }
248            });
249        });
250    });
251
252    observer.observe(document.body, { childList: true, subtree: true });
253}
254
255// Initialize
256validateForm();

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.