1import '../stimulus_bootstrap.js'; 2/* 3 * Welcome to your app's main JavaScript file! 4 * 5 * This file will be included onto the page via the importmap() Twig function, 6 * which should already be in your base.html.twig. 7 */ 8import './leadpage.css'; 9 10function openMenu(removed, added) { 11 const menu = document.getElementById("menu"); 12 menu.classList.remove(removed) 13 menu.classList.add(added) 14 15 const overlay = document.getElementById("overlay"); 16 if (removed == 'menuopen') { 17 overlay.style.display = "none"; 18 } else { 19 overlay.style.display = "block"; 20 } 21 22} 23 24// $(document).ready(function(){ 25// var boxWidth = "80%"; 26// var boxSmallWidth = "0%"; 27// $("#exitBtn").click(function(){ 28// $("#menu").animate({ 29// width: boxSmallWidth, 30// display: "none" 31// }); 32// }); 33// $("#hamburger").click(function(){ 34// $("#menu").animate({ 35// width: boxWidth 36// }); 37// $("menu").style.display = "flex"; 38// }); 39// }); 40 41const hamburgerEl = document.getElementById('hamburger'); 42if (hamburgerEl) { 43 hamburgerEl.onclick = function () { 44 openMenu("closed", "menuopen") 45 }; 46} 47 48const exitBtnEl = document.getElementById('exitBtn'); 49if (exitBtnEl) { 50 exitBtnEl.onclick = function () { 51 openMenu("menuopen", "closed") 52 }; 53} 54 55const overlayEl = document.getElementById('overlay'); 56if (overlayEl) { 57 overlayEl.onclick = function () { 58 openMenu("menuopen", "closed"); 59 }; 60} 61 62const searchFormEl = document.getElementById('searchform'); 63if (searchFormEl) { 64 searchFormEl.addEventListener("submit", (e) => { 65 e.preventDefault(); 66 const editieid = searchFormEl.dataset.editieid; 67 const postcode = document.getElementById("postcodeinput"); 68 const huisnr = document.getElementById("huisnummerinput"); 69 if (postcode && huisnr) { 70 window.location.href = "https://www.cannonworks.nl/connect-cannonworks?editie=" + editieid + "&postcode=" + postcode.value + "&nummer=" + huisnr.value; 71 } 72 }); 73} 74 75 76document.addEventListener('DOMContentLoaded', function () { 77 console.log("DOM fully loaded and parsed"); 78 initializeClickListeners(); 79 setupAdvertisersLazyLoad(); 80}); 81 82function setupAdvertisersLazyLoad() { 83 const gridEl = document.getElementById('adverteerders-grid'); 84 const loadingEl = document.getElementById('adverteerders-loading'); 85 const sectionEl = document.getElementById('adverteerders-section'); 86 if (!gridEl) { 87 return; 88 } 89 90 let hasLoaded = false; 91 const hadSSR = gridEl.children && gridEl.children.length > 0; 92} 93 94function renderAdvertisers(rows, gridEl) { 95 if (!rows || rows.length === 0) return; 96 97 function starHtml(avg, count) { 98 const a = Math.max(0, Math.min(5, Number(avg) || 0)); 99 const full = Math.floor(a); 100 const half = (a - full) >= 0.5 ? 1 : 0; 101 let stars = ''; 102 for (let i = 1; i <= 5; i++) { 103 if (i <= full) stars += 'â '; 104 else if (i === full + 1 && half) stars += 'â'; // simple half placeholder 105 else stars += 'â'; 106 } 107 const label = `${a.toFixed(1)}`; 108 return `<span style="color:#f7a400; font-weight:700; letter-spacing:1px;">${stars}</span> <span style="color:#355c7d; font-size:13px;">${label}</span>`; 109 } 110 111 const parts = []; 112 // Read editieId from bootstrap element for dynamic CTA link 113 const tokenEl = document.getElementById('app'); 114 const editieId = tokenEl ? (tokenEl.dataset.editieId || '') : ''; 115 116 rows.forEach(row => { 117 const handelsnaam = (row && row.handelsnaam) ? escapeHtml(row.handelsnaam) : 'Onbekend'; 118 const plaats = (row && row.plaats) ? escapeHtml(row.plaats) : 'Onbekend'; 119 const lic = (row && row.licentie_id) ? encodeURIComponent(row.licentie_id) : ''; 120 const rawLic = (row && row.licentie_id_raw) ? Number(row.licentie_id_raw) : 0; 121 const org = (row && row.organisatie_id) ? Number(row.organisatie_id) : 0; 122 const logo = (row && row.logo) ? row.logo : 'https://secure.cannonworks.com/images/bedrijfslogo_CW.png'; 123 const dataAttrs = (rawLic > 0 && org > 0) ? ` data-lic-raw="${rawLic}" data-org="${org}"` : ''; 124 parts.push(` 125 <div class="ad-card ad-reveal-prep" aria-hidden="true"${dataAttrs}> 126 <div class="ad-card-container"> 127 <div class="ad-card-logo"> 128 <img alt="${handelsnaam} bedrijfslogo" src="${logo}"> 129 </div> 130 <div style="display: flex; flex-direction: column; justify-content: center;"> 131 <div class="ad-title">${handelsnaam}</div> 132 <div class="ad-distance">${plaats}</div> 133 </div> 134 </div> 135 <div style=" display: flex; justify-content: flex-end; align-items: center;"> 136 <a href="https://www.cannonworks.nl/connect-cannonworks?editie=${encodeURIComponent(editieId)}&ref=${lic}" class="ad-btn" aria-label="Gratis offerte aanvragen"><span class="label-full">GRATIS OFFERTE AANVRAGEN</span><span class="label-short">AANVRAGEN</span></a> 137 </div> 138 </div> 139 `); 140 }); 141 gridEl.innerHTML = parts.join(''); 142 143 // Fetch and render star averages in batch (only for rows having licentie_id_raw & organisatie_id
143) 144 try { 145 const items = []; 146 const seen = new Set(); 147 rows.forEach(r => { 148 const licRaw = r && r.licentie_id_raw ? Number(r.licentie_id_raw) : 0; 149 const org = r && r.organisatie_id ? Number(r.organisatie_id) : 0; 150 if (licRaw > 0 && org > 0) { 151 const key = licRaw + '|' + org; 152 if (!seen.has(key)) { 153 seen.add(key); 154 items.push({licentie_id: licRaw, organisatie_id: org}); 155 } 156 } 157 }); 158 if (items.length > 0) { 159 const tokenEl = document.getElementById('app'); 160 const token = tokenEl ? tokenEl.dataset.apiToken : ''; 161 const editieId = tokenEl ? tokenEl.dataset.editieId : ''; 162 fetch('/api/adverteerders-sterren-batch', { 163 method: 'POST', 164 headers: { 165 'Content-Type': 'application/json', 166 'X-Requested-With': 'XMLHttpRequest', 167 'X-Page-Token': token, 168 'X-Editie-Id': editieId 169 }, 170 body: JSON.stringify({items}) 171 }) 172 .then(res => res.json()) 173 .then(json => { 174 const rowsRes = (json && Array.isArray(json.rows)) ? json.rows : []; 175 rowsRes.forEach(it => { 176 const lic = Number(it.licentie_id); 177 const org = Number(it.organisatie_id); 178 const avg = Number(it.starAverage || 0); 179 const cnt = Number(it.count || 0); 180 // Only show stars when a rating exists (avg>0 and count>0) 181 if (!(cnt > 0 && avg > 0)) { 182 return; 183 } 184 const card = gridEl.querySelector(`.ad-card[data-lic-raw="${lic}"][data-org="${org}"]`); 185 if (card) { 186 let starsEl = card.querySelector('.ad-stars'); 187 if (!starsEl) { 188 // Create the stars container above the title for advertiser cards only 189 const titleEl = card.querySelector('.ad-title'); 190 if (titleEl && titleEl.parentElement) { 191 starsEl = document.createElement('div'); 192 starsEl.className = 'ad-stars'; 193 starsEl.style.maxHeight = '28px'; 194 starsEl.style.marginBottom = '2px'; 195 starsEl.style.marginTop = '-10px'; 196 titleEl.parentElement.insertBefore(starsEl, titleEl); 197 } 198 } 199 if (starsEl) { 200 starsEl.innerHTML = starHtml(avg, cnt); 201 } 202 } 203 }); 204 }) 205 .catch(() => {/* ignore */ 206 }); 207 } 208 } catch (e) { 209 // ignore 210 } 211 212 // Apply and observe animations for newly injected cards 213 setupAdCardReveal(gridEl); 214} 215 216function setupAdCardReveal(gridEl) { 217 const cards = gridEl.querySelectorAll('.ad-card'); 218 const prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; 219 220 // Fallback: if reduced motion or no IntersectionObserver, reveal instantly 221 if (prefersReduced || !('IntersectionObserver' in window)) { 222 cards.forEach(card => { 223 card.classList.add('ad-reveal-in'); 224 card.classList.remove('ad-reveal-prep'); 225 card.removeAttribute('aria-hidden'); 226 card.style.transitionDelay = '0ms'; 227 }); 228 return; 229 } 230 231 const observer = new IntersectionObserver((entries, obs) => { 232 entries.forEach(entry => { 233 if (entry.isIntersecting) { 234 const el = entry.target; 235 const idx = parseInt(el.getAttribute('data-ad-index') || '0', 10); 236 // Light stagger capped at 300ms total 237 el.style.transitionDelay = `${Math.min(idx * 60, 300)}ms`; 238 el.classList.add('ad-reveal-in'); 239 el.classList.remove('ad-reveal-prep'); 240 el.removeAttribute('aria-hidden'); 241 obs.unobserve(el); 242 } 243 }); 244 }, {threshold: 0.05, rootMargin: '0px 0px -5% 0px'}); 245 246 cards.forEach((card, i) => { 247 if (!card.hasAttribute('data-ad-index')) { 248 card.setAttribute('data-ad-index', String(i)); 249 } 250 observer.observe(card); 251 }); 252} 253 254function escapeHtml(str) { 255 return String(str) 256 .replaceAll('&', '&') 257 .replaceAll('<', '<') 258 .replaceAll('>', '>') 259 .replaceAll('"', '"') 260 .replaceAll("'", '''); 261} 262 263function initializeClickListeners() { 264 const items = document.querySelectorAll(".werktitem"); 265 let activeItem = null; 266
267 items.forEach((item, index) => { 268 const step = index + 1; 269 const content = document.getElementById(`stap-content${step}`); 270 const icon = document.getElementById(`stapicon${step}`); 271 272 273 item.addEventListener('click', function () { 274 console.log(`Clicked ${step}`); 275 276 // Close the active item if it's not the clicked one 277 if (activeItem && activeItem !== content) { 278 activeItem.classList.remove('open'); 279 activeItem.previousElementSibling.querySelector('i').classList.remove('rotated'); 280 } 281 282 // Toggle the current step 283 const isOpen = content.classList.toggle('open'); 284 icon.classList.toggle('rotated', isOpen); 285 286 activeItem = isOpen ? content : null; 287 }); 288 }); 289} 290 291document.addEventListener('turbo:load', () => { 292 // Find all <a> tags that do not already have a data-turbo attribute 293 document.querySelectorAll('a:not([data-turbo])').forEach((link) => { 294 link.dataset.turbo = 'false'; 295 }); 296});
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.