PageSourceSearch

https://www.hjallerupkro.dk/typo3conf/ext/idefa_spectraintegration/Resources/Public/js/script.module.js?v=1

js hjallerupkro.dk collected 2026-09-26 14:35:01 UTC 19,921 bytes, 536 lines download raw bytes

1// import { MarkerClusterer } from "@googlemaps/markerclusterer";
2import { MarkerClusterer } from "https://cdn.jsdelivr.net/npm/@googlemaps/[email protected]/+esm";
3//OLD https://cdn.skypack.dev/@googlemaps/[email protected]
4
5
6async function initMap() {
7    // Request needed libraries.
8    const { Map, InfoWindow } = await google.maps.importLibrary("maps");
9    const { AdvancedMarkerElement, PinElement } = await google.maps.importLibrary("marker");    
10    const { AutocompleteSuggestion } = await google.maps.importLibrary("places");
11    var hotelStyledMap = new google.maps.StyledMapType(
12            [
13                {
14                    "featureType": "poi.business",
15                    "stylers": [
16                        {
17                            "visibility": "off"
18                        }
19                    ]
20                },
21                {
22                    "featureType": "poi.government",
23                    "stylers": [
24                        {
25                            "visibility": "off"
26                        }
27                    ]
28                },
29                {
30                    "featureType": "poi.school",
31                    "stylers": [
32                        {
33                            "visibility": "off"
34                        }
35                    ]
36                },
37                {
38                    elementType: "geometry",
39                    stylers: [{ color: "#ecede8" }],
40                },
41                {
42                    elementType: "labels.icon",
43                    stylers: [{ visibility: "off" }],
44                },
45                {
46                    elementType: "labels.text.fill",
47                    stylers: [{ color: "#706e6e" }],
48                },
49                {
50                    elementType: "labels.text.stroke",
51                    stylers: [{ color: "#f5f5f5" }],
52                },
53                {
54                    featureType: "administrative.land_parcel",
55                    elementType: "labels.text.fill",
56                    stylers: [{ color: "#bdbdbd" }],
57                },
58                {
59                    featureType: "poi",
60                    elementType: "geometry",
61                    stylers: [{ color: "#eeeeee" }],
62                },
63                {
64                    featureType: "poi",
65                    elementType: "labels.text.fill",
66                    stylers: [{ color: "#d8b05c" }],
67                },
68                {
69                    featureType: "poi.park",
70                    elementType: "geometry",
71                    stylers: [{ color: "#e5e5e5" }],
72                },
73                {
74                    featureType: "poi.park",
75                    elementType: "labels.text.fill",
76                    stylers: [{ color: "#B64733" }],
77                },
78                {
79                    featureType: "road",
80                    elementType: "geometry",
81                    stylers: [{ color: "#ffffff" }],
82                },
83                {
84                    featureType: "road.arterial",
85                    elementType: "labels.text.fill",
86                    stylers: [{ color: "#d8b05c" }],
87                },
88                {
89                    featureType: "road.highway",
90                    elementType: "geometry",
91                    stylers: [{ color: "#dadada" }],
92                },
93                {
94                    featureType: "road.highway",
95                    elementType: "labels.text.fill",
96                    stylers: [{ color: "#CF2E2E" }],
97                },
98                {
99                    featureType: "road.local",
100                    elementType: "labels.text.fill",
101                    stylers: [{ color: "#B64733" }],
102                },
103                {
104                    featureType: "transit.line",
105                    elementType: "geometry",
106                    stylers: [{ color: "#e5e5e5" }],
107                },
108                {
109                    featureType: "transit.station",
110                    elementType: "geometry",
111                    stylers: [{ color: "#eeeeee" }],
112                },
113                {
114                    featureType: "water",
115                    elementType: "geometry",
116                    stylers: [{ color: "#c9c9c9" }],
117                },
118                {
119                    featureType: "water",
120                    elementType: "labels.text.fill",
121                    stylers: [{ color: "#B64733" }],
122                },
123            ],
124            { name: 'Hotel Styled Map' });
125
126    var mapConfig = {
127        zoom: 6.7,
128        center: { lat: 56.227, lng: 10.419 },
129        mapTypeControl: false,
130        panControl: false,
131        zoomControl: true,
132        // gestureHandling: 'none',
133        streetViewControl: false,
134        mapId: "hotelMap"
135    };
136    if (window.hotelMap) {
137        mapConfig.zoom = window.hotelMap.getZoom();
138        mapConfig.center = window.hotelMap.getCenter();
139    }
140
141    window.hotelMap = new google.maps.Map(document.getElementById('hotelmap'), mapConfig);
142
143    //Theses lines spawn a warning that the map cannot be styles this way when using mapId https://developers.google.com/maps/documentation/javascript/cloud-customization but we can't use the cluster
143s unless we use a mapid - and it actually does work, soeh...
144    window.hotelMap.mapTypes.set('styled_map', hotelStyledMap);
145    window.hotelMap.setMapTypeId('styled_map');
146
147    const Icon = '/typo3conf/ext/idefa_spectraintegration/Resources/Public/Icons/bed-solid-full.svg'; 
148
149
150    
151    const $searchElement = $('.hotelsearch .search-input');
152    window.hotelSearch = new GoogleAutocomplete($searchElement, window.hotelMap, '/typo3conf/ext/idefa_spectraintegration/Resources/Public/Icons/hotel.png');
153
154    const infoWindow = new google.maps.InfoWindow({
155        content: "",
156        disableAutoPan: true,
157    });
158
159    // Add some markers to the map.
160    var allHotels = JSON.parse(document.querySelector('.hotelsearch .search-input').dataset['hotels']);
161
162    allHotels.sort(function (a,b){
163        if(a.name < b.name) return -1;
164        if(a.name > b.name) return 1;
165        return 0;
166    });  
167    const markers = allHotels.map((hotel, i) => {
168        if(!hotel.coords){ return; }
169        const pinGlyph = new google.maps.marker.PinElement({
170        glyphColor: "black",
171        glyphSrc: Icon
172        });
173        
174        if (!hotel.location) {
175            hotel.location = window.hotelSearch.stringToCoords(hotel.coords);
176        } 
177        const marker = new google.maps.marker.AdvancedMarkerElement({
178        position:hotel.location,
179        // map:window.hotelMap,
180        content: pinGlyph.element,
181        });
182        // markers can only be keyboard focusable when they have click listeners
183        // open info window when marker is clicked
184        marker.addListener("click", () => {
185                const place=hotel;const $autoComplete=window.hotelSearch;
186                var mapElement = $autoComplete.map.getDiv();
187                if (!$autoComplete.infowindow) {
188                    $autoComplete.infowindow = new google.maps.InfoWindow();
189                    $autoComplete.infowindow.addListener('closeclick', function () {
190                        $autoComplete.resetMapZoom();
191                        $(mapElement).trigger('hotelMap.showAll');
192                    });
193                    $autoComplete.map.addListener('click', function () {
194
195                        $autoComplete.infowindow.close();
196                        $autoComplete.resetMapZoom();
197                        $(mapElement).trigger('hotelMap.showAll');
198                    });
199                }
200                
201                var placemanchet = "";
202                if (place.manchet.length > 0) {
203                    var placemanchet = '<div class="mapplacedescription">' + place.manchet + '</div>';
204                }
205
206                $autoComplete.infowindow.setContent(
207                    '<a href="//'+place.domain.domainName+'"><h3 class="mapplacetitle">' + place.name + '</h3></a>' +
208
209                    placemanchet +
210
211                    '<div class="mapplacephone">Tlf: <a href="tel:' + place.phone + '">' + place.phone + ' | <a href="mailto:' + place.email + '">' + place.email + '</a></a></div>' +
212                    '<div class="mapplaceweb"><strong><a href="//' + place.domain.domainName + '">' + place.domain.domainName + '</a></strong></div>' +
213                    '<div class="mapvisitweb"><a href="//' + place.domain.domainName + '" class="btn-primary">GÅ TIL WEBSITE</a></div>'
214                );
215
216                $autoComplete.infowindow.open($autoComplete.map, marker);
217                $autoComplete.zoomMap(place);
218                $(mapElement).trigger('hotelMap.showHotel', [place]);            
219        });
220        hotel.marker=marker;
221        window.hotelSearch.addMarker(hotel);
222        return marker;
223    }).filter(marker => marker);
224    // Add a marker clusterer to manage the markers.
225    new MarkerClusterer({ markers:markers, map:window.hotelMap });
226    
227    google.maps.event.addListenerOnce(window.hotelMap, 'idle', function () {
228        var Input = window.hotelSearch.input,
229            tA = Input.data('typeahead'),
230            hotels = Input.data('hotels');
231        if (Input.val()) {
232            var found = false;
233            for (i in hotels) {
234                if (hotels[i].name == Input.val()) {
235                    found = hotels[i];
236                    break;
237                }
238            }
239            if (found) {
240                tA.setDefault(found);
241            } else {
242                window.hotelSearch.lookup(Input.val(), function (results) {
243                    if (results.length) {
244                        tA.setDefault(results[0]);
245                    } else {
246                        console.warn('unexpected', results);
247                    }
248                });
249            }
250        } else {
251            reArrangeByDistance();
252        }
253    });
254
255}
256
257
258function GoogleAutocomplete(input, map, hotelIcon) {
259    // TODO: rewrite @see https://developers.google.com/maps/documentation/javascript/reference/autocomplete-data#AutocompleteSuggestion
260    this.input = input;
261    this.lastValue = null;
262    this.lastData = null;
263    this.minLength = 3
264    this.map = map;
265    //TODO: Places API (new)
266    // this.autocompleteService = new google.maps.places.AutocompleteSuggestion();
267    this.autocompleteService = new google.maps.places.AutocompleteService();
268    this.placeService = new google.maps.places.PlacesService(this.map);
269    this.selected = false;
270    this.sessionToken = new google.maps.places.AutocompleteSessionToken();
271    this.markers = [];
272    this.hotelIcon = hotelIcon;
273    this.__selected = false;
274
275    if (typeof (jQuery.fn.typeahead) != 'function') {
276        console.error('GoogleAutocomlete is missing bootstrap typeahead - not loading');
277    } else {
278        var $autoComplete = this;
279        $(this.input).typeahead({
280            minLength: this.minLength,
281            source: function (value, callbackFunction) {
282
283                if (!value && $autoComplete.input.val()) {
284                    this.query = value = $autoComplete.input.val();
285                }
286                if (value == $autoComplete.lastValue) { return callbackFunction($autoComplete.lastData); }
287                $autoComplete.lastValue = value;
288                //! .data returns by reference so we need to slice in order to avoid getting all results ever stored...
289                // var data = ($autoComplete.input).data('hotels').slice();
290                var data = $autoComplete.markers.slice();
291                data.sort(function (a,b){
292                    if(a.name < b.name) return -1;
293                    if(a.name > b.name) return 1;
294                    return 0;
295                });
296
297                var postProcess = function (data) {
298                    //Post process data
299                    $autoComplete.lastData = data;
300                    callbackFunction(data);
301                }
302
303                if (value && value.length >= $autoComplete.minLength) {
304                    var result = $autoComplete.lookup(value, postProcess, data);
305                } else {
306                    postProcess(data);
307                }
308            },
309            afterSelect: function (selected) {
310                $autoComplete.selected = selected;
311                if (selected.isGoogle) {
312                    $autoComplete.resolvePlaceId(selected.id);
313                } else {
314                    $autoComplete.zoomMap(selected);
315                }
316            },
317            sorter: function (items) {
318                var beginswith = [];
319                var caseSensitive = [];
320                var caseInsensitive = [];
321                var item;
322                var hotelList = $($autoComplete.input).data('hotels').slice();
323
324                while ((item = items.shift())) {
325                    var it = this.displayText(item);
326                    if (item.isHotel) {
327                        //Makes sure hotels always appear first
328                        beginswith.unshift(item);
329                        continue;
330                    }
331                    if (!it.toLowerCase().indexOf(this.query.toLowerCase())) {
332                        beginswith.push(item);
333                    } else if (~it.indexOf(this.query)) {
334                        caseSensitive.push(item);
335                    } else {
336                        caseInsensitive.push(item);
337                    }
338                }
339
340                return beginswith.concat(caseSensitive, caseInsensitive);
341            },
342            matcher: function (item) {
343                var it = item.name;
344                return ~it.toLowerCase().indexOf(this.query.toLowerCase());
345            },
346            highlighter: function (item) {
347                //disable highlight
348                return item;
349            },
350            displayText: function (element) {
351                if (element == this.__selected) {
352                    this.__selected = false;
353                    return element.name;
354                }
355                if ( element.isHotel ){
356                    return (element.icon ? '<img src="' + element.icon + '">' : '<img src="' + $autoComplete.hotelIcon + '">') + element.name;
357                }
358                return (element.icon ? '<img src="' + element.icon + '">' : '') + element.name;
359            },
360            updater: function (element) {
361                this.__selected = element;
362                return element;
363            },
364            showHintOnFocus: 'all',
365            selectOnBlur: false,
366            items: 'all',
367            fitToElement: true,
368            scrollHeight: 10
369        });
370
371    }
372}
373
374
375GoogleAutocomplete.prototype.lookup = async function (text, callback, results) {
376    var results = results || [];
377    //TODO: Places API (new) has not been used in project 843862390463 before or it is disabled
378    // const { predictions } = await google.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions(
379    //     {
380    //         input: text,
381    //         sessionToken: this.sessionToken,
382    //         // componentRestrictions: { country: 'dk' },
383    //         includedPrimaryTypes: ['regions'],//(cities)
384    //         region: "dk"
385    //     }        
386    // );
387    // console.debug(predictions);
388    // callback(results);
389    // return ;
390    // if (!predictions) {
391    //     callback(results);
392    //     return;
393    // }
394    // predictions.forEach(function (element) {
395    //     results.push(
396    //         {
397    //             id: element["place_id"],
398    //             name: element.description,
399    //             isGoogle: true
400    //         }
401    //     )
402    // });
403    // callback(results);
404    this.autocompleteService.getPlacePredictions(
405        {
406            input: text,
407            sessionToken: this.sessionToken,
408            componentRestrictions: { country: 'dk' },
409            types: ['(regions)']//(cities)
410        },
411        function (predictions, status) {
412            if (status != google.maps.places.PlacesServiceStatus.OK) {
413                callback(results);
414                return;
415            }
416            predictions.forEach(function (element) {
417                results.push(
418                    {
419                        id: element["place_id"],
420                        name: element.description,
421                        isGoogle: true
422                    }
423                )
424            });
425            callback(results);
426        }
427    );
428}
429
430GoogleAutocomplete.prototype.resolvePlaceId = function (placeid) {
431    var $autoComplete = this;
432    this.placeService.getDetails(
433        {
434            placeId: placeid,
435            sessionToken: this.sessionToken,
436            fields: ['geometry']
437        },
438        function (place, status) {
439            if (status != google.maps.places.PlacesServiceStatus.OK) {
440                return;
441            }
442            $autoComplete.zoomMap(place);
443        }
444    );
445    //Genereate new session token or be punished by google
446    this.sessionToken = new google.maps.places.AutocompleteSessionToken();
447}
448
449GoogleAutocomplete.prototype.zoomMap = function (place) {
450    const zoomLevel=14;
451    if (place.autoclick == true) { place.autoclick = false; return; }
452    if (place.geometry && place.geometry.viewport) {
453        this.map.fitBounds(place.geometry.viewport);
454        this.map.setZoom(zoomLevel);
455        this.updateFormHotels();
456    } else {
457        this.map.setZoom(zoomLevel);
458        this.map.setCenter(this.stringToCoords(place.coords));
459        place.autoclick = true;
460        console.debug(place);
461        this.updateFormHotels();
462        google.maps.event.trigger(place.marker, 'click');
463        
464    }
465    //this.updateFormHotels();
466}
467
468GoogleAutocomplete.prototype.resetMapZoom = function () {
469    //Hard coded, cause time...
470    this.map.setZoom(6);
471    this.map.setCenter({ lat: 56.227, lng: 10.419 });
472}
473
474GoogleAutocomplete.prototype.updateFormHotels = function () {
475    this.input.closest("form").find("input[name='hotels']").val(this.getVisibleHotelIds().join(","));
476}
477
478GoogleAutocomplete.prototype.stringToCoords = function (str) {
479    var tmp = str.split(',');
480    return { lat: parseFloat(tmp[0]), lng: parseFloat(tmp[1]) };
481}
482
483GoogleAutocomplete.prototype.addMarker = function (place) {
484    if (place.coords && !place.location) {
485        place.location = this.stringToCoords(place.coords);
486    }
487    this.markers.push(place);
488}
489
490GoogleAutocomplete.prototype.getVisibleHotelIds = function () {
491
492    var visible = [], bounds = this.map.getBounds();
493    this.markers.forEach(gaPush);
494
495    function gaPush(element, index){
496        if (bounds.contains(element.location)) {
497            visible.push(element.id);
498        }
499    }
500    reArrangeByDistance();
501
502    return visible;
503}
504
505function reArrangeByDistance() {
506    var $hotels = $('.container.hotel-items'), mapCenter = window.hotelMap.getCenter(), hotelPos = false, dist = false, distArr = [];
507    var searchValue = $('.search-input').val();
508  
509    $hotels.each(function (i, e) {
510        $hotel = $(this);
511        hotelPos = new google.maps.LatLng($hotel.data('coords')[0], $hotel.data('coords')[1]);
512        dist = (google.maps.geometry.spherical.computeDistanceBetween(mapCenter, hotelPos) / 1000).toFixed(1);
513        distArr.push({
514            'hotel': $hotel,
515            'dist': dist
516        });
517    });
518    if (distArr.length) {
519        distArr.sort(function (a, b) {
520            return b.dist - a.dist;
521        });
522        $tmp = $('<div>');
523        $hotels.first().before($tmp);
524        for (i in distArr) {
525            $tmp.after(distArr[i].hotel.remove());
526            // console.debug(distArr[i].hotel.find('h2').text(), distArr[i].dist);
527            if(searchValue.length > 0){
528                distArr[i].hotel.find('.distinkm').html(distArr[i].dist + ' km');
529            }
530            
531        }
532        $tmp.remove();
533    }
534}
535
536initMap();

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.