PageSourceSearch

https://www.startpeople.be/scripts/JS/dataLayerHelper.js

js startpeople.be collected 2026-09-28 10:16:04 UTC 9,649 bytes, 260 lines download raw bytes

1export class DataLayerHelper {
2    dataLayerLocalStorageKey = "dxp_data_layer";
3
4    pushPendingDataLayerEvents() {
5        const dataLayerEvents = this.getDataLayerEventsFromLocalStorage().concat(this.getDataLayerEventsFromServer());
6        for (let i = 0; i < dataLayerEvents.length; i++) {
7            this.pushDataLayerEvent(dataLayerEvents[i]);
8        }
9
10        localStorage.removeItem(this.dataLayerLocalStorageKey);
11    }
12
13    pushDataLayerEvent(dataLayerEvent, toLocalStorage = false) {
14        if (toLocalStorage) {
15            this.addDataLayerEventToLocalStorage(dataLayerEvent);
16        } else {
17            dataLayer.push(dataLayerEvent);
18        }
19    }
20
21    getDataLayerEventsFromServer() {
22        const value = $("[data-dxp-data-layer-events-server]").html().trim();
23        if (!value) {
24            return [];
25        }
26
27        return JSON.parse(value);
28    }
29
30    getDataLayerEventsFromLocalStorage() {
31        const value = localStorage.getItem(this.dataLayerLocalStorageKey);
32        if (!value) {
33            return [];
34        }
35
36        return JSON.parse(value);
37    }
38
39    addDataLayerEventToLocalStorage(dataLayerEvent) {
40        let value = this.getDataLayerEventsFromLocalStorage();
41        value.push(dataLayerEvent);
42        localStorage.setItem(this.dataLayerLocalStorageKey, JSON.stringify(value));
43    }
44}
45
46document.addEventListener('DOMContentLoaded', function () {
47
48    const dxpDataLayer = document.querySelector('.dxp-data-layer-area');
49    const dxpInputTags = ['INPUT', 'SELECT', 'TEXTAREA'];
50    const dxpClickTags = ['A', 'I', 'BUTTON'];
51
52    // Track fields that have had their validation-error pushed to avoid duplicates
53    const triggeredFields = {};
54
55    // MutationObserver to detect text content changes or child node additions in .invalid-feedback elements
56    const observer = new MutationObserver(function (mutationsList) {
57        mutationsList.forEach(mutation => {
58            // If the mutation is related to text changes or children being added
59            if (mutation.type === 'characterData' || mutation.type === 'childList') {
60                const invalidFeedback = mutation.target;
61                const isUmbracoFormElement = invalidFeedback.parentElement?.closest('.umbraco-forms-form');
62
63                if (!isUmbracoFormElement &&
64                    invalidFeedback.nodeType === Node.ELEMENT_NODE &&
65                    invalidFeedback.classList.contains('invalid-feedback')) {
66                    const fieldName = invalidFeedback.getAttribute('data-validation-messages-for');
67                    const fieldInput = document.querySelector(`[data-input-field="${fieldName}"]`);
68                    const fieldOrder = getFieldOrder(fieldInput);
69                    const message = invalidFeedback.textContent.trim();
70
71                    // Only trigger event if there is a message content and if it hasn't been triggered yet
72                    if (message && !triggeredFields[fieldName]) {
73                        // Find the closest parent form and get its ID
74                        const form = invalidFeedback.closest('form');
75                        const formId = form ? form.id : null;
76
77                        // Push event to dataLayer
78                        window.dataLayer = window.dataLayer || [];
79                        window.dataLayer.push({
80                            'event': 'validation-error',
81                            'validation': {
82                                'status': 'invalid',
83                                'form': formId,
84                                'field': fieldName,
85                                'message': message,
86                            }
87                        });
88
89                        pushFormFieldEvent('formFieldError', invalidFeedback, fieldName, fieldOrder, false);
90
91                        // Mark this field as triggered to prevent duplicate events
92                        triggeredFields[fieldName] = true;
93
94                        // Optional: Reset the triggered state after a timeout to allow for potential updates
95                        setTimeout(() => {
96                            triggeredFields[fieldName] = false;
97                        }, 1000); // Adjust timeout based on how long validation messages stay visible
98                    }
99                }
100            }
101        });
102    });
103
104    const invalidFeedbackElements = document.querySelectorAll('.invalid-feedback');
105    invalidFeedbackElements.forEach(element => {
106        observer.observe(element, { characterData: true, childList: true, subtree: true });
107    });
108
109    dxpDataLayer.addEventListener('click', (event) => {
110        handleClickEvent(event.target, event);
111    });
112
113    dxpDataLayer.addEventListener('change', (event) => {
114        pushFormFieldEvent('formFieldEdit', event.target);
115    });
116
117    dxpDataLayer.addEventListener('focusin', (event) => {
118        handleInputEvent(event.target, event);
119        pushFormFieldEvent('formFieldFocus', event.target);
120    });
121
122    dxpDataLayer.addEventListener('focusout', (event) => {
123        handleInputEvent(event.target, event);
124    });
125
126    function handleClickEvent(target, event) {
127        if (dxpClickTags.includes(target.tagName) && !target.closest('[data-tglyr-plugin-id]')) {
128		
129		    if (!['A', 'BUTTON'].includes(target.tagName)) {
130                target = target.parentElement;
131            }
132			
133            const targetHref = target.href || null;
134            const targetName = target.getAttribute('aria-label') || target.id || target.textContent.trim() || 'unknown_element';
135            pushDataLayerEvent(event.type, 'button_click', targetName);
136			
137            if (targetHref) {
138                event.preventDefault();
139                setTimeout(() => {
140                    var anchor = document.createElement('a');
141                    anchor.href = targetHref;
142                    anchor.target = target.target;
143                    anchor.click();
144                }, 100);
145            }
146        }
147    }
148
149    function handleInputEvent(target, event) {
150        if (dxpInputTags.includes(target.tagName)) {
151            const targetName = target.id || target.name || 'unknown_input';
152            const interaction = target.type === 'file' ? 'upload_interaction' : 'input_interaction';
153            const targetValue = target.type === 'file' ? target.value?.split('.')?.pop() ?? null : null;
154            pushDataLayerEvent(event.type, interaction, targetName, targetValue);
155        }
156    }
157
158    function pushDataLayerEvent(eventType, interactionType, fieldName, value = null) {
159        window.dataLayer = window.dataLayer || [];
160        const eventData = {
161            'event': interactionType,
162            'interaction': {
163                'type': eventType,
164                'field': fieldName
165            }
166        };
167        if (value) {
168            eventData.interaction.value = value;
169        }
170        window.dataLayer.push(eventData);
171    }
172
173    function pushFormFieldEvent(eventType, target, fieldName = null, fieldOrder = null, checkTag = true) {
174        if (dxpInputTags.includes(target.tagName) || isSelect2Element(target) || !checkTag) {
175            window.dataLayer = window.dataLayer || [];
176            window.dataLayer.push({
177                'event': eventType,
178                'form': {
179                    field: {
180                        'name': fieldName ?? getFieldName(target),
181                        'order': fieldOrder ?? getFieldOrder(target),
182                        'time': ((Date.now() - new Date().setHours(0, 0, 0, 0)) * 10000)
183                    }
184                }
185            });
186        }
187    }
188
189    function getFieldName(target) {
190        const unknown = 'unknown_input';
191
192        if (!target) {
193            return unknown;
194        }
195
196        if (isSelect2Element(target)) {
197            var select = getSelect2Select(target);
198            return select.name?.split('.')?.pop() || select.id || unknown;
199        }
200
201        return target.name?.split('.')?.pop() || target.id || unknown;
202    }
203
204    function getFieldOrder(target) {
205        const form = target.form;
206
207        if (!form && isSelect2Element(target)) {
208            const select = getSelect2Select(target);
209            if (select) {
210                const selectForm = select.closest('form');
211                const formFields = getFormFields(selectForm);
212                if (formFields.length) {
213                    return [...formFields].indexOf(select);
214                }
215            }
216            return 0;
217        }
218
219        if (!form) {
220            return 0;
221        }
222
223        const formFields = getFormFields(form);
224        return [...formFields].indexOf(target);
225    }
226
227    function getFormFields(form) {
228
229        if (!form) {
230            return [];
231        }
232
233        const isValid = (field) => {
234            if (field.disabled) return false;
235            if (field.readOnly) return false;
236            if (field.type === 'hidden') return false;   
237            const style = window.getComputedStyle(field);
238            if (style.display === 'none' || style.visibility === 'hidden') return false;
239            return true;
240        }
241
242        return [...form.elements].filter(isValid);
243    }
244
245    function getSelect2Select(target) {
246        if (isSelect2Element(target)) {
247            const container = target.closest('.select2-container');
248            const selectItem = container.previousElementSibling;
249            if (selectItem && selectItem.tagName === 'SELECT') {
250                return selectItem;
251            }
252        }
253
254        return null;
255    }
256
257    function isSelect2Element(target) {
258        return target.classList.contains('select2-selection');
259    }    
260});

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.