1(function () { 2 'use strict'; 3 4 var PAGE_SIZE = 10; 5 6 var PIN_SVG = '<svg width="16" height="20" viewBox="0 0 16 20" fill="none" aria-hidden="true"><path d="M8 0C5.87827 0 3.84344 0.842855 2.34315 2.34315C0.842855 3.84344 0 5.87827 0 8C0 13.25 7 19.5 7.35 19.77C7.54762 19.9205 7.77057 20 8 20C8.22943 20 8.45238 19.9205 8.65 19.77C9 19.5 16 13.25 16 8C16 5.87827 15.1571 3.84344 13.6569 2.34315C12.1566 0.842855 10.1217 0 8 0ZM8 11C7.20887 11 6.43552 10.7654 5.77772 10.3259C5.11992 9.88633 4.60723 9.26164 4.30448 8.53073C4.00173 7.79983 3.92252 6.99556 4.07686 6.21964C4.2312 5.44372 4.61216 4.73098 5.17157 4.17157C5.73098 3.61216 6.44372 3.2312 7.21964 3.07686C7.99556 2.92252 8.79983 3.00173 9.53073 3.30448C10.2616 3.60723 10.8863 4.11992 11.3259 4.77772C11.7654 5.43552 12 6.20887 12 7C12 8.06087 11.5786 9.07828 10.8284 9.82843C10.0783 10.5786 9.06087 11 8 11Z" fill="currentColor"/></svg>'; 7 var SPINNER_HTML = '<span class="evf-spinner" aria-hidden="true"></span> Localisationâ¦'; 8 9 function ready(fn) { 10 if (document.readyState !== 'loading') fn(); 11 else document.addEventListener('DOMContentLoaded', fn); 12 } 13 14 ready(function () { 15 document.querySelectorAll('.ev-filter-container').forEach(initInstance); 16 }); 17 18 // ââ Instance ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 19 function initInstance(container) { 20 if (container.dataset.filterInited) return; 21 container.dataset.filterInited = '1'; 22 23 var inst = { 24 container: container, 25 cardSel: container.dataset.cardSelector || '.ev-filter-card', 26 offcanvas: null, 27 pagination: { page: 1, perPage: PAGE_SIZE }, 28 userLat: null, 29 userLng: null, 30 leafletMap: null, 31 leafletMarkers: [], 32 markerCluster: null, 33 mapIcon: container.dataset.mapIcon 34 ? L.icon({ 35 iconUrl: container.dataset.mapIcon, 36 iconSize: [32, 40], 37 iconAnchor: [16, 40], 38 popupAnchor: [0, -42], 39 }) 40 : null, 41 }; 42 43 initOffcanvas(inst); 44 initFilters(inst); 45 initPagination(inst); 46 initGeo(inst); 47 initDeptCommuneFilter(inst); 48 initDateShortcuts(inst); 49 initToggleAll(inst); 50 initMapToggle(inst); 51 applyFilters(inst); 52 } 53 54 // ââ Offcanvas âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 55 function initOffcanvas(inst) { 56 var container = inst.container; 57 var offcanvasId = container.id + '-offcanvas'; 58 var offcanvas = document.getElementById(offcanvasId); 59 inst.offcanvas = offcanvas; 60 61 var backdrop = container.querySelector('.ev-offcanvas-backdrop'); 62 var openBtn = container.querySelector('.ev-btn-filtres'); 63 var closeBtn = offcanvas && offcanvas.querySelector('.ev-offcanvas-close'); 64 var applyBtn = offcanvas && offcanvas.querySelector('.ev-btn-apply-filters'); 65 66 function openPanel() { 67 if (!offcanvas) return; 68 offcanvas.classList.add('open'); 69 if (backdrop) backdrop.classList.add('open'); 70 if (openBtn) openBtn.setAttribute('aria-expanded', 'true'); 71 document.body.style.overflow = 'hidden'; 72 } 73 74 function closePanel() { 75 if (!offcanvas) return; 76 offcanvas.classList.remove('open'); 77 if (backdrop) backdrop.classList.remove('open'); 78 if (openBtn) openBtn.setAttribute('aria-expanded', 'false'); 79 document.body.style.overflow = ''; 80 } 81 82 inst.closePanel = closePanel; 83 84 if (openBtn) openBtn.addEventListener('click', openPanel); 85 if (closeBtn) closeBtn.addEventListener('click', closePanel); 86 if (backdrop) backdrop.addEventListener('click', closePanel); 87 88 if (applyBtn) { 89 applyBtn.addEventListener('click', function () { 90 inst.pagination.page = 1; 91 applyFilters(inst); 92 closePanel(); 93 }); 94 } 95 96 document.addEventListener('keydown', function (e) { 97 if (e.key === 'Escape' && offcanvas && offcanvas.classList.contains('open')) { 98 closePanel(); 99 } 100 }); 101 } 102 103 // ââ Géolocalisation âââââââââââââââââââââââââââââââââââââââââââââââââââââââ 104 function initGeo(inst) { 105 var btns = inst.container.querySelectorAll('.evf-autour-de-moi'); 106 if (!btns.length) return; 107 108 function setLoading() { 109 btns.forEach(function (b) { b.innerHTML = SPINNER_HTML; b.disabled = true; }); 110 } 111 112 function setActive() { 113 btns.forEach(function (b) { 114 b.classList.add('evf-autour-de-moi--active'); 115 b.innerHTML = PIN_SVG + ' Position acquise â'; 116 b.disabled = false;
117 }); 118 } 119 120 function setReset() { 121 btns.forEach(function (b) { 122 b.classList.remove('evf-autour-de-moi--active'); 123 b.textContent = 'Afficher les événements autour de moi'; 124 b.disabled = false; 125 }); 126 } 127 128 btns.forEach(function (btn) { 129 btn.addEventListener('click', function () { 130 if (inst.userLat !== null) { 131 inst.userLat = null; 132 inst.userLng = null; 133 var rayonSel = inst.offcanvas && inst.offcanvas.querySelector('.evf-select-rayon'); 134 if (rayonSel && rayonSel.value === '20') rayonSel.value = ''; 135 setReset(); 136 inst.pagination.page = 1; 137 applyFilters(inst); 138 return; 139 } 140 if (!navigator.geolocation) { 141 alert("La géolocalisation n'est pas disponible sur ce navigateur."); 142 return; 143 } 144 setLoading(); 145 navigator.geolocation.getCurrentPosition( 146 function (pos) { 147 inst.userLat = pos.coords.latitude; 148 inst.userLng = pos.coords.longitude; 149 setActive(); 150 if (inst.closePanel) inst.closePanel(); 151 // Rayon par défaut si aucun sélectionné dans l'offcanvas 152 var rayonSel = inst.offcanvas && inst.offcanvas.querySelector('.evf-select-rayon'); 153 if (rayonSel && !rayonSel.value) rayonSel.value = '20'; 154 inst.pagination.page = 1; 155 applyFilters(inst); 156 scrollToList(inst); 157 }, 158 function () { 159 setReset(); 160 alert("Impossible d'obtenir votre position."); 161 } 162 ); 163 }); 164 }); 165 } 166 167 // ââ Restreindre communes par département ââââââââââââââââââââââââââââââââââ 168 function initDeptCommuneFilter(inst) { 169 var offcanvas = inst.offcanvas; 170 if (!offcanvas) return; 171 172 var deptSel = offcanvas.querySelector('.evf-select-dept'); 173 var communeSel = offcanvas.querySelector('.evf-select-commune'); 174 if (!deptSel || !communeSel) return; 175 176 deptSel.addEventListener('change', function () { 177 var selectedDept = deptSel.value; 178 var communeOptions = communeSel.querySelectorAll('option'); 179 180 communeOptions.forEach(function (opt) { 181 if (!opt.value) { 182 // Option "Toutes les communes" : toujours visible 183 opt.hidden = false; 184 opt.disabled = false; 185 return; 186 } 187 var optDepts = opt.dataset.dept ? opt.dataset.dept.split(' ') : []; 188 if (selectedDept && !optDepts.includes(selectedDept)) { 189 opt.hidden = true; 190 opt.disabled = true; 191 } else { 192 opt.hidden = false; 193 opt.disabled = false; 194 } 195 }); 196 197 // Si la commune actuellement sélectionnée est devenue invisible, réinitialiser 198 if (communeSel.value && communeSel.options[communeSel.selectedIndex].hidden) { 199 communeSel.value = ''; 200 } 201 }); 202 } 203 204 // ââ Raccourcis date âââââââââââââââââââââââââââââââââââââââââââââââââââââââ 205 function initDateShortcuts(inst) { 206 var offcanvas = inst.offcanvas; 207 if (!offcanvas) return; 208 209 offcanvas.querySelectorAll('.evf-shortcut-btn').forEach(function (btn) { 210 btn.addEventListener('click', function () { 211 var isActive = btn.classList.contains('evf-shortcut-btn--active'); 212 213 offcanvas.querySelectorAll('.evf-shortcut-btn').forEach(function (b) { 214 b.classList.remove('evf-shortcut-btn--active'); 215 }); 216 217 if (!isActive) { 218 btn.classList.add('evf-shortcut-btn--active'); 219 } 220 221 var jourSel = offcanvas.querySelector('.evf-select-jour'); 222 var moisSel = offcanvas.querySelector('.evf-select-mois'); 223 if (jourSel) jourSel.value = ''; 224 if (moisSel) moisSel.value = ''; 225 }); 226 }); 227 228 ['.evf-select-jour', '.evf-select-mois'].forEach(function (sel) { 229 var el = offcanvas.querySelector(sel); 230 if (el) { 231 el.addEventListener('change', function () { 232 offcanvas.querySelectorAll('.evf-shortcut-btn').forEach(function (b) { 233 b.classList.remove('evf-shortcut-btn--active'); 234 }); 235 }); 236 } 237 }); 238 } 239 240 // ââ Tout cocher / décocher ââââââââââââââââââââââââââââââââââââââââââââââââ 241 function initToggleAll(inst) { 242 var offcanvas = inst.offcanvas; 243 if (!offcanvas) return; 244 245 offcanvas.querySelectorAll('.evf-toggle-all').forEach(function (btn) { 246 btn.addEventListener('click', function () { 247 var group = btn.dataset.group; 248 var checkboxes = offcanvas.querySelectorAll('[data-group="' + group + '"]'); 249 var allChecked = Array.prototype.every.call(checkboxes, function (cb) { return cb.checked; }); 250
251 checkboxes.forEach(function (cb) { 252 cb.checked = !allChecked; 253 }); 254 }); 255 }); 256 } 257 258 // ââ Filtres âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 259 function initFilters(inst) { 260 var container = inst.container; 261 var offcanvas = inst.offcanvas; 262 var searchEl = container.querySelector('.ev-search-text'); 263 264 if (searchEl) { 265 var debounceTimer; 266 searchEl.addEventListener('input', function () { 267 inst.pagination.page = 1; 268 clearTimeout(debounceTimer); 269 debounceTimer = setTimeout(function () { applyFilters(inst); }, 220); 270 }); 271 } 272 273 var resetBtn = offcanvas && offcanvas.querySelector('.ev-btn-reset-filters'); 274 if (resetBtn) { 275 resetBtn.addEventListener('click', function () { 276 if (searchEl) searchEl.value = ''; 277 278 if (offcanvas) { 279 offcanvas.querySelectorAll('input[type="checkbox"]').forEach(function (cb) { cb.checked = false; }); 280 offcanvas.querySelectorAll('select').forEach(function (sel) { sel.value = ''; }); 281 offcanvas.querySelectorAll('.evf-shortcut-btn').forEach(function (b) { 282 b.classList.remove('evf-shortcut-btn--active'); 283 }); 284 // Réafficher toutes les communes quand on réinitialise 285 var communeOptions = offcanvas.querySelectorAll('.evf-select-commune option'); 286 communeOptions.forEach(function (opt) { 287 opt.hidden = false; 288 opt.disabled = false; 289 }); 290 } 291 292 container.querySelectorAll('.evf-autour-de-moi').forEach(function (btn) { 293 btn.classList.remove('evf-autour-de-moi--active'); 294 btn.textContent = 'Afficher les événements autour de moi'; 295 }); 296 inst.userLat = null; 297 inst.userLng = null; 298 299 inst.pagination.page = 1; 300 applyFilters(inst); 301 }); 302 } 303 } 304 305 // ââ Pagination (prev / next) ââââââââââââââââââââââââââââââââââââââââââââââ 306 function initPagination(inst) { 307 var prevBtn = inst.container.querySelector('.ev-pag-prev'); 308 var nextBtn = inst.container.querySelector('.ev-pag-next'); 309 310 if (!prevBtn && !nextBtn) { inst.pagination.perPage = Infinity; return; } 311 312 if (prevBtn) { 313 prevBtn.addEventListener('click', function () { 314 if (inst.pagination.page > 1) { 315 inst.pagination.page--; 316 applyFilters(inst); 317 scrollToList(inst); 318 } 319 }); 320 } 321 if (nextBtn) { 322 nextBtn.addEventListener('click', function () { 323 inst.pagination.page++; 324 applyFilters(inst); 325 scrollToList(inst); 326 }); 327 } 328 329 // Redessine la pagination (delta plus ou moins large selon la largeur, 330 // voir renderPagination) quand l'écran est redimensionné, pas seulement 331 // au chargement de la page. 332 var resizeTimer; 333 window.addEventListener('resize', function () { 334 clearTimeout(resizeTimer); 335 resizeTimer = setTimeout(function () { 336 if (inst.pagination.totalPages) renderPagination(inst, inst.pagination.totalPages); 337 }, 150); 338 }); 339 } 340 341 function scrollToList(inst) { 342 var list = inst.container.querySelector('.ev-arc-events-list'); 343 if (!list) return; 344 var header = document.querySelector('#site-header'); 345 var offset = (header ? header.offsetHeight : 0) + 16; 346 var top = list.getBoundingClientRect().top + window.pageYOffset - offset; 347 window.scrollTo({ top: Math.max(0, top), behavior: 'smooth' }); 348 } 349 350 // ââ Rendu de la pagination numérotée âââââââââââââââââââââââââââââââââââââ 351 function renderPagination(inst, totalPages) { 352 var container = inst.container; 353 var paginationEl = container.querySelector('.ev-pagination'); 354 var pagesEl = container.querySelector('.ev-pag-pages'); 355 var prevBtn = container.querySelector('.ev-pag-prev'); 356 var nextBtn = container.querySelector('.ev-pag-next'); 357 358 if (!paginationEl || !pagesEl) return; 359 360 inst.pagination.totalPages = totalPages; 361 362 // Masquée aussi tant que la carte est affichée (voir initMapToggle) : la 363 // carte montre tous les événements filtrés d'un coup, la pagination n'a 364 // plus de sens dans ce mode. 365 paginationEl.hidden = inst.mapVisible || totalPages <= 1; 366 if (totalPages <= 1) return; 367 368 if (prevBtn) prevBtn.disabled = inst.pagination.page <= 1; 369 if (nextBtn) nextBtn.disabled = inst.pagination.page >= totalPages; 370 371 // Tronque la liste de pages (1 ⦠page-delta..page+delta ⦠dernière) au lieu 372 // de tout afficher : avec beaucoup de pages, ça débordait de l'écran sur 373 // petit écran. Fenêtre (delta) plus large à mesure que l'écran grandit. 374 var current = inst.pagination.page; 375 var delta = window.innerWidth < 576 ? 0 : window.innerWidth < 1200 ? 2 : 4; 376 var pages = []; 377 for (var i = 1; i <= totalPages; i++) { 378 if (i === 1 || i === totalPages || (i >= current - delta && i <= current + delta)) { 379 pages.push(i); 380 } else if (pages[pages.length - 1] !== 'â¦') { 381 pages.push('â¦'); 382 } 383 } 384 385 pagesEl.innerHTML = '';
386 pages.forEach(function (p) { 387 if (p === 'â¦') { 388 var span = document.createElement('span'); 389 span.className = 'ev-pag-ellipsis'; 390 span.textContent = 'â¦'; 391 span.setAttribute('aria-hidden', 'true'); 392 pagesEl.appendChild(span); 393 return; 394 } 395 var btn = document.createElement('button'); 396 btn.type = 'button'; 397 btn.className = 'ev-pag-btn' + (p === current ? ' ev-pag-btn--active' : ''); 398 btn.textContent = p; 399 btn.setAttribute('aria-label', 'Page ' + p); 400 if (p === current) btn.setAttribute('aria-current', 'page'); 401 btn.addEventListener('click', function () { 402 inst.pagination.page = p; 403 applyFilters(inst); 404 scrollToList(inst); 405 }); 406 pagesEl.appendChild(btn); 407 }); 408 } 409 410 // ââ Application des filtres âââââââââââââââââââââââââââââââââââââââââââââââ 411 function applyFilters(inst) { 412 var container = inst.container; 413 var offcanvas = inst.offcanvas; 414 var searchEl = container.querySelector('.ev-search-text'); 415 416 var needle = searchEl ? searchEl.value.trim().toLowerCase() : ''; 417 418 var checkedCats = offcanvas 419 ? Array.prototype.map.call( 420 offcanvas.querySelectorAll('.evf-cat-activite:checked'), 421 function (cb) { return cb.value; } 422 ) : []; 423 424 var checkedProduits = offcanvas 425 ? Array.prototype.map.call( 426 offcanvas.querySelectorAll('.evf-filter-produit:checked'), 427 function (cb) { return cb.value; } 428 ) : []; 429 430 var checkedModalites = offcanvas 431 ? Array.prototype.map.call( 432 offcanvas.querySelectorAll('.evf-filter-modalite:checked'), 433 function (cb) { return cb.value; } 434 ) : []; 435 436 var dept = offcanvas ? (offcanvas.querySelector('.evf-select-dept') || {value: ''}).value : ''; 437 var communeSelEl = offcanvas ? offcanvas.querySelector('.evf-select-commune') : null; 438 var commune = communeSelEl ? communeSelEl.value : ''; 439 var rayon = offcanvas ? parseFloat((offcanvas.querySelector('.evf-select-rayon') || {value: ''}).value) || 0 : 0; 440 441 // Origine du rayon : priorité à la géolocalisation ("autour de moi"), 442 // sinon les coordonnées de la commune sélectionnée (portées en 443 // data-lat/data-lng sur son <option>, cf. evenement-filter.php). 444 var originLat = inst.userLat; 445 var originLng = inst.userLng; 446 if (originLat === null && commune && communeSelEl) { 447 var communeOpt = communeSelEl.options[communeSelEl.selectedIndex]; 448 var optLat = communeOpt ? parseFloat(communeOpt.dataset.lat) : NaN; 449 var optLng = communeOpt ? parseFloat(communeOpt.dataset.lng) : NaN; 450 if (!isNaN(optLat) && !isNaN(optLng)) { 451 originLat = optLat; 452 originLng = optLng; 453 } 454 } 455 456 var activeShortcut = ''; 457 if (offcanvas) { 458 var activeShortcutBtn = offcanvas.querySelector('.evf-shortcut-btn--active'); 459 if (activeShortcutBtn) activeShortcut = activeShortcutBtn.dataset.shortcut || ''; 460 } 461 462 var jourFilter = offcanvas ? parseInt((offcanvas.querySelector('.evf-select-jour') || {value: ''}).value, 10) : NaN; 463 var moisFilter = offcanvas ? parseInt((offcanvas.querySelector('.evf-select-mois') || {value: ''}).value, 10) : NaN; 464 465 // 1. Collecter les cartes qui passent les filtres 466 var matchedCards = []; 467 468 container.querySelectorAll(inst.cardSel).forEach(function (card) { 469 var ok = true; 470 471 if (ok && needle) { 472 ok = (card.dataset.search || '').indexOf(needle) !== -1; 473 } 474 475 if (ok && checkedCats.length > 0) {
476 var cardCats = (card.dataset.cats || '').split(' ').filter(Boolean); 477 ok = checkedCats.some(function (c) { return cardCats.indexOf(c) !== -1; }); 478 } 479 480 if (ok && checkedProduits.length > 0) { 481 var cardProduits = (card.dataset.produits || '').split(' ').filter(Boolean); 482 ok = checkedProduits.some(function (p) { return cardProduits.indexOf(p) !== -1; }); 483 } 484 485 if (ok && checkedModalites.length > 0) { 486 var cardModalites = (card.dataset.modalites || '').split(' ').filter(Boolean); 487 ok = checkedModalites.some(function (m) { return cardModalites.indexOf(m) !== -1; }); 488 } 489 490 if (ok && dept) { 491 ok = (card.dataset.dept || '') === dept; 492 } 493 494 // Filtre commune exacte : ignoré si un rayon est actif, sinon la 495 // commune restreindrait déjà aux seuls événements de cette commune 496 // et le rayon (censé chercher autour) n'aurait plus aucun effet. 497 if (ok && commune && !(rayon > 0)) { 498 ok = (card.dataset.commune || '') === commune; 499 } 500 501 if (ok && rayon > 0 && originLat !== null && originLng !== null) { 502 var lat = parseFloat(card.dataset.lat); 503 var lng = parseFloat(card.dataset.lng); 504 if (!isNaN(lat) && !isNaN(lng)) { 505 ok = haversine(originLat, originLng, lat, lng) <= rayon; 506 } else { 507 ok = false; 508 } 509 } 510 511 if (ok && activeShortcut) { 512 var ymd = card.dataset.dateDebut || ''; 513 if (activeShortcut === 'weekend') { 514 var wr = getWeekendRange(); 515 ok = ymd >= wr[0] && ymd <= wr[1]; 516 } else if (activeShortcut === 'week') { 517 var wkr = getWeekRange(); 518 ok = ymd >= wkr[0] && ymd <= wkr[1]; 519 } 520 } 521 522 if (ok && !isNaN(jourFilter) && !activeShortcut) { 523 var ymdJ = card.dataset.dateDebut || ''; 524 if (ymdJ) { 525 var dJ = new Date(ymdJ.substring(0,4) + '-' + ymdJ.substring(4,6) + '-' + ymdJ.substring(6,8)); 526 ok = dJ.getDate() === jourFilter; 527 } else { 528 ok = false; 529 } 530 } 531 532 if (ok && !isNaN(moisFilter) && !activeShortcut) { 533 var ymdM = card.dataset.dateDebut || ''; 534 if (ymdM) { 535 var dM = new Date(ymdM.substring(0,4) + '-' + ymdM.substring(4,6) + '-' + ymdM.substring(6,8)); 536 ok = (dM.getMonth() + 1) === moisFilter; 537 } else { 538 ok = false; 539 } 540 } 541 542 card.hidden = true; 543 if (card.parentElement) { 544 card.parentElement.hidden = true; 545 card.parentElement.classList.remove('col-lg-12'); 546 } 547 // Indépendant de la pagination (voir 2. plus bas) : sert de source de 548 // vérité pour la carte, qui doit montrer tous les événements filtrés, 549 // pas seulement ceux de la page courante. 550 card.dataset.filterMatch = ok ? '1' : '0'; 551 if (ok) matchedCards.push(card); 552 }); 553 554 // 2. Paginer : afficher uniquement la tranche de la page courante 555 var perPage = inst.pagination.perPage; 556 var totalPages = Math.max(1, Math.ceil(matchedCards.length / perPage)); 557 558 if (inst.pagination.page > totalPages) inst.pagination.page = totalPages; 559 560 var startIdx = (inst.pagination.page - 1) * perPage; 561 matchedCards.forEach(function (card, i) { 562 card.hidden = i < startIdx || i >= startIdx + perPage; 563 if (card.parentElement) card.parentElement.hidden = card.hidden; 564 }); 565 566 // Dernière card seule sur sa rangée â pleine largeur 567 var pageCount = Math.min(perPage, matchedCards.length - startIdx); 568 if (pageCount % 2 !== 0) { 569 var lastCard = matchedCards[startIdx + pageCount - 1]; 570 if (lastCard && lastCard.parentElement) lastCard.parentElement.classList.add('col-lg-12'); 571 } 572 573 // 3. Compteur 574 var countEl = container.querySelector('.ev-results-count'); 575 if (countEl) { 576 var count = matchedCards.length; 577 countEl.textContent = count + ' événement' + (count > 1 ? 's' : ''); 578 } 579 580 // 4. Rendu de la pagination 581 renderPagination(inst, totalPages); 582 583 // 5. Sync marqueurs carte si elle est ouverte 584 syncMapMarkers(inst); 585 } 586 587 // ââ Carte interactive (toggle liste â carte) ââââââââââââââââââââââââââââââ 588 function initMapToggle(inst) { 589 var btn = inst.container.querySelector('.ev-btn-carte'); 590 var mapContainer = inst.container.querySelector('.ev-arc-map-container'); 591 592 if (!btn || !mapContainer) return; 593 594 inst.mapBtn = btn; 595 inst.mapContainerEl = mapContainer; 596 597 btn.addEventListener('click', function () { 598 setMapVisible(inst, !inst.mapVisible); 599 }); 600 601 // Carte affichée par défaut au chargement. 602 setMapVisible(inst, true); 603 } 604 605 // ââ Bascule effective liste â carte (appelée au clic et à l'init) âââââââââ 606 function setMapVisible(inst, mapVisible) { 607 var btn = inst.mapBtn; 608 var mapContainer = inst.mapContainerEl; 609 var listEl = inst.container.querySelector('.ev-arc-events-list'); 610 var paginationEl = inst.container.querySelector('.ev-pagination'); 611 612 inst.mapVisible = mapVisible; 613 614 mapContainer.hidden = !mapVisible; 615 if (listEl) listEl.hidden = mapVisible; 616 // La pagination n'a pas de sens en mode carte (qui montre tous les 617 // événements filtrés d'un coup, voir syncMapMarkers) : masquée tant 618 // que la carte est affichée, restaurée selon le nombre de pages au 619 // retour en liste. renderPagination() respecte aussi inst.mapVisible 620 // pour ne pas la réafficher si les filtres changent carte ouverte. 621 if (paginationEl) { 622 paginationEl.hidden = mapVisible || inst.pagination.totalPages <= 1; 623 } 624 625 btn.textContent = mapVisible ? 'Afficher la liste' : 'Afficher la carte'; 626 627 if (!mapVisible) return; 628 629 if (inst.leafletMap) { 630 inst.leafletMap.invalidateSize(); 631 syncMapMarkers(inst); 632 return; 633 } 634 635 buildMap(inst, mapContainer); 636 } 637 638 // ââ Construction initiale de la carte Leaflet (une seule fois) ââââââââââââ 639 function buildMap(inst, mapContainer) { 640 if (typeof L === 'undefined' || typeof BioetlocalMap === 'undefined') return; 641 642 var mapEl = document.createElement('div'); 643 mapEl.className = 'ev-arc-map-inner'; 644 mapContainer.appendChild(mapEl); 645 646 var seeds = []; 647 inst.container.querySelectorAll(inst.cardSel).forEach(function (card) { 648 var lat = parseFloat(card.dataset.lat); 649 var lng = parseFloat(card.dataset.lng); 650 if (!isNaN(lat) && !isNaN(lng) && lat !== 0 && lng !== 0) { 651 seeds.push({ 652 lat: lat, 653 lng: lng, 654 title: card.getAttribute('aria-label') || '', 655 url: card.getAttribute('href') || '#', 656 card: card, 657 }); 658 } 659 }); 660 661 if (!seeds.length) { 662 mapContainer.innerHTML = '<p class="ev-arc-empty" style="padding:1rem">Aucun événement géolocalisé.</p>'; 663 return; 664 } 665 666 var lats = seeds.map(function (m) { return m.lat; }); 667 var lngs = seeds.map(function (m) { return m.lng; }); 668 var centerLat = (Math.min.apply(null, lats) + Math.max.apply(null, lats)) / 2; 669 var centerLng = (Math.min.apply(null, lngs) + Math.max.apply(null, lngs)) / 2; 670 671 // Sur petit écran, désactiver le pan tactile au départ (le pincement 672 // à 2 doigts pour zoomer reste actif) et l'activer au premier tap : 673 // sinon le scroll de la page se fait happer par le glisser-carte 674 // dès que le doigt passe dessus (peu de marge autour sur mobile). 675 var smallScreen = window.matchMedia('(max-width: 767px)').matches; 676 inst.leafletMap = L.map(mapEl, { dragging: !smallScreen }
676).setView([centerLat, centerLng], 9); 677 BioetlocalMap.addOsmTiles(inst.leafletMap); 678 if (smallScreen) { 679 inst.leafletMap.once('click', function () { inst.leafletMap.dragging.enable(); }); 680 } 681 682 inst.markerCluster = typeof L.markerClusterGroup === 'function' 683 ? L.markerClusterGroup() 684 : L.layerGroup(); // dégradation silencieuse si le plugin ne charge pas 685 686 seeds.forEach(function (m) { 687 var popupHtml = '<a href="' + m.url + '" class="ev-arc-card ev-popup-card">' + m.card.innerHTML + '</a>'; 688 var markerOpts = inst.mapIcon ? { icon: inst.mapIcon } : {}; 689 var lm = L.marker([m.lat, m.lng], markerOpts) 690 .bindPopup(popupHtml, { maxWidth: 280, minWidth: 260 }); 691 inst.markerCluster.addLayer(lm); 692 inst.leafletMarkers.push({ marker: lm, card: m.card }); 693 }); 694 695 inst.leafletMap.addLayer(inst.markerCluster); 696 697 if (seeds.length > 1) { 698 var bounds = L.latLngBounds(seeds.map(function (m) { return [m.lat, m.lng]; })); 699 inst.leafletMap.fitBounds(bounds, { padding: [40, 40] }); 700 } 701 702 syncMapMarkers(inst); 703 } 704 705 // ââ Sync marqueurs carte avec état filtré ââââââââââââââââââââââââââââââââââ 706 function syncMapMarkers(inst) { 707 if (!inst.leafletMarkers.length || !inst.leafletMap) return; 708 var mapContainer = inst.container.querySelector('.ev-arc-map-container'); 709 if (!mapContainer || mapContainer.hidden) return; 710 711 // Basé sur dataset.filterMatch (indépendant de la pagination) : montre 712 // tous les événements qui correspondent aux filtres actifs d'un coup, 713 // pas seulement ceux de la page de liste actuellement affichée. 714 // Ajout/retrait réel du groupe de clustering (pas juste une opacité) : 715 // un marqueur simplement rendu transparent resterait compté dans le 716 // chiffre du cluster, ce qui fausserait le regroupement affiché. 717 var visibleLatLngs = []; 718 inst.leafletMarkers.forEach(function (lm) { 719 var show = lm.card.dataset.filterMatch === '1'; 720 var inLayer = inst.markerCluster && inst.markerCluster.hasLayer(lm.marker); 721 if (inst.markerCluster) { 722 if (show && !inLayer) inst.markerCluster.addLayer(lm.marker); 723 else if (!show && inLayer) inst.markerCluster.removeLayer(lm.marker); 724 } 725 if (show) visibleLatLngs.push([lm.marker.getLatLng().lat, lm.marker.getLatLng().lng]); 726 }); 727 728 if (visibleLatLngs.length > 1) { 729 inst.leafletMap.fitBounds(L.latLngBounds(visibleLatLngs), { padding: [40, 40] }); 730 } else if (visibleLatLngs.length === 1) { 731 inst.leafletMap.setView(visibleLatLngs[0], 13); 732 } 733 } 734 735 // ââ Helpers âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 736 function haversine(lat1, lng1, lat2, lng2) { 737 var R = 6371; 738 var dLat = (lat2 - lat1) * Math.PI / 180; 739 var dLng = (lng2 - lng1) * Math.PI / 180; 740 var a = Math.sin(dLat/2) * Math.sin(dLat/2) 741 + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) 742 * Math.sin(dLng/2) * Math.sin(dLng/2); 743 return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 744 } 745 746 function ymdFromDate(d) { 747 var y = d.getFullYear(); 748 var m = String(d.getMonth() + 1).padStart(2, '0'); 749 var day = String(d.getDate()).padStart(2, '0'); 750 return '' + y + m + day; 751 } 752 753 function getWeekendRange() { 754 var now = new Date(); 755 var day = now.getDay(); 756 var diffToSat = (6 - day + 7) % 7 || 7; 757 var sat = new Date(now); 758 sat.setDate(now.getDate() + diffToSat); 759 var sun = new Date(sat); 760 sun.setDate(sat.getDate() + 1); 761 return [ymdFromDate(sat), ymdFromDate(sun)]; 762 } 763 764 function getWeekRange() { 765 var now = new Date(); 766 var day = now.getDay(); 767 var diffToMon = (day === 0 ? -6 : 1 - day); 768 var mon = new Date(now); 769 mon.setDate(now.getDate() + diffToMon); 770 var sun = new Date(mon); 771 sun.setDate(mon.getDate() + 6); 772 return [ymdFromDate(mon), ymdFromDate(sun)]; 773 } 774 775})();
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.