PageSourceSearch

https://www.cannonworks.nl/assets/leadpages/leadpage-ggG66K6.js

js cannonworks.nl collected 2026-10-05 01:56:55 UTC 11,595 bytes, 296 lines download raw bytes

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('&', '&amp;')
257        .replaceAll('<', '&lt;')
258        .replaceAll('>', '&gt;')
259        .replaceAll('"', '&quot;')
260        .replaceAll("'", '&#039;');
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.