PageSourceSearch

https://www.visitmontidauni.it/js/osm-map.js

js visitmontidauni.it collected 2026-10-02 06:10:57 UTC 14,984 bytes, 430 lines download raw bytes

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: '&copy; <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                '&': '&amp;',
40                '<': '&lt;',
41                '>': '&gt;',
42                '"': '&quot;',
43                "'": '&#39;'
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.