PageSourceSearch

https://www.umassglobal.edu/sites/default/files/asset_injector/js/…-01ee0f2e290de6f15459a8738a6fb659.js?tlv5va

js umassglobal.edu collected 2026-09-24 19:01:44 UTC 11,556 bytes, 339 lines download raw bytes

1const dataSent = {
2    "onClick": false,
3    "onUnload": false,
4    "onSubmit": false
5};
6const modifiedForms = [];
7
8/**
9 * Serializes form data into an object with additional metadata.
10 * @param {FormData} data - FormData object from the form.
11 * @param {HTMLFormElement} originForm - The source form used to gather field details.
12 * @returns {Object} Serialized form data with metadata (field type, tag, etc.).
13 */
14function serialize(data, originForm) {
15    const obj = {};
16    for (const [key, value] of data) {
17        const fieldDetails = {
18            value: decodeEscapedEntities(value), // Field value
19            timeStart: null, // Start time of interaction
20            timeEnd: null, // End time of interaction
21            isChanged: false, // Tracks whether the field value has been modified
22            fieldType: getFieldType(originForm, key), // Field type (e.g., text, hidden)
23            fieldTag: getFieldTag(originForm, key) // HTML tag (e.g., INPUT, TEXTAREA)
24        };
25        if (obj[key]) {
26            // If key already exists, convert it into an array (e.g., for multiple values from the same key)
27            obj[key] = Array.isArray(obj[key]) ? [...obj[key], fieldDetails] : [obj[key], fieldDetails];
28        } else {
29            obj[key] = fieldDetails; // Create a new entry for the key if it doesn't exist
30        }
31    }
32    return obj;
33}
34
35/**
36 * Retrieves the type of a form field.
37 * @param {HTMLFormElement} form - The form containing the field.
38 * @param {string} fieldName - The name of the field.
39 * @returns {string} Field type as a string or an empty string if undefined.
40 */
41function getFieldType(form, fieldName) {
42    return form[fieldName]?.type || "";
43}
44
45/**
46 * Retrieves the HTML tag of a form field.
47 * @param {HTMLFormElement} form - The form containing the field.
48 * @param {string} fieldName - The name of the field.
49 * @returns {string} HTML tag name as a string or an empty string if undefined.
50 */
51function getFieldTag(form, fieldName) {
52    return form[fieldName]?.tagName || "";
53}
54
55/**
56 * Updates the data in formA with changes from formB.
57 * @param {Object} formA - Original serialized form data.
58 * @param {Object} formB - Updated serialized form data.
59 */
60function formDataUpdate(formA, formB) {
61    Object.keys(formA).forEach(key => {
62        const fieldA = formA[key];
63        const fieldB = formB[key];
64        if (fieldA && fieldB && fieldA.value !== fieldB.value) {
65            formA[key] = {
66                ...fieldA,
67                value: decodeEscapedEntities(fieldB.value), // Update with the new value
68                isChanged: true, // Mark as changed
69                timeEnd: new Date().toISOString() // Record the time of change
70            };
71        }
72    });
73}
74
75/**
76 * Collects all forms in the document that use the POST method.
77 * Only returns forms with a stable id (required for your tracking model).
78 * @returns {Array<HTMLFormElement>} Array of form elements.
79 */
80function gatherForms() {
81    return Array.from(document.querySelectorAll("form"))
82        .filter((form) => {
83            // Use DOM property because it normalizes method casing.
84            const method = (form.method || form.getAttribute("method") || "").toLowerCase();
85            if (method !== "post") return false;
86
87            // Enforce your requirement: no id => do not track
88            if (!form.id || !form.id.trim()) return false;
89
90            return true;
91        });
92}
93
94
95/**
96 * Serializes an array of forms into an array of objects with metadata.
97 * @param {Array<HTMLFormElement>} formArray - Array of form elements.
98 * @returns {Array<Object>} Array of serialized form data.
99 */
100function serializeForms(formArray) {
101    return formArray.map(form => serialize(new FormData(form), form));
102}
103
104// ... existing code ...
105/**
106 * Tracks changes and interactions in forms by adding event listeners.
107 * Prevent double-binding by marking the form node.
108 * @param {Array<HTMLFormElement>} formArray - Array of form elements.
109 */
110function trackForms(formArray) {
111    formArray.forEach((form, index) => {
112        if (form.dataset.pfTracked === "1") {
113            return;
114        }
115        form.dataset.pfTracked = "1";
116
117        form.addEventListener('focusin', (event) => {
118            if (!modifiedForms.includes(form.id)) {
119                modifiedForms.push(form.id);
120            }
121            if (event.target) {
122                let targetField = getFormElementFromTarget(event.target);
123                const field = serializedFormDataArray[index][targetField.name];
124                if (field && field.timeStart == null) {
125                    field.timeStart = new Date().toISOString();
126                }
127            }
128        });
129
130        form.addEventListener('change', (event) => {
131            if (!modifiedForms.includes(form.id)) {
132                modifiedForms.push(form.id);
133            }
134            formDataUpdate(serializedFormDataArray[index], serialize(new FormData(form), form));
135        });
136
137        form.addEventListener('input', (event) => {
138            if (!modifiedForms.includes(form.id)) {
139                modifiedForms.push(form.id);
140            }
141            formDataUpdate(serializedFormDataArray[index], serialize(new FormData(form), form));
142        });
143
144        form.addEventListener('focusout', (event) => {
145            if (!modifiedForms.includes(form.id)) {
146                modifiedForms.push(form.id);
147            }
148            formDataUpdate(serializedFormDataArray[index], serialize(new FormData(form), form));
149        });
150        form.addEventListener('submit', (event) => {
151            if (event.target.innerText.toLowerCase().trim() !== "continue") {
152                if (!dataSent.onSubmit) {
153                    pf2dl(form.id, 'submit');
154                    dataSent.onSubmit = true;
155                }
156            }
157        });
158
159        form.querySelectorAll('input[type=submit]').forEach((submitButton) => {
160            submitButton.addEventListener("click", (event) => {
161                if (event.target.value.toLowerCase().trim() !== "continue") {
162                    if (!dataSent.onClick) {
163                        pf2dl(form.id, 'click');
164                        dataSent.onClick = true;
165                    }
166                }
167            });
168        });
169    });
170}
171
172
173/**
174 * Prepares form data for submission by hashing sensitive fields and removing metadata.
175 * @param {Object} form - Serialized form data.
176 */
177function prepare(form) {
178    let protectedFields = ["source", "burecruiter", "buemployercde", "agreementcode", "ga_client_id", "ga_ttid", "msclkid", "postal_code", "campaignid", "gclid"];
179    Object.entries(form).forEach(([key, field]) => {
180        // Hash sensitive fields (e.g., text, email, phone)
181        if (field?.fieldType === "text" || field?.fieldType === "email" || field?.fieldType === "tel") {
182            field.value = gUMG.hashThisHard(field.value);
183        } else {
184            field.value = decodeEscapedEntities(field.value);
185        }
186        // Remove hidden fields entirely
187        if (field?.fieldType === "hidden" && !key.toString().includes("utm_") && !protectedFields.includes(key)) {
188            delete form[key];
189        } else {
190            // Remove metadata
191            //  delete field.fieldType;
192            //  delete field.fieldTag;
193        }
194
195    });
196    return form;
197}
198
199/**
200 * Converts serialized form data into a JSON string.
201 * @param {Object} serializedFormData - Serialized form data object.
202 * @returns {string} JSON string representation of the form data.
203 */
204function goJSON(serializedFormData) {
205    return JSON.stringify(serializedFormData);
206}
207
208function decodeEscapedEntities(text) {
209    const tdiv = document.createElement('div');
210    tdiv.innerHTML = text;
211    return tdiv.textContent || tdiv.innerText;
212}
213
214let formArray;
215let serializedFormDataArray;
216
217
218function initForms() {
219    formArray = gatherForms();
220    if (!formArray || formArray.length === 0) {
221        return false;
222    }
223    serializedFormDataArray = serializeForms(formArray);
224    trackForms(formArray);
225    return true;
226}
227
228function initFormsWhenReady() {
229    // First attempt: works for non-modal pages
230    if (initForms()) return;
231
232    // Modal/AJAX pages: observe DOM until the form is injected
233    const mo = new MutationObserver(() => {
234        if (initForms()) {
235            mo.disconnect();
236        }
237    });
238
239    mo.observe(document.documentElement, {childList: true, subtree: true});
240
241    // Stop observing after 15s to avoid running forever on pages where modal never opens
242    setTimeout(() => {
243        mo.disconnect();
244    }, 15000);
245}
246
247window.addEventListener("load", (event) => {
248    initFormsWhenReady();
249    setUpTrackedEvents();
250});
251
252function getFormName(form) {
253    const qry = ':scope ';
254    let formName = '';
255    if (form.name) {
256        formName = form.name;
257    } else {
258        let headingsInOrder = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
259        let headingsInReverseOrder = headingsInOrder.reverse();
260        let heading = ''
261
262        headingsInReverseOrder.forEach(headingType => {
263            if (form.querySelectorAll(qry + headingType)[0]) {
264                heading = form.querySelectorAll(qry + headingType)[0];
265            }
266        });
267
268        if (heading) {
269            formName = heading.innerText;
270        } else {
271            formName = form.id;
272        }
273    }
274    return formName || 'Unknown Form';
275}
276
277function submitAttempt(formId) {
278    let formIndex;
279    formArray.forEach((formElement, index) => {
280        if (formElement.id === formId) {
281            formIndex = index;
282        }
283    });
284    return formIndex;
285}
286
287function pf2dl(formId, submitAction) {
288    window.dataLayer = window.dataLayer || [];
289    let submittedFormID = submitAttempt(formId);
290    if (typeof submittedFormID !== "number") {
291        return; // form not found / not initialized yet
292    }
293    if (!serializedFormDataArray || !serializedFormDataArray[submittedFormID]) {
294        return; // no serialized data available
295    }
296    let preparedForm = prepare(JSON.parse(JSON.stringify(serializedFormDataArray[submittedFormID])));
297    // add submitAction information to the form payload
298    preparedForm.submitAction = submitAction || 'unknown';
299    preparedForm.interactionToken = gUMG.interactionToken || 'unknown';
300    //Mixpanel Form Copy
301    document.mp_form_copy = JSON.parse(JSON.stringify(serializedFormDataArray[submittedFormID]));
302    let formName = getFormName(formArray[submittedFormID]);
303    let formEventObject = {};
304    formEventObject.event = 'Form Submit - ' + formName;
305    formEventObject['Form Submit - ' + formName] = JSON.parse(JSON.stringify(preparedForm));
306    window.dataLayer.push(formEventObject);
307}
308
309
310function pushFormChanges(submitAction) {
311    modifiedForms.forEach((modifiedForm) => {
312        pf2dl(modifiedForm, submitAction);
313    });
314}
315
316function setUpTrackedEvents() {
317    document.addEventListener('visibilitychange', function () {
318        if (document.visibilityState == 'hidden' && !dataSent.onUnload) {
319            pushFormChanges('visibilitychange');
320            dataSent.onUnload = true;
321
322        }
323    });
324    document.addEventListener('beforeunload', function () {
325        if (!dataSent.onUnload) {
326            pushFormChanges('beforeunload');
327            dataSent.onUnload = true;
328        }
329    });
330}
331
332function getFormElementFromTarget(target) {
333    const formElements = ["input", "select", "button"];
334    if (formElements.includes(target.tagName.toLowerCase())) {
335        return target;
336    } else {
337        return target.closest("div").querySelectorAll("select, input, button")[0];
338    }
339}

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.