1var OtqMap = function ($, Drupal, Bootstrap) { 2 let self = {}; 3 let mapScriptLoaded = false; 4 5 self.mapSlider = null; 6 self.$cartographyMaps = []; 7 8 let $overlay, $mapMarkerAjax; 9 let stickyMapInitialized = false; 10 let zonesCleared = false; 11 let pageClickDelay; 12 13 /** ----------------------------- 14 * Routes 15 --------------------------------- */ 16 17 /** 18 * Init default map behavior, only btn behavior is tracked 19 */ 20 self.init = function (e) { 21 $overlay = $('.map-overlay'); 22 $mapMarkerAjax = $('#map-marker-ajax'); 23 let $pagination = $overlay.find('.pagination'); 24 25 CossetteApp.$body.on('click', '.btn-map', onMapBtnClick); 26 27 $overlay.on('click', '.btn-map-controls-resize', onMapResize); 28 $overlay.on('click', '.btn-map-controls-geolocate', onLocationClick); 29 $overlay.on('click', '.marker-trigger', onMarkerTriggerClick); 30 $overlay.on('mouseenter', '.marker-trigger', onMarkerEnter); 31 $overlay.on('mouseleave', '.marker-trigger', onMarkerLeave); 32 $overlay.on('click', '.explore-nav button', onExploreNavClick); 33 $overlay.on('click', '.btn-close-neighbourhood', onCloseNeighbourhood); 34 $pagination.on('click', 'button', onPaginationClick); 35 $mapMarkerAjax.on('click', '.btn-close', onLeaveInfowindow); 36 37 self.clearMarkers = false; 38 }; 39 40 /** 41 * Init sticky map behavior for list and itinerary pages 42 */ 43 self.initStickyMap = function () { 44 let $container = $('.map-sticky'); 45 self.$listContentWrapper = $('#list-content-wrapper'); 46 47 if ($container.length) { 48 CossetteApp.$stickyOffset = $('#map-sticky-content'); 49 50 if (CossetteApp.$stickyOffset.length) { 51 CossetteApp.$stickyMap = $container; 52 CossetteApp.$stickySteps = $('.map-sticky-item'); 53 CossetteApp.$stickyStepsWithMarker = $('.map-sticky-item[data-has-marker="1"]'); 54 55 self.handleStickyHeightChange(); 56 } 57 58 // mutation observer on article height 59 const stickyContentElement = document.getElementById('map-sticky-content'); 60 if (stickyContentElement) { 61 const observer = new ResizeObserver(() => { 62 self.handleStickyHeightChange(); 63 }); 64 observer.observe(stickyContentElement); 65 } 66 } 67 68 if (document.querySelector('.remote-iframe')) { 69 initStickyGmap($container); 70 stickyMapInitialized = true; 71 self.enableListMapMode(0); 72 } else { 73 if (Modernizr.mq('(min-width: 900px)')) { 74 // Use intersection observer to check if map is in viewport before enabling 75 if ('IntersectionObserver' in window) { 76 const lazyMapObserver = new IntersectionObserver(function (entries, observer) { 77 entries.forEach(function (entry) { 78 if (entry.isIntersecting) { 79 initStickyGmap($container); 80 stickyMapInitialized = true; 81 lazyMapObserver.unobserve(entry.target); 82 } 83 }); 84 }); 85 86 lazyMapObserver.observe($container.get(0)); 87 } else { 88 initStickyGmap($container); 89 stickyMapInitialized = true; 90 } 91 } 92 } 93 }; 94 95 /** ----------------------------- 96 * Public 97 --------------------------------- */ 98 99 /** 100 * Triggered when someones clicks on a map view button in list/itinerary 101 */ 102 self.enableListMapMode = function (startIndex) { 103 if (!stickyMapInitialized) { 104 initStickyGmap($('.map-sticky')); 105 stickyMapInitialized = true; 106 } 107 108 // Ensure $listContentWrapper is initialized 109 if (!self.$listContentWrapper || !self.$listContentWrapper.length) { 110 self.$listContentWrapper = $('#list-content-wrapper'); 111 } 112 113 // Ensure $stickyOffset is initialized 114 if (typeof CossetteApp.$stickyOffset === 'undefined' || !CossetteApp.$stickyOffset || !CossetteApp.$stickyOffset.length) { 115 CossetteApp.$stickyOffset = $('#map-sticky-content'); 116 } 117 118 // Check if the element exists before using it 119 if (!CossetteApp.$stickyOffset || !CossetteApp.$stickyOffset.length) { 120 console.error('[Map.js] ERROR: #map-sticky-content element not found in DOM'); 121 return; 122 } 123 124 // Check if $listContentWrapper exists 125 if (!self.$listContentWrapper || !self.$listContentWrapper.length) { 126 console.error('[Map.js] ERROR: #list-content-wrapper element not found in DOM'); 127 return; 128 } 129 130 // Save height to avoid page movement 131 self.$listContentWrapper.height(CossetteApp.$stickyOffset.height()); 132 133 let _Slider = CossetteRouter.getClassReference('OtqSlider'); 134 _Slider.destroyAll(); 135 136 CossetteApp.$html.addClass('has-overlay').removeClass('main-nav-sticky main-nav-showing'); 137 self.$listContentWrapper.addClass('map-visible'); 138 139 // Bind to slider object 140 let gMapInterval = setInterval(function () { 141 if (typeof CossetteApp.maps[0] !== 'undefined' && CossetteApp.maps[0].loaded === true) { 142 clearInterval(gMapInterval); 143 // Ensure $stickyOffset is still valid before using it 144 if (CossetteApp.$stickyOffset && CossetteApp.$stickyOffset.length) { 145 self.mapSlider = _Slider.initMapSlider(true, CossetteApp.$stickyOffset.get(0), startIndex); 146 } 147 } 148 }, 100); 149 }; 150 151 /** 152 * Triggered when someones clicks on close button of map view in list/itinerary or when reach 900+ width 153 */
154 self.disableListMapMode = function () { 155 let _Slider = CossetteRouter.getClassReference('OtqSlider'); 156 157 CossetteApp.$html.removeClass('has-overlay'); 158 self.$listContentWrapper.removeClass('map-visible').css('height', 'auto'); 159 160 _Slider.initMapSlider(false, self.mapSlider); 161 _Slider.init(); 162 163 // Reset the stick offset to new markup 164 self.mapSlider = null; 165 CossetteApp.$stickyOffset = $('#map-sticky-content'); 166 CossetteApp.$stickySteps = $('.map-sticky-item'); 167 }; 168 169 /** 170 * Sticky map behavior + active step 171 * @param currentScroll 172 */ 173 self.adjustMapStickyness = function (currentScroll) { 174 let elementOffsetTop = CossetteApp.stickyOffsetTop; 175 let elementOffsetBottom = CossetteApp.stickyOffsetBottom; 176 let elementOffsetEnd = CossetteApp.stickyOffsetEnd; 177 let elementOffset = self.$listContentWrapper; 178 let elementSteps = CossetteApp.$stickySteps; 179 180 if (currentScroll >= elementOffsetTop) { 181 if (currentScroll <= elementOffsetBottom) { 182 if (!CossetteApp.$stickyMap.hasClass('is-sticky')) { 183 CossetteApp.$stickyMap.css('transform', 'translate3d(0,0,0)'); 184 CossetteApp.$stickyMap.addClass('is-sticky').trigger('map-sticky-change'); 185 } 186 187 // Don't trigger this when scrollTo has been called 188 if (!CossetteApp.$body.hasClass('is-scrolling')) { 189 setStickyStepState(currentScroll); 190 } 191 } else { 192 if (CossetteApp.$stickyMap.hasClass('is-sticky')) { 193 let posY = elementOffset.outerHeight() - CossetteApp.$window.height(); 194 posY += 'px'; 195 CossetteApp.$stickyMap.css('transform', 'translate3d(0,' + posY + ',0)'); 196 elementSteps.removeClass('is-active'); 197 CossetteApp.$stickyMap.removeClass('is-sticky').trigger('map-sticky-change'); 198 } 199 200 if (currentScroll <= elementOffsetEnd) { 201 setStickyStepState(currentScroll); 202 } else { 203 setLastStickyStepState(); 204 } 205 } 206 } else { 207 if (CossetteApp.$stickyMap.hasClass('is-sticky')) { 208 elementSteps.removeClass('is-active'); 209 CossetteApp.$stickyMap.removeClass('is-sticky').trigger('map-sticky-change'); 210 } 211 } 212 }; 213 214 /** 215 * Map markers only exist for geolocated list items; convert Splide slide index â markers[] index. 216 */ 217 self.slideIndexToMarkerIndex = function (slideIndex) { 218 if (slideIndex < 0 || typeof slideIndex !== 'number') { 219 return -1; 220 } 221 const slides = document.querySelectorAll('#map-sticky-content .splide__list > .splide__slide:not(.splide__slide--clone)'); 222 if (!slides.length || slideIndex >= slides.length) { 223 return -1; 224 } 225 let count = -1; 226 for (let i = 0; i <= slideIndex; i++) { 227 if (slides[i].getAttribute('data-has-marker') === '1') { 228 count++; 229 } 230 } 231 return count; 232 }; 233 234 /** 235 * Reverse: markers[] index (from map click) â Splide slide index. 236 */ 237 self.markerIndexToSlideIndex = function (markerIndex) { 238 if (markerIndex < 0 || typeof markerIndex !== 'number') { 239 return 0; 240 } 241 const slides = document.querySelectorAll('#map-sticky-content .splide__list > .splide__slide:not(.splide__slide--clone)'); 242 let seen = -1; 243 for (let i = 0; i < slides.length; i++) { 244 if (slides[i].getAttribute('data-has-marker') === '1') { 245 seen++; 246 if (seen === markerIndex) { 247 return i; 248 } 249 } 250 } 251 return 0; 252 }; 253 254 self.updateActiveMapMarkerFromSlider = function (slideIndex) { 255 if (!CossetteApp.maps[0]) { 256 return; 257 } 258 const markerIndex = self.slideIndexToMarkerIndex(slideIndex); 259 if (markerIndex < 0) { 260 return; 261 } 262 let offset = { y: CossetteApp.$stickyMap.height() / 6, x: 0 }; 263 264 CossetteApp.maps[0].highlightCurrentMarker(markerIndex); 265 266 if (document.querySelector('article.remote-iframe')) { 267 CossetteApp.maps[0].canvas.setZoom(CossetteApp.maps[0].canvas.getZoom() + 2); 268 } 269 270 CossetteApp.maps[0].handlePan(markerIndex, offset); 271 }; 272 273 /** 274 * When viewport width changes (from Router), recalculate values 275 */ 276 self.handleStickyHeightChange = function () { 277 CossetteApp.stickyOffsetTop = self.$listContentWrapper.offset().top; 278 CossetteApp.stickyOffsetBottom = CossetteApp.stickyOffsetTop + self.$listContentWrapper.outerHeight() - CossetteApp.$window.height(); 279 CossetteApp.stickyOffsetEnd = CossetteApp.stickyOffsetBottom + CossetteApp.$window.height(); 280 281 CossetteApp.stickyYAdjust = 0; 282 const $listIntro = self.$listContentWrapper.find('.list-intro'); 283 const $introWrapper = self.$listContentWrapper.find('.intro-wrapper'); 284 285 if ($listIntro.length) { 286 CossetteApp.stickyYAdjust += $listIntro.height(); 287 } 288 289 if ($introWrapper.length) { 290 CossetteApp.stickyYAdjust += $introWrapper.height(); 291 } 292 }; 293 294 /** 295 * Set offset from context 296 * @param {int} markerNid : current marker's node ID to select corresponding panel 297 */ 298 299 self.setMapOffset = function (markerNid) { 300 let offset = { y: 0, x: 0 }; 301 let $overlayContent = $('.map-overlay .content-container'); 302 let mapViewMode = self.checkMapViewMode(); 303 304 if (markerNid) { 305 let $markerPanel = $(`.map-marker[data-nid='${markerNid}']`); 306 if (Modernizr.mq('(max-width: 767px)')) { 307 offset = { 308 y: parseInt($markerPanel.height() / 2), 309 x: 0 310 }; 311 } else { 312 offset = { 313 y: -60, 314 x: $overlayContent.width() / 2 315 }; 316 } 317 } else { 318 if (Modernizr.mq('(max-width: 767px)')) { 319 if (mapViewMode === 'normal') { 320 offset = { 321 y: 130, 322 x: 0 323 }; 324 } 325 } else { 326 if (mapViewMode === 'normal' && CossetteApp.$html.hasClass('has-map-overlay')) { 327 offset = { 328 y: 0, 329 x: $overlayContent.width() / 2 330 }; 331 } 332 } 333 } 334 335 return offset; 336 }; 337 338 /** 339 * Convert layout offset to padding for fitbounds 340 * @param offset 341 * @returns {{left: number, bottom: number}} 342 */ 343 self.convertToPadding = function (offset) { 344 let padding = { left: 30, bottom: 30 }; 345 if (offset.x !== 0) { 346 padding.left += offset.x * 2; 347 } 348 if (offset.y !== 0) { 349 padding.bottom += offset.y * 2; 350 } 351 352 return padding; 353 }; 354 355 /** 356 * Check for map viewing mode (full or with aside) 357 */ 358 359 self.checkMapViewMode = function () { 360 let viewMode = 'normal'; 361 if (CossetteApp.$html.hasClass('map-overlay-full')) { 362 viewMode = 'full'; 363 } else if (CossetteApp.$html.hasClass('map-overlay-none')) { 364 viewMode = 'none'; 365 } 366 return viewMode; 367 }; 368 369 /** 370 * Set map view mode by changing classes on HTML 371 * @param mode (set viewing mode) 372 * @param remove (used to remove a defined class) 373 */ 374 375 self.setMapViewMode = function (mode = 'normal', remove = false) { 376 if (remove) { 377 CossetteApp.$html.removeClass(`map-overlay-${mode}`); 378 } else { 379 if (mode !== 'hidden') { 380 CossetteApp.$html.removeClass('map-overlay-full map-overlay-none'); 381 } 382 if (mode !== 'normal') { 383 CossetteApp.$html.addClass(`map-overlay-${mode}`); 384 } 385 } 386 }; 387 388 /** 389 * Parse marker markup to build google map markers 390 * @param elements 391 * @returns {Array} 392 */ 393 self.parseMarkerData = function (elements) { 394 let data = []; 395 396 $.each(elements, function () {
397 let $this = $(this); 398 399 if ( 400 typeof $this.data('lat') == 'undefined' || 401 typeof $this.data('lng') == 'undefined' || 402 $this.data('lat') === '' || 403 $this.data('lng') === '' 404 ) { 405 return; 406 } 407 408 let markerData = { 409 coords: { lat: parseFloat($this.data('lat').toFixed(7)), lng: parseFloat($this.data('lng').toFixed(7)) }, 410 autoClicked: typeof $this.data('clicked') !== 'undefined' 411 }; 412 413 if (typeof $this.data('icon') !== 'undefined') { 414 markerData.icon = { 415 url: $this.data('icon'), 416 size: { 417 width: typeof $this.data('icon-width') !== 'undefined' ? parseInt($this.data('icon-width')) : 48, 418 height: typeof $this.data('icon-height') !== 'undefined' ? parseInt($this.data('icon-height')) : 53 419 } 420 }; 421 422 if (document.querySelector('article.remote-iframe') && window.innerWidth >= 2160) { 423 markerData.icon.size.width *= 2.5; 424 markerData.icon.size.height *= 2.5; 425 } 426 // For terminal, use larger marker size 427 } else if (document.querySelector('article.remote-iframe') && window.innerWidth >= 2160) { 428 markerData.customWidth = 66; 429 markerData.customHeight = 72; 430 } 431 432 // Add nodeId to marker data 433 if (typeof $this.data('nid') !== 'undefined') { 434 markerData.nid = $this.data('nid'); 435 } 436 437 // Add nodeId to marker data 438 if (typeof $this.data('ajax') !== 'undefined') { 439 markerData.fetchAjax = parseInt($this.data('ajax')); 440 } 441 442 if (typeof $this.data('label') !== 'undefined') { 443 markerData.dayLabel = $this.data('label'); 444 } 445 446 // Custom zoom when highlighted 447 if (typeof $this.data('zoom') !== 'undefined') { 448 markerData.zoom = parseInt($this.data('zoom')); 449 } 450 451 // Check if not empty to push html inside infowindow 452 let infoWindowHtml = $this.html(); 453 if (infoWindowHtml && infoWindowHtml.toString().trim()) { 454 markerData.infoWindow = infoWindowHtml.toString(); 455 } 456 457 data.push(markerData); 458 }); 459 460 return data; 461 }; 462 463 self.setNeighborhood = function (neighbourhood) { 464 const $containers = document.querySelectorAll('.map-overlay'); 465 466 $containers.forEach(function ($container) { 467 $container.querySelectorAll('.marker-trigger').forEach(function ($marker) { 468 $marker.classList.remove('active'); 469 }); 470 471 if (neighbourhood) { 472 const $neighbourhood = $container.querySelector(`article[data-neighbourhood="${neighbourhood}"]`); 473 if ($neighbourhood) { 474 $neighbourhood.parentElement.classList.add('active'); 475 } 476 } 477 }); 478 }; 479 480 /** ------------------------------ 481 * Events 482 --------------------------------- */ 483 484 /** 485 * Map view btn catchall, different behavior based on action 486 * @param ev 487 */ 488 var onMapBtnClick = function ($button) { 489 let $this = $(this).hasClass('btn-map') ? $(this) : $button; 490 let action = $this[0].hasAttribute('data-action') ? $this.data('action') : 'default'; 491 let zones = $this[0].hasAttribute('data-zones') ? $this.data('zones') : null; 492 493 let mapViewMode = self.checkMapViewMode(); 494 let offset = self.setMapOffset(); 495 let $listItem = $this.closest('.list-item'); 496 let startSlideIndex = 0; 497 if ($listItem.length) { 498 startSlideIndex = $listItem.parent().children('.splide__slide').index($listItem); 499 if (startSlideIndex < 0) { 500 startSlideIndex = 0; 501 } 502 } 503 504 let neighbourhood = $this.closest('.list-item').data('neighbourhood'); 505 let zoneBounds = true; 506 self.setNeighborhood(neighbourhood); 507 508 switch (action) { 509 case 'enable-list-map': 510 self.enableListMapMode(startSlideIndex); 511 break; 512 case 'disable-list-map': 513 self.disableListMapMode(); 514 break; 515 default: 516 let $parent = $this.parent(); 517 518 CossetteApp.$html.addClass('has-map-overlay').removeClass('main-nav-sticky main-nav-showing'); 519 $parent.addClass('has-map-overlay'); 520 521 let mapId = parseInt($this.data('map-id')); 522 self.mapId = mapId; 523 524 if (typeof mapId === 'undefined' || typeof self.$cartographyMaps[mapId] === 'undefined') { 525 let $containers = $parent.find('.map-container'); 526 527 if ($containers.length) { 528 loadGoogle().then(function () { 529 let options = { 530 zoom: 15, 531 maxZoom: 18, 532 minZoom: 3, 533 fitToMarker: true, 534 mapTypeControl: false, 535 fullscreenControl: false, 536 gestureHandling: 'greedy', 537 zoomControlOptions: { 538 position: 'LEFT_TOP' 539 } 540 }; 541 542 $.each($containers, function (index, value) { 543 let $container = $(this); 544 let $mapSelector = $container.find('.map'); 545 let $parent = $this.parent(); 546 let $relatedMembers = $container.parents('.related-members'); 547 let $markerSelector = $parent.find('.map-marker'); 548 let markers = self.parseMarkerData($markerSelector); 549 let must_see_markers = self.parseMarkerData($parent.find('.map-marker-must-see')); 550 zoneBounds = $container.data('behavior') === 'homepage' ? false : true; 551 552 if ($relatedMembers.length) { 553 copyFilters($relatedMembers); 554 } 555 556 if (must_see_markers.length) { 557 options.must_see_markers = must_see_markers; 558 } 559 560 if ($container.get(0).hasAttribute('data-zoom')) { 561 options.initial_zoom = $container.data('zoom'); 562 } 563 564 options.$container = $container; 565 let map = new GMap($mapSelector.get(0), options, markers); 566 567 // Charger les zones depuis le bouton et les stocker dans la carte 568 if ($this.get(0).hasAttribute('data-zones')) { 569 let zones = $this.data('zones'); 570 if (zones && zones.length > 0) { 571 console.log('Loading zones for map:', zones); 572 map.zones = zones; 573 } 574 } 575 576 self.$cartographyMaps.push(map); 577 $this.data('map-id', self.$cartographyMaps.length - 1); 578 579 const $exploreNavFirstItem = document.querySelector('.explore-nav button[data-index="0"]'); 580 581 if (typeof $container.data('behavior') !== 'undefined') { 582 if ($container.data('behavior') === 'bounds') { 583 options.idleCallback = OtqSearch.searchBounds; 584 } else if ($container.data('behavior') === 'nearby') { 585 options.initOnceCallback = OtqSearch.searchNearby; 586 } else if ($container.data('behavior') === 'prefiltered') { 587 options.initOnceCallback = OtqSearch.searchPreFilteredAuto; 588 } else if ($container.data('behavior') === 'homepage') { 589 setTimeout(function () { 590 $exploreNavFirstItem.click(); 591 }, 700); 592 } 593 } 594 595 setTimeout(function () { 596 if (mapViewMode === 'normal') { 597 if ($container.data('behavior') !== 'homepage') { 598 self.$cartographyMaps[self.$cartographyMaps.length - 1].handlePan(0, offset); 599 } 600 601 if (zones) {
602 zones.forEach(function (zone) { 603 self.$cartographyMaps[self.$cartographyMaps.length - 1].drawZone(zone, zoneBounds, false, $container.data('behavior')); 604 }); 605 } 606 } 607 }, 1000); 608 }); 609 }); 610 } 611 } else { 612 if (self.clearMarkers) { 613 self.$cartographyMaps[mapId].clearMarkers(true, true); 614 self.clearMarkers = false; 615 } 616 617 let $container = self.$cartographyMaps[mapId].canvas.$container; 618 619 let $relatedMembers = $container.parents('.related-members'); 620 if ($relatedMembers.length) { 621 copyFilters($relatedMembers); 622 OtqSearch.searchPreFilteredAuto(self.$cartographyMaps[mapId]); 623 } 624 625 if (mapViewMode === 'normal') { 626 self.$cartographyMaps[mapId].handlePan(0, offset); 627 } 628 if (zones) { 629 zones.forEach(function (zone) { 630 self.$cartographyMaps[mapId].drawZone(zone, zoneBounds); 631 }); 632 } 633 } 634 635 break; 636 } 637 }; 638 639 /** 640 * When a sticky marker is clicked, scroll to list item 641 * @param markerIndex 642 */ 643 var onStickyMarkerClick = function (markerIndex) { 644 if (CossetteApp.$html.hasClass('has-overlay')) { 645 const slideIndex = self.markerIndexToSlideIndex(markerIndex); 646 self.mapSlider.go(slideIndex); 647 } else { 648 let $listItem = $(CossetteApp.$stickyStepsWithMarker[markerIndex]); 649 if ($listItem.length) { 650 CossetteApp.scrollTo($listItem); 651 } 652 } 653 }; 654 655 /** 656 * Resize map overlay on resize click 657 */ 658 659 let onMapResize = function (ev) { 660 ev.preventDefault(); 661 662 if (CossetteApp.$html.hasClass('map-overlay-full')) { 663 CossetteApp.$html.addClass('map-overlay-return').removeClass('map-overlay-full'); 664 setTimeout(function () { 665 CossetteApp.$html.removeClass('map-overlay-return'); 666 }, 650); 667 } else { 668 CossetteApp.$html.addClass('map-overlay-full'); 669 } 670 }; 671 672 let onLocationClick = function (ev) { 673 ev.preventDefault(); 674 675 let $this = $(this),
676 $overlay = $this.parents('.map-overlay'), 677 mapId = $overlay.siblings('.btn-map').data('map-id'); 678 679 function success(position) { 680 const latitude = position.coords.latitude; 681 const longitude = position.coords.longitude; 682 683 if (typeof self.$cartographyMaps[mapId] !== 'undefined') { 684 self.$cartographyMaps[mapId].canvas.setCenter(new google.maps.LatLng(latitude, longitude)); 685 } 686 } 687 688 if (navigator.geolocation) { 689 navigator.geolocation.getCurrentPosition(success, () => {}); 690 } 691 }; 692 693 /** 694 * On marker trigger click 695 */ 696 var onMarkerTriggerClick = function (e) { 697 if (!e.target.closest('.marker-link')) { 698 e.preventDefault(); 699 700 let $this = $(this); 701 let mapId = parseInt($this.parents('.map-overlay').siblings('.btn-map').data('map-id')); 702 let nid = $this.data('nid'); 703 704 if (typeof self.$cartographyMaps[mapId] !== 'undefined') { 705 if ($this.hasClass('marker-latlng') && $this.data('lat') && $this.data('lng')) { 706 self.$cartographyMaps[mapId].zoomOnLatLng($this.data('lat'), $this.data('lng')); 707 } else if ($this.hasClass('marker-zone')) { 708 self.$cartographyMaps[mapId].doActionOnMarkerZone(nid, $this.find('.hidden-infowindow').html()); 709 console.log('voici le html', $this.find('.hidden-infowindow').html()); 710 } else if ($this.hasClass('marker-subzone')) { 711 const zones = self.$cartographyMaps[mapId].zones; 712 const neighbourhood = $this.find('.teaser').data('neighbourhood'); 713 714 if (zones.length && neighbourhood && zones[0] && zones[0].subzones) { 715 const subZone = zones[0].subzones.find((subzone) => parseInt(subzone.id) === parseInt(neighbourhood)); 716 if (subZone) { 717 self.$cartographyMaps[mapId].drawSubzone(subZone); 718 } 719 } 720 } else { 721 self.$cartographyMaps[mapId].doActionOnMarkerNid(nid, 'click'); 722 } 723 724 if ($this.hasClass('marker-overlay-return')) { 725 self.setMapViewMode('none', true); 726 } 727 } 728 } 729 }; 730 731 /** 732 * On marker trigger click 733 */ 734 var onMarkerEnter = function (e) { 735 e.preventDefault(); 736 let $this = $(this); 737 let mapId = parseInt($this.parents('.map-overlay').siblings('.btn-map').data('map-id')); 738 console.log('onMarkerEnter', mapId); 739 let nid = $this.data('nid'); 740 741 if (typeof self.$cartographyMaps[mapId] !== 'undefined') { 742 self.$cartographyMaps[mapId].doActionOnMarkerNid(nid, 'enter'); 743 } 744 }; 745 746 /** 747 * On marker trigger click 748 */ 749 var onMarkerLeave = function (e) { 750 e.preventDefault(); 751 let $this = $(this); 752 let mapId = parseInt($this.parents('.map-overlay').siblings('.btn-map').data('map-id')); 753 let nid = $this.data('nid'); 754 755 if (typeof self.$cartographyMaps[mapId] !== 'undefined') { 756 self.$cartographyMaps[mapId].doActionOnMarkerNid(nid, 'leave'); 757 } 758 }; 759 760 var onPaginationClick = function (ev) { 761 let $this = $(this), 762 $container = $overlay.find('.map-container'), 763 page = parseInt($this.data('page')), 764 mapId = parseInt($overlay.siblings('.btn-map').data('map-id')); 765 766 clearTimeout(pageClickDelay); 767 pageClickDelay = setTimeout(function () { 768 if ($container.data('behavior') === 'nearby') { 769 OtqSearch.searchNearby(self.$cartographyMaps[mapId], page); 770 } else if ($container.data('behavior') === 'bounds') { 771 OtqSearch.searchBounds(self.$cartographyMaps[mapId], page); 772 } 773 }, 250); 774 }; 775 776 var onLeaveInfowindow = function () { 777 $mapMarkerAjax.removeClass('selected'); 778 self.setMapViewMode('hidden', true); 779 }; 780 781 var onExploreNavClick = function (ev) { 782 ev.preventDefault(); 783 self.changeExploreNav(ev.target); 784 785 let $this = $(this), 786 zoom = $this.data('zoom'), 787 mapId = parseInt($overlay.siblings('.btn-map').data('map-id')); 788 789 if (typeof self.$cartographyMaps[mapId] !== 'undefined') { 790 if (zoom === 'bounds') { 791 self.$cartographyMaps[mapId].fitToMarkers(); 792 } else { 793 zoom = parseInt(zoom); 794 self.$cartographyMaps[mapId].canvas.setZoom(zoom); 795 self.$cartographyMaps[mapId].centerOnCenter(); 796 zonesCleared = false; 797 } 798 799 if ($this.data('clear-zones') && !zonesCleared) { 800 self.$cartographyMaps[mapId].removeFeatures(); 801 zonesCleared = true; 802 } else { 803 // Dessiner les zones appropriées selon le zoom 804 self.drawZonesForZoom(mapId, zoom); 805 } 806 } 807 }; 808 809 self.drawZonesForZoom = function (mapId, zoom) { 810 let map = self.$cartographyMaps[mapId]; 811 if (!map || !map.zones) return; 812 813 // Effacer toutes les zones existantes 814 map.removeFeatures(); 815 816 // Trouver et dessiner les zones appropriées pour ce niveau de zoom
817 map.zones.forEach(function (zone) { 818 if (zone.zoom && zoom >= zone.zoom.start && zoom <= zone.zoom.end) { 819 console.log('Drawing zone for zoom', zoom, ':', zone.id); 820 map.drawZone(zone, false); 821 } 822 }); 823 }; 824 825 self.changeExploreNav = function (button) { 826 if (button.classList.contains('active')) { 827 return; 828 } 829 830 let $exploreNav = button.closest('.explore-nav'); 831 let $overlay = button.closest('.map-overlay'); 832 let $follow = $exploreNav.querySelector('.follow'); 833 let $allButtons = $exploreNav.querySelectorAll('button'); 834 835 $allButtons.forEach(function ($button) { 836 $button.classList.remove('active'); 837 }); 838 839 button.classList.add('active'); 840 841 const buttonBounds = button.getBoundingClientRect(); 842 const parentBounds = $exploreNav.getBoundingClientRect(); 843 const relativeLeft = buttonBounds.left - parentBounds.left; 844 $follow.style.transform = `translateX(${relativeLeft}px)`; 845 $follow.style.width = `${buttonBounds.width}px`; 846 847 const $mapSections = $overlay.querySelectorAll('.map-section'); 848 $mapSections.forEach(function (section) { 849 if (section.dataset.index === button.dataset.index) { 850 section.classList.add('active'); 851 } else { 852 section.classList.remove('active'); 853 } 854 }); 855 }; 856 857 /** 858 * Copy Filters from paragraph to embedded map, that way it displays always the same 859 * @param $container 860 */ 861 var copyFilters = function ($container) { 862 // Copy filters 863 let $filters = $container.find('.filters-container'); 864 865 if ($filters.length === 2) { 866 if ($($filters[0]).find('.orphan-filters').length) { 867 $($filters[1]).find('.orphan-filter.selected').trigger('click'); 868 869 $($filters[0]) 870 .find('input[type=checkbox]:checked') 871 .each(function () { 872 let $this = $(this); 873 $($filters[1]) 874 .find('input[name="' + $this.attr('name') + '"][value=' + $this.attr('value') + ']') 875 .siblings('.btn-filter') 876 .trigger('click'); 877 }); 878 } else { 879 $($filters[1]).find('input[type=checkbox]:checked').prop('checked', false).trigger('change'); 880 $($filters[0]) 881 .find('input[type=checkbox]:checked') 882 .each(function () { 883 let $this = $(this); 884 $($filters[1]) 885 .find('input[name="' + $this.attr('name') + '"][value=' + $this.attr('value') + ']') 886 .prop('checked', true) 887 .trigger('change'); 888 }); 889 } 890 } 891 892 // Copy input 893 let $searchInputs = $container.find('input[name=q]'); 894 895 if ($searchInputs.length === 2) { 896 $($searchInputs[1]).val($($searchInputs[0]).val()); 897 } 898 }; 899 900 var onCloseNeighbourhood = function (ev) { 901 ev.preventDefault(); 902 let $this = $(this); 903 let $overlay = $this.closest('.map-overlay'); 904 let $cta = $this.closest('.cta-map'); 905 let $neighbourhood = $overlay.find('.marker-trigger.active'); 906 $neighbourhood.removeClass('active'); 907 self.clearMarkers = true; 908 onMapBtnClick($cta.find('.btn-map')); 909 }; 910 911 /** ------------------------------ 912 * Private methods 913 --------------------------------- */ 914 let loadGoogle = function () { 915 return new Promise((resolve, reject) => { 916 if (mapScriptLoaded) { 917 resolve('complete'); 918 } else { 919 // This code loads the code asynchronously. 920 var tag = document.createElement('script'); 921 922 tag.src = '//maps.googleapis.com/maps/api/js?v=3®ion=CA&key=' + Globals.mapApiKey + '&callback=gmapReady&libraries=geometry'; 923 var firstScriptTag = document.getElementsByTagName('script')[0]; 924 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 925 926 mapScriptLoaded = true; 927 928 let interval = setInterval(() => { 929 if (CossetteApp.$body.hasClass('gmap-ready')) { 930 resolve('complete'); 931 clearInterval(interval); 932 } 933 }, 50); 934 } 935 }); 936 }; 937 938 let initStickyGmap = function ($container) { 939 if ($container.length) { 940 loadGoogle().then(function () { 941 let options = { 942 $container: $container, 943 zoom: 8, 944 maxZoom: 18, 945 minZoom: 3, 946 mapTypeControl: false, 947 fullscreenControl: false, 948 gestureHandling: 'greedy', 949 zoomControlOptions: { 950 position: 'LEFT_TOP' 951 }, 952 handleMarkerCallback: onStickyMarkerClick 953 }; 954 955 let $mapSelector = $container.find('.map'); 956 let $markerSelector = $container.find('.map-marker'); 957 958 let markers = self.parseMarkerData($markerSelector); 959 let must_see_markers = self.parseMarkerData(self.$listContentWrapper.find('.map-marker-must-see')); 960 961 if (must_see_markers.length) { 962 options.must_see_markers = must_see_markers; 963 } 964 965 let map = new GMap($mapSelector.get(0), options, markers); 966 967 CossetteApp.maps.push(map); 968 969 if (CossetteApp.$stickyOffset && CossetteApp.$stickyOffset.length) { 970 let gMapInterval = setInterval(function () { 971 if (typeof CossetteApp.maps[0] !== 'undefined' && CossetteApp.maps[0].loaded === true) { 972 clearInterval(gMapInterval); 973 CossetteApp.stickyMapLoaded = true; 974 975 // Set scroll from start 976 let gMapInterval2 = setInterval(function () { 977 clearInterval(gMapInterval2); 978
979 let currentScroll = 980 window.scrollY || 981 window.pageYOffset || 982 document.body.scrollTop + ((document.documentElement && document.documentElement.scrollTop) || 0); 983 self.adjustMapStickyness(currentScroll); 984 CossetteRouter.initiliazedClasses['OtqSlider'].checkListSlider(); 985 }, 300); 986 } 987 }, 100); 988 } 989 }); 990 } 991 }; 992 993 /** 994 * Set current active list item 995 * @param currentScroll 996 */ 997 var setStickyStepState = function (currentScroll) { 998 let $currentStep = CossetteApp.$stickySteps.closestToOffset({ left: 0, top: currentScroll }); 999 1000 if ($currentStep.attr('data-has-marker') === '1' && !$currentStep.hasClass('is-active')) { 1001 let markerIndex = CossetteApp.$stickyStepsWithMarker.index($currentStep); 1002 1003 if (CossetteApp.maps.length) { 1004 CossetteApp.maps[0].highlightCurrentMarker(markerIndex); 1005 CossetteApp.maps[0].handlePan(markerIndex); 1006 } 1007 1008 CossetteApp.$stickySteps.removeClass('is-active'); 1009 $currentStep.addClass('is-active'); 1010 } 1011 }; 1012 1013 /** 1014 * Highlight last marker when past the list 1015 */ 1016 var setLastStickyStepState = function () { 1017 if (!CossetteApp.maps[0]) return; 1018 1019 let $currentStep = CossetteApp.$stickySteps.last(); 1020 1021 if (!$currentStep.hasClass('is-active')) { 1022 let markerIndex = $currentStep.index() - 1; 1023 1024 CossetteApp.maps[0].highlightCurrentMarker(markerIndex); 1025 CossetteApp.maps[0].handlePan(markerIndex); 1026 1027 CossetteApp.$stickySteps.removeClass('is-active'); 1028 1029 $currentStep.addClass('is-active'); 1030 } 1031 }; 1032 1033 /** ------------------------------ 1034 * Constructor/Deconstructor 1035 --------------------------------- */ 1036 1037 self.construct = (function () {})(); 1038 1039 return self; 1040};
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.