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.