PageSourceSearch

https://www.hellermanntyton.es/js/alpineworldwide.min.js

js hellermanntyton.es collected 2026-10-02 21:35:18 UTC 15,464 bytes, 465 lines download raw bytes

1/**
2 * Alpine.js component for Find Us Worldwide map functionality
3 */
4var MY_MAPTYPE_ID = 'custom_style'
5
6var featureOpts = [
7  {
8    featureType: 'water',
9    elementType: 'geometry',
10    stylers: [
11      {hue: '#e4ebf1'},
12      {saturation: -30},
13      {lightness: 67},
14      {visibility: 'on'}
15    ]
16  }, {
17    featureType: 'water',
18    elementType: 'labels',
19    stylers: [
20      {hue: '#5e89ae'},
21      {saturation: -27},
22      {lightness: -31},
23      {visibility: 'on'}
24    ]
25  }, {
26    featureType: 'road',
27    elementType: 'geometry',
28    stylers: [
29      {hue: '#ffffff'},
30      {saturation: -100},
31      {lightness: 100},
32      {visibility: 'on'}
33    ]
34  }, {
35    featureType: 'landscape',
36    elementType: 'geometry',
37    stylers: [
38      {hue: '#7a9dbc'},
39      {saturation: 8},
40      {lightness: -32},
41      {visibility: 'on'}
42    ]
43  }, {
44    featureType: 'poi',
45    elementType: 'geometry',
46    stylers: [
47      {hue: '#7a9dbc'},
48      {saturation: 33},
49      {lightness: 20},
50      {visibility: 'on'}
51    ]
52  }, {
53    featureType: 'administrative',
54    elementType: 'labels',
55    stylers: [
56      {hue: '#7a9dbc'},
57      {saturation: 33},
58      {lightness: 20},
59      {visibility: 'on'}
60    ]
61  }, {
62    featureType: 'road',
63    elementType: 'labels',
64    stylers: [
65      {hue: '#5e89ae'},
66      {saturation: -67},
67      {lightness: -18},
68      {visibility: 'on'}
69    ]
70  }, {
71    featureType: 'poi',
72    elementType: 'labels',
73    stylers: [
74      {hue: '#e4ebf1'},
75      {saturation: -26},
76      {lightness: 20},
77      {visibility: 'on'}
78    ]
79  }, {
80    featureType: 'administrative',
81    elementType: 'geometry.fill',
82    stylers: [
83      {visibility: 'off'}
84    ]
85  }
86]
87
88document.addEventListener('alpine:init', () => {
89
90  Alpine.data('WorldwideLocationData', (locationsUrl) => ({
91    // Data properties
92    locations: [],
93    selectedLocation: null,
94    selectedSupported: false,
95    infoWindows: [],
96    map: null,
97    markers: [],
98
99    // CSP build: optional chaining is unsupported in inline template expressions,
100    // so expose the selected-location fields as getters (JS itself keeps ?.).
101    get selectedUuid() { return this.selectedLocation?.uuid; },
102    get selectedFlag() { return this.selectedLocation?.flag; },
103    get selectedName() { return this.selectedLocation?.content?.name; },
104    get selectedAddress() { return this.selectedLocation?.content?.address; },
105    get selectedAddress2() { return this.selectedLocation?.content?.address2; },
106    get selectedAddress3() { return this.selectedLocation?.content?.address3; },
107    get selectedTown() { return this.selectedLocation?.content?.town; },
108    get selectedContact() { return this.selectedLocation?.content?.contact; },
109    get selectedPhone() { return this.selectedLocation?.content?.phone; },
110    get selectedFax() { return this.selectedLocation?.content?.fax; },
111    get selectedMail() { return this.selectedLocation?.content?.mail; },
112    get selectedMail2() { return this.selectedLocation?.content?.mail2; },
113    get selectedUrl() { return this.selectedLocation?.content?.url; },
114    get selectedUrl2() { return this.selectedLocation?.content?.url2; },
115
116    // Initialize the component
117    init() {
118      console.log('Initializing WorldwideLocationData component');
119      // Use setTimeout to ensure DOM is ready
120      setTimeout(() => {
121        this.loadLocations();
122        this.setupLocationClickHandlers();
123        this.setupMap();
124      }, 100);
125    },
126
127    // Load locations from the API
128    async loadLocations() {
129      try {
130        const response = await fetch(locationsUrl);
131        if (!response.ok) {
132          throw new Error(`HTTP error! status: ${response.status}`);
133        }
134        const data = await response.json();
135        this.locations = data.locations || [];
136        this.setupTypeahead();
137
138      } catch (error) {
139        console.error('Error loading locations:', error);
140        // Fallback to global locationsForMap if available
141        if (typeof locationsForMap !== 'undefined') {
142          this.locations = locationsForMap || [];
143        }
144      }
145    },
146    setupMap() {
147      // Wait for Google Maps API to be loaded
148      let attempts = 0;
149      const maxAttempts = 50; // 5 seconds max
150
151      const checkGoogleMaps = () => {
152        attempts++;
153        if (typeof google !== 'undefined' && google.maps) {
154          this.initializeMap();
155        } else if (attempts < maxAttempts) {
156          setTimeout(checkGoogleMaps, 100);
157        } else {
158          console.error('Google Maps API failed to load after 5 seconds');
159        }
160      };
161      checkGoogleMaps();
162    },
163
164    // Initialize the Google Map
165    initializeMap() {
166      const mapElement = document.getElementById('map_canvas');
167      if (!mapElement) {
168        console.error('Map canvas element not found');
169        // Retry after a short delay
170        setTimeout(() => this.initializeMap(), 500);
171        return;
172      }
173
174      try {
175        // Check if element has dimensions
176        const rect = mapElement.getBoundingClientRect();
177        if (rect.width === 0 || rect.height === 0) {
178          console.warn('Map canvas has no dimensions, retrying...');
179          setTimeout(() => this.initializeMap(), 500);
180          return;
181        }
182
183        this.map = new google.maps.Map(mapElement, {
184          zoom: 2,
185          minZoom: 1,
186          disableDefaultUI: true,
187          zoomControl: true,
188          zoomControlOptions: {
189            style: google.maps.ZoomControlStyle.SMALL
190          },
191          center: new google.maps.LatLng(30, 10),
192          mapTypeControlOptions: {
193            mapTypeIds: [google.maps.MapTypeId.ROADMAP, MY_MAPTYPE_ID]
194          },
195          mapTypeId: MY_MAPTYPE_ID
196        });
197
198        var markerImage = new google.maps.MarkerImage(markerImageURL,
199          new google.maps.Size(23, 31),
200          new google.maps.Point(0, 0),
201          new google.maps.Point(13, 31))
202
203        var infowindow = new google.maps.InfoWindow()
204
205        var styledMapOptions = {
206          name: 'Custom Style'
207        }
208
209        var customMapType = new google.maps.StyledMapType(featureOpts, styledMapOptions)
210
211        this.map.mapTypes.set(MY_MAPTYPE_ID, customMapType)
212
213        locationsForMap.forEach((location) => {
214          this.setMarker(location, markerImage, template, infowindow, this.map);
215        });
216
217      } catch (error) {
218        console.error('Error initializing map:', error);
219      }
220    },
221
222    // Create a marker for a location
223    setMarker(location, markerImage, template, infowindow, map) {
224      if (location.showMarker) {
225        var marker = new google.maps.Marker({
226          map: map,
227          position: new google.maps.LatLng(location.mapsLat, location.mapsLng),
228          title: location.name,
229          icon: markerImage
230        })
231        this.markers.push(marker);
232        google.maps.event.addListener(marker, 'click', function () {
233          map.setZoom(8)
234          map.setCenter(marker.getPosition())
235          infowindow.setContent(template(location))
236          infowindow.open(marker.get('map'), marker)
237        })
238        $('.aboutUsLocation a[data-uuid=' + location.uuid + ']').each(function () {
239          google.maps.event.addDomListener(this, 'click', function () {
240            google.maps.event.trigger(marker, 'click', {})
241          })
242        })
243      }
244    },
245
246    // Focus on a location on the map
247    focusOnLocation(location) {
248      if (!this.map || !location.mapsLat || !location.mapsLng) {
249        return;
250      }
251
252      try {
253        const position = {lat: parseFloat(location.mapsLat), lng: parseFloat(location.mapsLng)};
254        this.map.setCenter(position);
255        this.map.setZoom(8);
256
257        // Find and trigger the marker
258        const marker = this.markers.find(m =>
259          Math.abs(m.getPosition().lat() - position.lat) < 0.001 &&
260          Math.abs(m.getPosition().lng() - position.lng) < 0.001
261        );
262
263        if (marker) {
264          google.maps.event.trigger(marker, 'click');
265        }
266      } catch (error) {
267        console.error('Error focusing on location:', error);
268      }
269    },
270
271    // Generate info window content
272    generateInfoWindowContent(location) {
273      // Try to use Handlebars template if available
274      if (typeof template !== 'undefined' && template) {
275        try {
276          return template(location);
277        } catch (error) {
278          console.error('Error generating template content:', error);
279        }
280      }
281
282      // Check for global Handlebars and template element
283      if (typeof Handlebars !== 'undefined') {
284        const templateEl = document.getElementById('map-infowindow-template');
285        if (templateEl && templateEl.innerHTML.trim()) {
286          try {
287            const compiledTemplate = Handlebars.compile(templateEl.innerHTML);
288            return compiledTemplate(location);
289          } catch (error) {
290            console.error('Error compiling Handlebars template:', error);
291          }
292        }
293      }
294
295      // Fallback content
296      let content = `<div>`;
297      if (location.flag) {
298        content += `<div class="aboutUsLocationFlag" style="background-image:url('${location.flag}'); background-repeat:no-repeat;">&nbsp;</div>`;
299      }
300      content += `<p><strong>${location.content?.name || location.name}</strong></p>`;
301      if (location.content?.address) content += `<p>${location.content.address}</p>`;
302      if (location.content?.address2) content += `<p>${location.content.address2}</p>`;
303      if (location.content?.address3) content += `<p>${location.content.address3}</p>`;
304      if (location.content?.town) content += `<p>${location.content.town}</p>`;
305      if (location.content?.contact) content += `<p>${location.content.contact}</p>`;
306      if (location.content?.phone) content += `<p>Phone: ${location.content.phone}</p>`;
307      if (location.content?.fax) content += `<p>Fax: ${location.content.fax}</p>`;
308      if (location.content?.mail) content += `<p class="aboutUsLocationMail"><a href="mailto:${location.content.mail}">${location.content.mail}</a></p>`;
309      if (location.content?.mail2) content += `<p class="aboutUsLocationMail"><a href="mailto:${location.content.mail2}">${location.content.mail2}</a></p>`;
310      if (location.content?.url) content += `<p class="aboutUsLocationWebsite"><a href="https://${location.content.url}">${location.content.url}</a></p>`;
311      if (location.content?.url2) content += `<p class="aboutUsLocationWebsite"><a href="https://${location.content.url2}">${location.content.url2}</a></p>`;
312      content += `</div>`;
313      return content;
314    },
315
316    // Close all info windows
317    closeAllInfoWindows() {
318      this.infoWindows.forEach(infoWindow => infoWindow.close());
319    },
320
321    // Select a location
322    selectLocation(location) {
323      if (!location) {
324        this.selectedLocation = null;
325        this.selectedSupported = false;
326        return;
327      }
328
329      // Check if this location is supported by another location
330      if (location.supportedBy) {
331        const supportingLocation = this.locations.find(loc => loc.uuid === location.supportedBy);
332        if (supportingLocation) {
333          this.selectedLocation = supportingLocation;
334          this.selectedSupported = true;
335        } else {
336          this.selectedLocation = location;
337          this.selectedSupported = false;
338        }
339      } else {
340        this.selectedLocation = location;
341        this.selectedSupported = false;
342      }
343    },
344
345    // Setup typeahead functionality
346    setupTypeahead() {
347      // Wait for typeahead libraries to be loaded
348      const checkTypeahead = () => {
349        if (typeof Bloodhound !== 'undefined' && typeof $ !== 'undefined') {
350          this.initializeTypeahead();
351        } else {
352          setTimeout(checkTypeahead, 100);
353        }
354      };
355      checkTypeahead();
356    },
357
358    // Initialize typeahead
359    // Alpine.js version of AngularJS typeahead setup
360    initializeTypeahead() {
361      try {
362        const locationSearchEngine = new Bloodhound({
363          datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
364          queryTokenizer: Bloodhound.tokenizers.whitespace,
365          limit: 5,
366          prefetch: {
367            url: locationsUrl,
368            ttl: 1800000,
369            filter: function (data) {
370              return data.locations.filter(el => el.hideInFurtherCountrySupport !== true);
371            }
372          }
373        });
374        locationSearchEngine.initialize();
375
376        const $searchInput = $('.typeaheadlocations');
377        if ($searchInput.length) {
378          $searchInput.typeahead({
379            hint: true,
380            highlight: true,
381            minLength: 1
382          }, {
383            name: 'locations',
384            source: locationSearchEngine.ttAdapter(),
385            display: 'name',
386            limit: 4,
387            templates: {
388              suggestion: Handlebars.compile($("#suggestionTemplate").html())
389            }
390          });
391
392          $searchInput.on('typeahead:selected', (ev, suggestion) => {
393            this.selectLocation(suggestion);
394            if (this.selectedSupported === true) {
395              this.focusOnLocation(this.selectedLocation);
396            } else {
397              this.focusOnLocation(suggestion);
398            }
399          });
400
401          $searchInput.on('focusout', (ev) => {
402            const value = ev.target.value;
403            const location = this.locations.find(loc => loc.name.toLowerCase() === value.toLowerCase());
404            if (location) {
405              this.selectLocation(location);
406              if (this.selectedSupported === true) {
407                this.focusOnLocation(this.selectedLocation);
408              } else {
409                this.focusOnLocation(location);
410              }
411          }});
412
413          $searchInput.on('keypress', (ev) => {
414            if (ev.keyCode === 13) {
415              const value = ev.target.value;
416              const location = this.locations.find(loc => loc.name.toLowerCase() === value.toLowerCase());
417              if (location) {
418                this.selectLocation(location);
419                if (this.selectedSupported === true) {
420                  this.focusOnLocation(this.selectedLocation);
421                } else {
422                  this.focusOnLocation(location);
423                }
424            }
425          }});
426
427          $searchInput.parent().find(".tt-dropdown-menu").on('click', function (e) {
428            e.stopPropagation();
429          });
430        }
431      } catch (error) {
432        console.error('Error initializing typeahead:', error);
433      }
434    },
435
436    // Setup location click handlers for static location list
437    setupLocationClickHandlers() {
438      document.addEventListener('click', (event) => {
439        const link = event.target.closest('a[data-uuid]');
440        if (link) {
441          event.preventDefault();
442          const uuid = link.getAttribute('data-uuid');
443          const location = this.locations.find(loc => loc.uuid === uuid || loc.identifier === uuid);
444          if (location) {
445            this.selectLocation(location);
446            this.focusOnLocation(location);
447
448            // Scroll to map
449            const mapElement = document.getElementById('click-jump');
450            if (mapElement) {
451              mapElement.scrollIntoView({behavior: 'smooth'});
452            }
453          }
454        }
455      });
456    },
457
458    // Utility method to get location by UUID
459    getLocationByUuid(uuid) {
460      return this.locations.find(loc => loc.uuid === uuid || loc.identifier === uuid);
461    }
462  }));
463});
464
465

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.