1/** 2 * Footer Mobil: sticky Banner unten, Rotation alle 5s, Impression/Klick-Tracking. 3 * Rotation laeuft lokal anhand der einmalig eingebetteten Bannerliste. 4 */ 5(function () { 6 'use strict'; 7 8 const config = window.wiesnMobileFooterBanner || {}; 9 const bannerRoot = document.querySelector('[data-wiesn-mobile-footer-banner]'); 10 11 if (!bannerRoot || bannerRoot.getAttribute('data-wiesn-footer-rotator') === '1') { 12 return; 13 } 14 15 bannerRoot.setAttribute('data-wiesn-footer-rotator', '1'); 16 17 const banners = Array.isArray(config.banners) ? config.banners.filter((banner) => banner && banner.image) : []; 18 let lastBannerIndex = parseInt(bannerRoot.querySelector('[data-wiesn-mobile-footer-banner-link]')?.dataset.bannerIndex || '-1', 10); 19 const sessionCounts = {}; 20 const interval = parseInt(String(config.interval || 5000), 10); 21 let timer = null; 22 const mobileMq = window.matchMedia('(max-width: 1023px)'); 23 24 const canStart = () => { 25 if (!banners.length || !mobileMq.matches || document.hidden) { 26 return false; 27 } 28 return !document.body.classList.contains('wiesn-mobile-nav-open'); 29 }; 30 31 const isPaintedAndActive = () => { 32 if (bannerRoot.hidden) { 33 return false; 34 } 35 if (window.wiesnBannerRuntime && typeof window.wiesnBannerRuntime.isPlacementActive === 'function') { 36 return window.wiesnBannerRuntime.isPlacementActive('mobile_footer', bannerRoot); 37 } 38 const style = window.getComputedStyle(bannerRoot); 39 if (style.display === 'none' || style.visibility === 'hidden') { 40 return false; 41 } 42 const rect = bannerRoot.getBoundingClientRect(); 43 return rect.width >= 1 && rect.height >= 1; 44 }; 45 46 const pickFair = (list, excludeIndex, counts) => { 47 if (window.wiesnBannerRuntime && typeof window.wiesnBannerRuntime.pickFair === 'function') { 48 return window.wiesnBannerRuntime.pickFair(list, excludeIndex, counts); 49 } 50 51 const poolBase = list.filter((banner) => Number(banner.index) !== Number(excludeIndex)); 52 const pool = poolBase.length ? poolBase : list; 53 let best = null; 54 const candidates = []; 55 56 pool.forEach((banner) => { 57 const score = Number(counts[banner.index]) || 0; 58 if (best === null || score < best) { 59 best = score; 60 candidates.length = 0; 61 candidates.push(banner); 62 } else if (score === best) { 63 candidates.push(banner); 64 } 65 }); 66 67 return candidates.length ? candidates[Math.floor(Math.random() * candidates.length)] : null; 68 }; 69 70 const trackClick = (index) => { 71 if (window.wiesnBannerRuntime && typeof window.wiesnBannerRuntime.isPlatform === 'function' && window.wiesnBannerRuntime.isPlatform()) { 72 return; 73 } 74 if (!config.ajaxUrl || !config.nonce) { 75 return; 76 } 77 78 const params = new URLSearchParams({ 79 action: config.actions?.click || 'wiesn_mobile_footer_banner_click', 80 nonce: config.nonce, 81 index: String(index), 82 }); 83 84 if (typeof navigator.sendBeacon === 'function') { 85 navigator.sendBeacon(config.ajaxUrl, params); 86 return; 87 } 88 89 fetch(config.ajaxUrl, { 90 method: 'POST', 91 headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, 92 body: params.toString(), 93 keepalive: true, 94 }).catch(() => {}); 95 }; 96 97 const renderBanner = (payload) => { 98 const link = bannerRoot.querySelector('[data-wiesn-mobile-footer-banner-link]'); 99 const img = bannerRoot.querySelector('[data-wiesn-mobile-footer-banner-image]'); 100 101 if (!(link instanceof HTMLAnchorElement) || !(img instanceof HTMLImageElement) || !payload || !payload.image) { 102 return false; 103 } 104 105 const url = typeof payload.url === 'string' ? payload.url.trim() : ''; 106 const alt = typeof payload.alt === 'string' ? payload.alt : ''; 107 const index = typeof payload.index === 'number' ? payload.index : parseInt(String(payload.index), 10); 108 109 img.src = payload.image; 110 img.alt = alt; 111 link.dataset.bannerIndex = String(index); 112 113 if (url) { 114 link.href = url; 115 link.removeAttribute('aria-disabled'); 116 link.tabIndex = 0; 117 } else { 118 link.removeAttribute('href'); 119 link.setAttribute('aria-disabled', 'true'); 120 link.tabIndex = -1; 121 } 122 123 bannerRoot.hidden = false;
124 lastBannerIndex = Number.isFinite(index) ? index : lastBannerIndex; 125 sessionCounts[lastBannerIndex] = (sessionCounts[lastBannerIndex] || 0) + 1; 126 127 if (window.wiesnBannerRuntime && typeof window.wiesnBannerRuntime.queueImpression === 'function') { 128 window.wiesnBannerRuntime.queueImpression('mobile_footer', lastBannerIndex, bannerRoot); 129 } 130 131 return true; 132 }; 133 134 const refreshBanner = () => { 135 if (!canStart()) { 136 return; 137 } 138 if (!bannerRoot.hidden && !isPaintedAndActive()) { 139 return; 140 } 141 142 const payload = pickFair(banners, lastBannerIndex, sessionCounts); 143 144 if (!payload) { 145 return; 146 } 147 148 renderBanner(payload); 149 }; 150 151 const stopTimer = () => { 152 if (timer) { 153 window.clearInterval(timer); 154 timer = null; 155 } 156 }; 157 158 const hasPaintedImage = () => { 159 const img = bannerRoot.querySelector('[data-wiesn-mobile-footer-banner-image]'); 160 return !bannerRoot.hidden && img instanceof HTMLImageElement && !!img.getAttribute('src'); 161 }; 162 163 const recordShown = (index) => { 164 if (!Number.isFinite(index) || index < 0) { 165 return; 166 } 167 lastBannerIndex = index; 168 sessionCounts[lastBannerIndex] = (sessionCounts[lastBannerIndex] || 0) + 1; 169 if (window.wiesnBannerRuntime && typeof window.wiesnBannerRuntime.queueImpression === 'function') { 170 window.wiesnBannerRuntime.queueImpression('mobile_footer', lastBannerIndex, bannerRoot); 171 } 172 }; 173 174 const startTimer = () => { 175 stopTimer(); 176 if (interval > 0 && banners.length > 1 && canStart()) { 177 timer = window.setInterval(() => { 178 refreshBanner(); 179 }, interval); 180 } 181 }; 182 183 let running = false; 184 let initialCounted = false; 185 186 const syncPlacement = () => { 187 if (canStart()) { 188 if (!running) { 189 running = true; 190 if (hasPaintedImage()) { 191 if (!initialCounted) { 192 recordShown(lastBannerIndex); 193 initialCounted = true; 194 } 195 } else { 196 refreshBanner(); 197 initialCounted = true; 198 } 199 startTimer(); 200 } 201 return; 202 } 203 running = false; 204 stopTimer(); 205 }; 206 207 const link = bannerRoot.querySelector('[data-wiesn-mobile-footer-banner-link]'); 208 if (link instanceof HTMLAnchorElement) { 209 link.addEventListener('click', (event) => { 210 const index = parseInt(link.dataset.bannerIndex || '-1', 10); 211 if (index < 0 || !link.href || link.href === '#') { 212 event.preventDefault(); 213 return; 214 } 215 trackClick(index); 216 }); 217 } 218 219 syncPlacement(); 220 221 if (typeof mobileMq.addEventListener === 'function') { 222 mobileMq.addEventListener('change', syncPlacement); 223 } else if (typeof mobileMq.addListener === 'function') { 224 mobileMq.addListener(syncPlacement); 225 } 226 227 window.addEventListener('resize', syncPlacement); 228 document.addEventListener('visibilitychange', syncPlacement); 229 document.addEventListener('wiesn-mobile-nav-open', syncPlacement); 230 document.addEventListener('wiesn-mobile-nav-close', syncPlacement); 231 232 window.addEventListener('pagehide', () => { 233 stopTimer(); 234 }); 235})();
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.