PageSourceSearch

https://www.royalqueenseeds.nl/modules/shoplocator/views/front/js/…?_=d48b1777d0aaac453cd44bd14c47baa7

js royalqueenseeds.nl collected 2026-10-08 00:01:58 UTC 15,538 bytes, 473 lines download raw bytes

1ShopLocator = {
2	shopsList: [],
3  isSearch: false,
4  markers: [],
5  selectedShop: false,
6  noShops: false,
7
8  getCountryCode: function() {
9    const url = window.location.href;
10    try {
11      const urlObj = new URL(url);
12      const pathSegments = urlObj.pathname.split('/').filter(segment => segment.length > 0);
13      if (pathSegments.length > 0 && /^[a-z]{2}$/i.test(pathSegments[0])) {
14        return pathSegments[0].toLowerCase();
15      }
16      const hostname = urlObj.hostname;
17      const domainParts = hostname.split('.');
18      const tld = domainParts[domainParts.length - 1];
19      if (tld.length === 2 && tld !== 'com') {
20        return tld.toLowerCase();
21      }
22      return null;
23    } catch (e) {
24      console.error('Error parsing URL:', e);
25      return null;
26    }
27  },
28
29  init: function () {
30    this.shopsList = $.parseJSON($('#shop-locator-vars').text());
31    const countryCode = this.getCountryCode();
32
33    if (countryCode) {
34        this.zoomToCountry(countryCode);
35    } else {
36        this.noShops = !this.shopsList || !this.shopsList.length;
37        this.renderMap();
38    }
39
40    this.eventSearchShop();
41  },
42
43	eventSearchShop: function() {
44		var self = this;
45    let searchInput = $('.shop-search-input input');
46
47		$('.shop-search-input button').click(function (e) {
48			e.preventDefault();
49			self.getEndRenderSearchList();
50		});
51
52    searchInput.keyup(function (e) {
53			if (e.which == 13) {
54				self.getEndRenderSearchList();
55			}
56		});
57
58  },
59
60  zoomToCountry: function(countryCode) {
61    var self = this;
62    self.isSearch = true;
63    self.zoomToCountryActive = true;
64    $.ajax({
65        url: baseDir + 'modules/shoplocator/ajax.php',
66        type: "POST",
67        dataType: 'json',
68        data: {
69            search: countryCode.toUpperCase(),
70            ajax: 1
71        },
72        success: function(data) {
73            self.shopsList = data.shops || [];
74            self.noShops = !data.shops || !data.shops.length;
75            $('.map-shops-list').empty();
76            self.renderMap();
77        }
78    });
79  },
80
81	getEndRenderSearchList: function (e) {
82
83    let search = $('.shop-search-input input').val().trim();
84    let selectedShopId = $('#shop-search-input-shop-id').val();
85    this.isSearch = search !== '';
86    this.selectedShop = false;
87
88		var self = this;
89		$.ajax({
90			url: baseDir + 'modules/shoplocator/ajax.php',
91			type: "POST",
92			dataType: 'json',
93			data: {
94				search: search,
95				ajax: 1
96			},
97			success: function(data) {
98        $('.search-messages-container .shop-count .count').text(data.shops?.length || 0);
99        self.shopsList = data.shops || [];
100        self.noShops = !data.shops || !data.shops.length;
101        $('.map-shops-list').empty();
102
103        self.renderMap(function() {
104          if (data.shops && data.shops.length > 0) {
105            setTimeout(function() {
106              if (data.searchType === 'country') {
107                const bounds = new google.maps.LatLngBounds();
108                self.shopsList.forEach(function(shop) {
109                  if (self.markers[shop.id_shop]) {
110                    bounds.extend(self.markers[shop.id_shop].getPosition());
111                  }
112                });
113                if (self.map && self.shopsList.length > 0) {
114                  self.map.fitBounds(bounds);
115                }
116
117                if (selectedShopId) {
118                  google.maps.event.trigger(self.markers[selectedShopId], 'click');
119                }
120              } else {
121                if (data.selectedShopId != null) {
122                  selectedShopId = data.selectedShopId;
123                }
124                if (selectedShopId) {
125                  google.maps.event.trigger(self.markers[selectedShopId], 'click');
126                } else if (self.shopsList.length > 0) {
127                  const bounds = new google.maps.LatLngBounds();
128                  self.shopsList.forEach(function(shop) {
129                    if (self.markers[shop.id_shop]) {
130                      bounds.extend(self.markers[shop.id_shop].getPosition());
131                    }
132                  });
133                  if (self.map && self.shopsList.length > 0) {
134                    self.map.fitBounds(bounds);
135                  }
136                }
137              }
138            }, 300);
139          }
140        });
141      }
142		});
143	},
144
145  renderMap: function (callback) {
146    const self = this;
147
148    if (self.noShops && !self.zoomToCountryActive) {
149        const countryCode = self.getCountryCode();
150        if (countryCode) {
151            self.zoomToCountry(countryCode);
152            return;
153        }
154    }
155
156    function initMap(userLatLng = null) {
157      const bounds = new google.maps.LatLngBounds();
158
159      const map = new google.maps.Map(document.getElementById('shop-locator-map'), {
160        zoom: self.shopsList.length > 0 ? 7 : (self.zoomToCountryActive ? 5 : 2),
161        center: userLatLng || { lat: 0, lng: 0 },
162        zoomControl: true,
163        zoomControlOptions: { position: google.maps.ControlPosition.RIGHT_TOP },
164        mapTypeControl: true,
165        mapTypeControlOptions: {
166          style: google.maps.MapTypeControlStyle.DEFAULT,
167          position: google.maps.ControlPosition.LEFT_TOP,
168        },
169        streetViewControl: false,
170        streetViewControlOptions: {
171          position: google.maps.ControlPosition.RIGHT_TOP,
172        },
173        fullscreenControl: false,
174        rotateControl: false,
175        scaleControl: false,
176      });
177
178      if (userLatLng && !self.zoomToCountryActive) {
179        new google.maps.Marker({
180          position: userLatLng,
181          map: map,
182          icon: {
183            path: google.maps.SymbolPath.CIRCLE,
184            scale: 6,
185            fillColor: '#4285F4',
186            fillOpacity: 1,
187            strokeWeight: 1,
188            strokeColor: '#ffffff'
189          }
190        });
191
192        if (!self.isSearch) {
193          bounds.extend(userLatLng);
194        }
195
196        self.shopsList.forEach(function (shop) {
197          const coords = shop.coordinates.split(/,\s*/);
198          const lat = parseFloat(coords[0]);
199          const lng = parseFloat(coords[1]);
200
201          if (!isNaN(lat) && !isNaN(lng)) {
202            const shopLatLng = new google.maps.LatLng(lat, lng);
203            const distanceInMeters = google.maps.geometry.spherical.computeDistanceBetween(userLatLng, shopLatLng);
204            shop.distance = parseFloat((distanceInMeters / 1000).toFixed(2));
205          } else {
206            shop.distance = null;
207          }
208        });
209
210        self.shopsList.sort(function (a, b) {
211          if (a.distance === null) return 1;
212          if (b.distance === null) return -1;
213          return a.distance - b.distance;
214        });
215      }
216
217      const container = $('.map-shops-list');
218      container.empty();
219
220      self.shopsList.forEach(function (shop) {
221        let shopRateContainer = '';
222        if (shop.rating) {
223          shopRateContainer = `
224            <div class="shop-rate">
225              <img src="/themes/royalqueenseeds/assets/images/modules/shoplocator/icon-star--green.webp" alt=""/>
226              <span>${shop.rating} <span class="rate-count">(${shop.user_ratings_total})</span></span>
227            </div>
228          `;
229        }
230
231        const shopHtml = `
232        <div class="shop-container">
233          <div>
234            <div class="shop-name">
235              <span>${shop.shop_name}</span>
236            </div>
237            ${shopRateContainer}
238            <div class="shop-address">
239              <img src="/themes/royalqueenseeds/assets/images/modules/shoplocator/icon-location--grey.svg" alt=""/>
240              <span>${shop.shop_address}</span>
241            </div>
242          </div>
243          <div class="shop-map-link" data-shop-id="${shop.id_shop}"><span class="link-text">${linkText} ></span><span class="selected-text hidden">${selectedText}</span></div>
244        </div>`;
245
246        container.append(shopHtml);
247      });
248
249      $('.search-messages-container .shop-count .count').text(self.shopsList.length);
250      $('.search-messages-container .shop-count').removeClass('hidden');
251
252      self.markers = [];
253      const popups = [];
254
255      $.each(self.shopsList, function (key, itm) {
256        const coords = itm.coordinates.split(/,\s*/);
257        const lat = parseFloat(coords[0]);
258        const lng = parseFloat(coords[1]);
259
260        if (isNaN(lat) || isNaN(lng)) return;
261
262        const shopLatLng = new google.maps.LatLng(lat, lng);
263        bounds.extend(shopLatLng);
264
265        let shopRateContainer = '';
266        if (itm.rating) {
267          shopRateContainer = `
268            <div class="shop-rate">
269              <img src="/themes/royalqueenseeds/assets/images/modules/shoplocator/icon-star--green.webp" alt=""/>
270              <span>${itm.rating} <span class="rate-count">(${itm.user_ratings_total})</span></span>
271            </div>
272          `;
273        }
274
275        let shopImage = '';
276        if (itm.image) {
277          shopImage = `
278            <img class="shop-img" src="${itm.image}" alt="">
279          `;
280        }
281
282        const popupContent = `
283        <div class="shop-locator-map-popup">
284          ${shopImage}
285          <div class="popup-text-content">
286            <div class="shop-name">
287              <span>${itm.shop_name}</span>
288            </div>
289            ${shopRateContainer}
290            <div class="shop-address">
291              <img src="/themes/royalqueenseeds/assets/images/modules/shoplocator/icon-location--grey.svg" alt=""/>
292              <span class="text">${itm.zip_code} ${itm.shop_address}</span>
293            </div>
294          </div>
295        </div>`;
296
297        popups[itm.id_shop] = new google.maps.InfoWindow({ content: popupContent });
298
299        let mapPin = `${imageDir}icon-location--default.svg`;
300
301        if (id_main_featured_shop && id_main_featured_shop == itm.id_shop) {
302          mapPin = `${imageDir}icon-location--orange.svg`;
303        } else if (ids_featured_shops && ids_featured_shops.includes(itm.id_shop)) {
304          mapPin = `${imageDir}icon-location--green--dark.svg`;
305        }
306
307        let shopMapIcon = '';
308
309        function createCombinedIcon(baseUrl, overlayUrl, callback) {
310          let baseImg = new Image();
311          let overlayImg = new Image();
312
313          let loaded = 0;
314          function tryFinish() {
315            loaded++;
316            if (loaded === 2) {
317              let canvas = document.createElement('canvas');
318              let ctx = canvas.getContext('2d');
319              canvas.width = baseImg.width;
320              canvas.height = baseImg.height;
321
322              ctx.drawImage(baseImg, 0, 0);
323
324              let overlayX = 13.5;
325              let overlayY = 9;
326              let overlaySize = 27;
327              let radius = (overlaySize / 2) - 1;
328
329              ctx.save();
330              ctx.beginPath();
331              ctx.arc(overlayX + radius, overlayY + radius, radius, 0, Math.PI * 2);
332              ctx.closePath();
333              ctx.clip();
334
335              ctx.drawImage(overlayImg, overlayX, overlayY, overlaySize, overlaySize);
336              ctx.restore();
337
338              callback(canvas.toDataURL());
339            }
340          }
341
342
343          baseImg.crossOrigin = 'anonymous';
344          overlayImg.crossOrigin = 'anonymous';
345
346          baseImg.onload = tryFinish;
347          overlayImg.onload = tryFinish;
348
349          baseImg.src = baseUrl;
350          overlayImg.src = overlayUrl;
351        }
352
353        function createMarker(key, shopLatLng, iconUrl) {
354          self.markers[key] = new google.maps.Marker({
355            position: shopLatLng,
356            map: map,
357            icon: {
358              url: iconUrl,
359              scaledSize: new google.maps.Size(40, 40)
360            }
361          });
362
363          self.markers[key].addListener('click', function () {
364            popups.forEach(p => p.close());
365            popups[key].open(map, self.markers[key]);
366
367            Object.entries(self.markers).forEach(([shopId, marker]) => {
368              marker.setIcon(`${imageDir}icon-location--default.svg`);
369              if (id_main_featured_shop && id_main_featured_shop == shopId) {
370                marker.setIcon(`${imageDir}icon-location--orange.svg`);
371              } else if (ids_featured_shops && ids_featured_shops.includes(shopId)) {
372                marker.setIcon(`${imageDir}icon-location--green--dark.svg`);
373              }
374            });
375
376            self.markers[key].setIcon(`${imageDir}icon-location--green.svg`);
377
378            const $selectedShop = $(`.shop-map-link[data-shop-id="${key}"]`).closest('.map-shops-list .shop-container');
379            const $shopList = $selectedShop.parent();
380
381            $('.shop-container').removeClass('selected');
382            $('.shop-map-link .selected-text').addClass('hidden');
383            $('.shop-map-link .link-text').removeClass('hidden');
384
385            $selectedShop.addClass('selected');
386            $selectedShop.find('.selected-text').removeClass('hidden');
387            $selectedShop.find('.link-text').addClass('hidden');
388
389            $shopList.animate({
390                scrollTop: $selectedShop.offset().top - $shopList.offset().top + $shopList.scrollTop()
391            }, 300);
392
393            let offset = window.innerWidth <= 768 ? 55 : 150;
394            $('html, body').animate({
395              scrollTop: $('.map-container').offset().top - offset
396            }, 500);
397
398            map.setCenter(self.markers[key].getPosition());
399            map.setZoom(15);
400          });
401        }
402
403        if (shopMapIcon) {
404          createCombinedIcon(mapPin, shopMapIcon, function (combinedUrl) {
405            createMarker(itm.id_shop, shopLatLng, combinedUrl);
406          });
407        } else {
408          createMarker(itm.id_shop, shopLatLng, mapPin);
409        }
410      });
411
412      if ((!userLatLng || self.isSearch || self.zoomToCountryActive) && !self.selectedShop && self.shopsList.length > 0) {
413        map.fitBounds(bounds);
414      }
415
416      const zoomChangeBoundsListener = google.maps.event.addListenerOnce(map, 'bounds_changed', function () {
417        if (this.getZoom() > 15) {
418          this.setZoom(15);
419        }
420      });
421
422      setTimeout(() => {
423        google.maps.event.removeListener(zoomChangeBoundsListener);
424      }, 2000);
425
426      if (typeof callback === 'function') {
427        setTimeout(function() {
428          callback();
429        }, 100);
430      }
431    }
432
433    if (navigator.geolocation && !self.zoomToCountryActive) {
434      navigator.geolocation.getCurrentPosition(
435        function (position) {
436          const userLatLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
437          initMap(userLatLng);
438        },
439        function (error) {
440          console.error(`Geolocation error (${error.code}): ${error.message}`);
441          initMap(null);
442        }
443      );
444    } else {
445      self.zoomToCountryActive = false;
446      initMap(null);
447    }
448  },
449}
450
451$(document).on('click', '.shop-container, .featured-shops .carousel-item, .shop-map-link', function (event) {
452  let shopId = $(this).find('.shop-map-link').data('shop-id');
453  if (typeof shopId == 'undefined') {
454    shopId = $(this).data('shop-id');
455  }
456
457  const $shopLink = $(`.map-shops-list .shop-map-link[data-shop-id="${shopId}"]`);
458  if ($shopLink.length) {
459     if (ShopLocator.markers[shopId]) {
460       google.maps.event.trigger(ShopLocator.markers[shopId], 'click');
461    }
462  } else {
463    $('.shop-search-input input').val('');
464    $('#shop-search-input-shop-id').val(shopId);
465    $('.shop-search-input button').click();
466    $('#shop-search-input-shop-id').val('');
467  }
468
469  if ($(this).closest('.ftup-wrapper').length > 0) {
470    event.preventDefault();
471    $('.ftup-wrapper').fadeOut();
472  }
473});

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.