PageSourceSearch

https://www.dakotacooks.com/wp-content/themes/dakotacooks/js/tec-pro-mobile-buttons.js?ver=1785265272

js dakotacooks.com collected 2026-10-02 05:19:43 UTC 12,751 bytes, 281 lines download raw bytes

1document.addEventListener('DOMContentLoaded', function () {
2    /**
3     * Fetches ticket data for a collection of event rows in a single bulk AJAX request
4     * and updates the 'BUY TICKETS' buttons with the retrieved URLs and status tags.
5     * @param {NodeListOf<Element>} eventRows - A collection of event row elements.
6     */
7    function fetchAndUpdateTicketData(eventRows) {
8        if (!eventRows || eventRows.length === 0) {
9            return;
10        }
11
12        const eventIds = [];
13        eventRows.forEach(row => {
14            const button = row.querySelector('.buy-tickets-button[data-post-id]');
15            if (button) {
16                const postId = button.getAttribute('data-post-id');
17                if (postId) {
18                    eventIds.push(postId);
19                }
20            }
21        });
22
23        if (eventIds.length === 0) {
24            return; // No IDs to fetch
25        }
26
27        // Use the 'dakotaAjax' object passed from PHP
28        const { ajaxUrl, nonce } = window.dakotaAjax;
29
30        const formData = new URLSearchParams();
31        formData.append('action', 'get_bulk_etix_data');
32        formData.append('nonce', nonce);
33        formData.append('event_ids', JSON.stringify(eventIds));
34        fetch(ajaxUrl, {
35            method: 'POST',
36            body: formData,
37        })
38            .then(response => {
39                if (!response.ok) {
40                    throw new Error(`HTTP error! status: ${response.status}`);
41                }
42                return response.json();
43            })
44            .then(response => {
45                if (response.success) {
46                    const ticketData = response.data;
47                    // Loop through the returned data (key is the event ID)
48                    for (const eventId in ticketData) {
49                        if (!ticketData.hasOwnProperty(eventId)) continue; // Security check
50
51                        const data = ticketData[eventId];
52                        // Find all matching buttons for this event ID (handles both views)
53                        const buttons = document.querySelectorAll(`.buy-tickets-button[data-post-id="${eventId}"]`);
54
55                        buttons.forEach(button => {
56                            // Check if a valid ticket link was returned
57                            if (data.link && data.link !== '') {
58                                // Security: Basic URL validation - ensure it's a proper URL
59                                try {
60                                    new URL(data.link); // This will throw if URL is invalid
61                                    button.href = data.link;
62                                } catch (e) {
63                                    //console.warn('Invalid URL detected, hiding button:', data.link);
64                                    button.style.display = 'none';
65                                    return;
66                                }
67
68                                // Add status tags as CSS classes (already sanitized server-side)
69                                if (data.status_tags && Array.isArray(data.status_tags)) {
70                                    data.status_tags.forEach(tag => {
71                                        // Additional validation: ensure tag is a string and safe for CSS
72                                        if (typeof tag === 'string' && tag.length > 0) {
73                                            button.classList.add(`tag-${tag}`);
74                                        }
75                                    });
76                                }
77                            } else {
78                                // If no link, hide the 'BUY TICKETS' button for a clean UI
79                                button.style.display = 'none';
80                            }
81                        });
82                    }
83                }
84            })
85            .catch(error => console.error('Error fetching ticket data:', error));
86    }
87
88    /**
89     * Creates and inserts the 'EVENT DETAILS' and 'BUY TICKETS' buttons into an event row's header.
90     * @param {Element} eventRow - The event row element.
91     */
92    function addButtonsToEvent(eventRow) {
93        // Support both list and day view headers
94        const header = eventRow.querySelector('.tribe-events-calendar-list__event-header, .tribe-events-calendar-day__event-header');
95        // Support both list and day view title links
96        const eventLinkElement = eventRow.querySelector('.tribe-events-calendar-list__event-title-link, .tribe-events-calendar-day__event-title-link');
97
98        if (!header || !eventLinkElement || header.querySelector('.event-buttons-container')) {
99            return; // Abort if elements are missing or buttons already exist
100        }
101
102        const eventUrl = eventLinkElement.getAttribute('href');
103        const buttonContainer = document.createElement('div');
104        buttonContainer.className = 'event-buttons-container';
105
106        const detailsButton = document.createElement('a');
107        detailsButton.className = 'event-button event-details-button';
108        detailsButton.href = eventUrl;
109        detailsButton.textContent = 'EVENT DETAILS';
110        detailsButton.setAttribute('role', 'button');
111
112        const buyTicketsButton = document.createElement('a');
113        buyTicketsButton.className = 'event-button buy-tickets-button';
114        buyTicketsButton.href = '#'; // Placeholder
115        buyTicketsButton.textContent = 'BUY TICKETS';
116        buyTicketsButton.setAttribute('role', 'button');
117
118        // Try to find the article element more robustly (search up the tree if needed)
119        let articleElement = eventRow.querySelector('article[class*="post-"]');
120        if (!articleElement) {
121            // Try searching ancestors (in case markup differs in day view)
122            articleElement = eventRow.closest('article[class*="post-"]');
123        }
124        let postId = null;
125        if (articleElement) {
126            const postIdClass = Array.from(articleElement.classList).find(cls => cls.startsWith('post-'));
127            if (postIdClass) {
128                postId = postIdClass.replace('post-', '');
129                // Security: Validate the post ID is numeric before using it
130                if (/^\d+$/.test(postId)) {
131                    buyTicketsButton.setAttribute('data-post-id', postId);
132                    // Debug: Log for troubleshooting
133                    // console.log('Set data-post-id:', postId, 'for', buyTicketsButton, 'in', eventRow);
134                } else {
135                    // console.warn('Non-numeric post ID found:', postIdClass);
136                }
137            } else {
138                // console.warn('No post-* class found on article:', articleElement);
139            }
140        } else {
141            // console.warn('No article[class*="post-"] found for eventRow:', eventRow);
142        }
143
144        buttonContainer.appendChild(detailsButton);
145        buttonContainer.appendChild(buyTicketsButton);
146        header.appendChild(buttonContainer);
147    }
148
149    // --- NEW, SEPARATE FUNCTION ---
150    /**
151     * Finds the event date/time string, splits it, and wraps each part in its own span.
152     * This function is self-contained and does not interact with button/data logic.
153     * @param {Element} eventRow - The event row element.
154     */
155    function formatEventDateTime(eventRow) {
156        // Support both list and day view datetime containers
157        const timeElement = eventRow.querySelector('.tribe-events-calendar-list__event-datetime, .tribe-events-calendar-day__event-datetime');
158        if (!timeElement) return;
159
160        const originalSpan = timeElement.querySelector('.tribe-event-date-start');
161        if (!originalSpan || !originalSpan.textContent.includes('|')) return;
162
163        const parts = originalSpan.textContent.split('|').map(part => part.trim());
164        if (parts.length < 2) return;
165
166        const dateStr = parts[0];
167        const timeStr = parts[1];
168
169        const dateSpan = document.createElement('span');
170        dateSpan.className = 'event-list-date';
171        dateSpan.textContent = dateStr;
172
173        const timeSpan = document.createElement('span');
174        timeSpan.className = 'event-list-time';
175        timeSpan.textContent = timeStr;
176
177        timeElement.innerHTML = '';
178        timeElement.appendChild(dateSpan);
179        timeElement.appendChild(timeSpan);
180    }
181
182    /**
183     * Processes a collection of newly added nodes, adds buttons, and then triggers the data fetch.
184     * @param {NodeListOf<Node>} addedNodes - Nodes added to the DOM.
185     */
186    function processNewNodes(addedNodes) {
187        const newEventRows = [];
188        addedNodes.forEach(node => {
189            if (node.nodeType === 1) { // Is an element
190                // Support both list and day view event rows
191                if (node.matches && node.matches('.tribe-events-calendar-list__event-row, .tribe-events-calendar-day__event-row')) {
192                    addButtonsToEvent(node);
193                    formatEventDateTime(node);
194                    newEventRows.push(node);
195                } else {
196                    const foundRows = node.querySelectorAll('.tribe-events-calendar-list__event-row, .tribe-events-calendar-day__event');
197                    if (foundRows.length > 0) {
198                        foundRows.forEach(row => {
199                            addButtonsToEvent(row);
200                            formatEventDateTime(row);
201                            newEventRows.push(row);
202                        });
203                    }
204                }
205            }
206        });
207
208        if (newEventRows.length > 0) {
209            fetchAndUpdateTicketData(newEventRows);
210        }
211    }
212
213    /**
214     * Wraps unlinked featured images in anchor tags so they navigate to the event.
215     * Covers Month view and List view image wrappers.
216     */
217    function linkEventImages() {
218        var selectors = [
219            {
220                wrapper: '.tribe-events-calendar-month__calendar-event-featured-image-wrapper',
221                link: '.tribe-events-calendar-month__calendar-event-title-link'
222            },
223            {
224                wrapper: '.tribe-events-calendar-list__event-featured-image-wrapper',
225                link: '.tribe-events-calendar-list__event-title-link'
226            },
227            {
228                wrapper: '.tribe-events-calendar-month-mobile-events__mobile-event-featured-image-wrapper',
229                link: '.tribe-events-calendar-month-mobile-events__mobile-event-title-link'
230            }
231        ];
232
233        selectors.forEach(function (sel) {
234            document.querySelectorAll(sel.wrapper).forEach(function (imgWrapper) {
235                if (imgWrapper.querySelector('a')) return;
236                var event = imgWrapper.closest('article') || imgWrapper.closest('[class*="calendar-event"]');
237                if (!event) return;
238                var titleLink = event.querySelector(sel.link);
239                if (!titleLink) return;
240
241                var anchor = document.createElement('a');
242                anchor.href = titleLink.href;
243                anchor.setAttribute('aria-hidden', 'true');
244                anchor.setAttribute('tabindex', '-1');
245                while (imgWrapper.firstChild) {
246                    anchor.appendChild(imgWrapper.firstChild);
247                }
248                imgWrapper.appendChild(anchor);
249            });
250        });
251    }
252
253    // --- Initial page load processing ---
254    // Support both list and day view event rows
255    const initialEventRows = document.querySelectorAll('.tribe-events-calendar-list__event-row, .tribe-events-calendar-day__event');
256    if (initialEventRows.length > 0) {
257        initialEventRows.forEach(row => {
258            addButtonsToEvent(row);
259            formatEventDateTime(row);
260        });
261        fetchAndUpdateTicketData(initialEventRows);
262    }
263    linkEventImages();
264
265    // --- Set up MutationObserver to handle events loaded via AJAX ---
266    const observer = new MutationObserver(mutationsList => {
267        for (const mutation of mutationsList) {
268            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
269                processNewNodes(mutation.addedNodes);
270                linkEventImages();
271                // We only process the first mutation with added nodes to avoid redundant checks
272                break;
273            }
274        }
275    });
276
277    observer.observe(document.body, {
278        childList: true,
279        subtree: true
280    });
vendor: 3 bytes, line 281
281});

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.