1/** 2 * LT_MAP â Mappe con Leaflet + OpenStreetMap 3 * 4 * Sostituisce la Google Maps JavaScript API (a pagamento oltre le 10.000 5 * richieste/mese) mantenendo invariati il markup e gli stili CSS esistenti: 6 * .map-marker-container, .map-popup-wrap, .infoBox-close, .mapzoom-in/out. 7 * 8 * Le indicazioni stradali continuano ad aprire Google Maps, ma tramite un 9 * semplice link al prodotto consumer (schema "Google Maps URLs"): nessuna 10 * API key, nessuna quota, nessun costo. 11 */ 12var LT_MAP = (function ($) { 13 'use strict'; 14 15 var settings = { 16 tileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', 17 tileAttribution: '© <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">OpenStreetMap</a>', 18 maxZoom: 19, 19 iconPath: '', 20 directionsLabel: '' 21 }; 22 23 // Imposta le opzioni globali (chiamato una volta da script.php) 24 function config(options) { 25 for (var key in options) { 26 if (options.hasOwnProperty(key) && options[key] !== undefined && options[key] !== '') { 27 settings[key] = options[key]; 28 } 29 } 30 31 if (settings.iconPath && typeof L !== 'undefined') { 32 L.Icon.Default.imagePath = settings.iconPath; 33 } 34 } 35 36 function escapeHtml(value) { 37 return String(value).replace(/[&<>"']/g, function (char) { 38 return { 39 '&': '&', 40 '<': '<', 41 '>': '>', 42 '"': '"', 43 "'": ''' 44 }[char]; 45 }); 46 } 47 48 // Link alle indicazioni stradali su Google Maps. 49 // Senza "origin" Google usa automaticamente la posizione dell'utente e, 50 // da smartphone, apre direttamente l'app. 51 function directionsUrl(lat, lng) { 52 return 'https://www.google.com/maps/dir/?api=1&destination=' + 53 encodeURIComponent(lat + ',' + lng) + '&travelmode=driving'; 54 } 55 56 function tileLayer() { 57 return L.tileLayer(settings.tileUrl, { 58 maxZoom: settings.maxZoom, 59 attribution: settings.tileAttribution 60 }); 61 } 62 63 // Marker con il markup atteso da style.css 64 function markerIcon(imageUrl, count, cssClass) { 65 var html = '<div class="map-marker-container lt-marker' + (cssClass ? ' ' + cssClass : '') + '">' + 66 '<div class="marker-container">' + 67 '<span class="marker-count">' + count + '</span>' + 68 '<div class="marker-card">' + 69 '<div class="marker-holder"><img src="' + imageUrl + '" alt=""></div>' + 70 '</div>' + 71 '</div>' + 72 '</div>'; 73 74 return L.divIcon({ 75 html: html, 76 className: 'lt-marker-icon', 77 iconSize: [60, 68], 78 iconAnchor: [30, 68], 79 popupAnchor: [0, -62] 80 }); 81 } 82 83 // Contenuto dell'infobox: HTML generato da locationData() in PHP, 84 // al quale aggiungo il pulsante delle indicazioni stradali. 85 // 86 // Restituisce una funzione, non un nodo: Leaflet la invoca solo alla prima 87 // apertura del popup. Costruire subito il DOM di tutti gli infobox farebbe 88 // scaricare al caricamento della pagina l'immagine di ogni singolo marker. 89 function popupBuilder(html, lat, lng) { 90 return function () { 91 return popupContent(html, lat, lng); 92 }; 93 } 94 95 function popupContent(html, lat, lng) { 96 var wrapper = document.createElement('div'); 97 wrapper.innerHTML = html; 98 99 var footer = wrapper.querySelector('.map-popup-footer'); 100 101 if (footer && settings.directionsLabel) { 102 var link = document.createElement('a'); 103 link.className = 'main-link lt-directions-link'; 104 link.href = directionsUrl(lat, lng); 105 link.target = '_blank'; 106 link.rel = 'noopener'; 107 link.innerHTML = escapeHtml(settings.directionsLabel) + '<i class="fal fa-directions"></i>'; 108 footer.appendChild(link); 109 } 110 111 return wrapper; 112 } 113 114 // Controlli di zoom personalizzati (stili già presenti in style.css) 115 function zoomButtons(map) { 116 var container = map.getContainer(); 117 118 var zoomIn = document.createElement('div'); 119 zoomIn.className = 'mapzoom-in'; 120 121 var zoomOut = document.createElement('div'); 122 zoomOut.className = 'mapzoom-out'; 123 124 container.appendChild(zoomIn); 125 container.appendChild(zoomOut); 126 127 L.DomEvent.disableClickPropagation(zoomIn); 128 L.DomEvent.disableClickPropagation(zoomOut); 129 L.DomEvent.on(zoomIn, 'click', function () { map.zoomIn(); }); 130 L.DomEvent.on(zoomOut, 'click', function () { map.zoomOut(); }); 131 } 132 133 // La mappa può essere dentro un contenitore inizialmente nascosto
134 // (tab, modale): senza invalidateSize resterebbe grigia. 135 function keepSized(map, element) { 136 var pending = null; 137 138 function refresh() { 139 if (pending) return; 140 pending = setTimeout(function () { 141 pending = null; 142 map.invalidateSize(); 143 }, 120); 144 } 145 146 setTimeout(refresh, 300); 147 $(window).on('resize', refresh); 148 149 if (window.ResizeObserver) { 150 new ResizeObserver(refresh).observe(element); 151 } 152 } 153 154 function bindPopupBehaviour(map) { 155 map.on('popupopen', function (event) { 156 var element = event.popup.getElement(); 157 var source = event.popup._source; 158 159 if (source && source._icon) { 160 $('.map-marker-container').removeClass('clicked infoBox-opened'); 161 $(source._icon).find('.map-marker-container').addClass('clicked infoBox-opened'); 162 } 163 164 if (element) { 165 $(element).find('.infoBox-close').off('click.ltmap').on('click.ltmap', function (e) { 166 e.preventDefault(); 167 map.closePopup(); 168 }); 169 } 170 171 // Ricalcola le stelline delle recensioni dentro l'infobox 172 if (typeof cardRaining === 'function') cardRaining(); 173 }); 174 175 map.on('popupclose', function () { 176 $('.map-marker-container').removeClass('clicked infoBox-opened'); 177 }); 178 } 179 180 /** 181 * Mappa con più marker (pagine di ricerca ed elenco). 182 * 183 * options.locations mantiene il formato già usato dal sito: 184 * [ htmlInfoBox, latitudine, longitudine, offsetContatore, urlIcona, classeCss ] 185 */ 186 function initMain(options) { 187 options = options || {}; 188 189 var element = document.getElementById(options.el || 'map-main'); 190 if (!element || typeof L === 'undefined') return null; 191 192 var map = L.map(element, { 193 center: options.center, 194 zoom: options.zoom, 195 scrollWheelZoom: false, 196 zoomControl: false 197 }); 198 199 tileLayer().addTo(map); 200 zoomButtons(map); 201 keepSized(map, element); 202 bindPopupBehaviour(map); 203 204 var locations = options.locations || []; 205 var markers = []; 206 var currentIndex = null; 207 208 var layer = (options.cluster === false || typeof L.markerClusterGroup !== 'function') 209 ? L.layerGroup() 210 : L.markerClusterGroup({ 211 showCoverageOnHover: false, 212 maxClusterRadius: 50, 213 iconCreateFunction: function (cluster) { 214 return L.divIcon({ 215 html: '<div class="lt-cluster"><span>' + cluster.getChildCount() + '</span></div>', 216 className: 'lt-cluster-icon', 217 iconSize: [50, 50] 218 }); 219 } 220 }); 221 222 for (var i = 0; i < locations.length; i++) { 223 var location = locations[i]; 224 var lat = location[1]; 225 var lng = location[2]; 226 227 var marker = L.marker([lat, lng], { 228 icon: markerIcon(location[4], (location[3] || 0) + 1, location[5] || '') 229 }); 230 231 marker.ltIndex = i; 232 marker.bindPopup(popupBuilder(location[0], lat, lng), { 233 className: 'lt-popup', 234 closeButton: false, 235 minWidth: 300, 236 maxWidth: 320, 237 autoPanPadding: [20, 120] 238 }); 239 240 markers.push(marker); 241 layer.addLayer(marker); 242 } 243 244 layer.addTo(map); 245 246 map.on('popupopen', function (event) { 247 var source = event.popup._source; 248 if (source && typeof source.ltIndex === 'number') currentIndex = source.ltIndex; 249 }); 250 251 // Marker aggiuntivo non cliccabile (es. l'azienda produttrice) 252 if (options.extraMarker) { 253 var extra = options.extraMarker; 254 L.marker([extra.lat, extra.lng], { 255 icon: markerIcon(extra.img, extra.count || 0, extra.cssClass || ''), 256 interactive: false, 257 zIndexOffset: 1000 258 }).addTo(map); 259 } 260 261 // Apre il marker richiesto, uscendo prima dal cluster che lo contiene 262 function openMarker(index) { 263 var target = markers[index]; 264 if (!target) return; 265 266 if (typeof layer.zoomToShowLayer === 'function') { 267 layer.zoomToShowLayer(target, function () { target.openPopup(); }); 268 } else { 269 target.openPopup(); 270 } 271 } 272 273 $('.map-item').on('click', function (e) { 274 e.preventDefault(); 275 276 var href = $(this).attr('href') || ''; 277 var index = parseInt(href.split('#')[1], 10); 278 279 if (!isNaN(index)) openMarker(index - 1); 280 281 if ($(window).width() > 1064 && $('.map-container').hasClass('fw-map')) { 282 $('html, body').animate({ 283 scrollTop: $('.map-container').offset().top - 110 284 }, 1000); 285 return false;
286 } 287 }); 288 289 $('.nextmap-nav').on('click', function (e) { 290 e.preventDefault(); 291 if (!markers.length) return; 292 293 openMarker(currentIndex === null ? 0 : (currentIndex + 1) % markers.length); 294 }); 295 296 $('.prevmap-nav').on('click', function (e) { 297 e.preventDefault(); 298 if (!markers.length) return; 299 300 openMarker(currentIndex === null 301 ? markers.length - 1 302 : (currentIndex - 1 + markers.length) % markers.length); 303 }); 304 305 // Pulsante di abilitazione dello zoom con la rotella 306 $('.scrollContorl').on('click', function (e) { 307 e.preventDefault(); 308 $(this).toggleClass('enabledsroll'); 309 310 if ($(this).is('.enabledsroll')) { 311 map.scrollWheelZoom.enable(); 312 } else { 313 map.scrollWheelZoom.disable(); 314 } 315 }); 316 317 // Geolocalizzazione 318 $('.geoLocation, .input-with-icon.location a').on('click', function (e) { 319 e.preventDefault(); 320 if (!navigator.geolocation) return; 321 322 navigator.geolocation.getCurrentPosition(function (position) { 323 var coordinates = [position.coords.latitude, position.coords.longitude]; 324 var avatar = $('.avatar-img').attr('data-srcav'); 325
326 map.setView(coordinates, 12); 327 328 L.marker(coordinates, { 329 title: 'Your location', 330 icon: avatar 331 ? L.icon({ iconUrl: avatar, iconSize: [40, 40], iconAnchor: [20, 20], className: 'lt-user-marker' }) 332 : new L.Icon.Default() 333 }).addTo(map); 334 }); 335 }); 336 337 return map; 338 } 339 340 /** 341 * Mappa con un solo marker (pagine di dettaglio e contatti). 342 * Legge le coordinate dagli attributi data- del div #singleMap. 343 */ 344 function initSingle(options) { 345 options = options || {}; 346 347 var element = document.getElementById('singleMap'); 348 if (!element || typeof L === 'undefined') return null; 349 350 var lat = parseFloat(element.getAttribute('data-latitude')); 351 var lng = parseFloat(element.getAttribute('data-longitude')); 352 if (isNaN(lat) || isNaN(lng)) return null; 353 354 var title = element.getAttribute('data-mapTitle') || ''; 355 356 var map = L.map(element, { 357 center: [lat, lng], 358 zoom: options.zoom || 15, 359 scrollWheelZoom: false, 360 zoomControl: false 361 }); 362 363 tileLayer().addTo(map); 364 zoomButtons(map); 365 keepSized(map, element); 366 367 var marker = L.marker([lat, lng]).addTo(map); 368 marker.bindPopup(singlePopup(title, lat, lng), { 369 className: 'lt-single-popup', 370 minWidth: 180 371 }); 372 marker.openPopup(); 373 374 // Elenco laterale che sposta il marker sulla mappa (modale mappa) 375 $('.single-map-item').on('click', function (e) { 376 e.preventDefault(); 377 378 var $item = $(this); 379 var newLat = parseFloat($item.data('newlatitude')); 380 var newLng = parseFloat($item.data('newlongitude')); 381 var $title = $item.parents('.title-sin_item').find('.title-sin_map a'); 382 383 $('.map-modal-wrap').fadeIn(400); 384 setTimeout(function () { map.invalidateSize(); }, 420); 385 386 if (isNaN(newLat) || isNaN(newLng)) return; 387 388 marker.setLatLng([newLat, newLng]); 389 marker.setPopupContent(singlePopup($title.text(), newLat, newLng)); 390 map.panTo([newLat, newLng]); 391 392 $('.map-modal-container h3 a').text($title.text()).attr('href', $title.attr('href')); 393 }); 394 395 $('.map-modal-close, .map-modal-wrap-overlay').on('click', function () { 396 $('.map-modal-wrap').fadeOut(400); 397 map.setZoom(14); 398 }); 399 400 return map; 401 } 402 403 function singlePopup(title, lat, lng) { 404 var html = '<div class="lt-single-popup-inner">'; 405 406 if (title) html += '<strong>' + escapeHtml(title) + '</strong>'; 407 408 if (settings.directionsLabel) { 409 html += '<a href="' + directionsUrl(lat, lng) + '" target="_blank" rel="noopener" class="main-link">' + 410 escapeHtml(settings.directionsLabel) + '<i class="fal fa-directions"></i>' + 411 '</a>'; 412 } 413 414 return html + '</div>'; 415 } 416 417 return { 418 config: config, 419 initMain: initMain, 420 initSingle: initSingle, 421 directionsUrl: directionsUrl 422 }; 423})(jQuery); 424 425// Le pagine di dettaglio contengono solo il div #singleMap: inizializzo da solo. 426// osm-map.js è caricato unicamente dalle pagine con mappa (vedi script.php), 427// quindi questo non scatta mai dove non serve. 428jQuery(function () { 429 if (document.getElementById('singleMap')) LT_MAP.initSingle(); 430});
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.