1/** 2 * WP Google Map Frontend Logic 3 * Centralized script to handle map initialization, search, and directions. 4 * @since 1.9.1 5 */ 6 7(function ($) { 8 "use strict"; 9 10 window.wgm_front = { 11 instances: {}, 12 13 init: function (data) { 14 var self = this; 15 var id = data.count; 16 this.instances[id] = data; 17 18 // Define legacy config early for themes/templates 19 window["wgm_config_" + id] = data.config; 20 21 $(document).ready(function () { 22 self.initMap(id); 23 self.initLightbox(id); 24 self.initSelect2(); 25 }); 26 }, 27 28 /** 29 * Escape HTML strings to prevent XSS 30 * @param {string} str 31 * @returns {string} 32 */ 33 escapeHTML: function (str) { 34 if (!str) return ""; 35 return String(str) 36 .replace(/&/g, "&") 37 .replace(/</g, "<") 38 .replace(/>/g, ">") 39 .replace(/"/g, """) 40 .replace(/'/g, "'"); 41 }, 42 43 /** 44 * Remove theme-injected classes that might interfere with design 45 * @param {string} selector 46 * @param {string[]} allowed 47 */ 48 sanitizeClasses: function (selector, allowed) { 49 $(selector).each(function () { 50 var $el = $(this); 51 var classes = ($el.attr("class") || "").split(/\s+/); 52 var filtered = classes.filter(function (c) { 53 return ( 54 allowed.indexOf(c) !== -1 || 55 (c && c.indexOf("wgm-") === 0) || 56 (c && c.indexOf("wpgmap_") === 0) 57 ); 58 }); 59 $el.attr("class", filtered.join(" ")); 60 }); 61 }, 62 63 initSelect2: function () { 64 if ($.fn.select2) { 65 $(".wgm-select2").each(function () { 66 var $this = $(this); 67 if (!$this.hasClass("select2-hidden-accessible")) { 68 $this.select2({ 69 allowClear: true, 70 width: "100%", 71 }); 72 } 73 }); 74 } 75 }, 76 77 initMap: function (id) { 78 var data = this.instances[id]; 79 var self = this; 80 81 var initFn = function () { 82 console.log("WGM: Initializing map " + id); 83 if (typeof google !== "object" || typeof google.maps !== "object") { 84 console.warn("WGM: Google Maps API not ready yet inside initFn."); 85 return; 86 } 87 88 var mapDiv = document.getElementById("srm_gmp_embed_" + id); 89 if (!mapDiv) { 90 console.error("WGM: Map container not found: srm_gmp_embed_" + id); 91 return; 92 } 93 94 console.log( 95 "WGM: Found map container, proceeding with initialization." 96 ); 97 try { 98 var mapOptions = { 99 center: new google.maps.LatLng( 100 data.map_center.lat, 101 data.map_center.lng 102 ), 103 zoom: parseInt(data.map_zoom), 104 mapTypeId: google.maps.MapTypeId[data.map_type], 105 scrollwheel: data.options.disable_mouse_wheel_zoom !== "Y", 106 zoomControl: data.options.zoom_control, 107 zoomControlOptions: { 108 position: 109 google.maps.ControlPosition[data.options.zoom_control_pos], 110 }, 111 mapTypeControl: data.options.map_type_control, 112 mapTypeControlOptions: { 113 position: 114 google.maps.ControlPosition[data.options.map_type_control_pos], 115 }, 116 streetViewControl: data.options.street_view_control, 117 streetViewControlOptions: { 118 position: 119 google.maps.ControlPosition[ 120 data.options.street_view_control_pos 121 ], 122 }, 123 fullscreenControl: data.options.fullscreen_control, 124 fullscreenControlOptions: { 125 position: 126 google.maps.ControlPosition[ 127 data.options.fullscreen_control_pos 128 ], 129 }, 130 rotateControl: data.options.rotate_control, 131 rotateControlOptions: { 132 position: 133 google.maps.ControlPosition[data.options.rotate_control_pos], 134 }, 135 scaleControl: data.options.scale_control, 136 scaleControlOptions: { 137 position: 138 google.maps.ControlPosition[data.options.scale_control_pos], 139 }, 140 draggable: data.options.disable_mouse_dragging !== "Y", 141 disableDoubleClickZoom: 142 data.options.disable_mouse_double_click_zooming === "Y", 143 }; 144 145 var map = new google.maps.Map(mapDiv, mapOptions); 146 data.map = map; 147 window["wgm_map_" + id] = map; // For legacy/theme compatibility 148 149 // Apply Theme 150 if (data.theme_json) { 151 try { 152 map.setOptions({ styles: JSON.parse(data.theme_json) }); 153 } catch (e) { 154 console.error("WGM: Invalid map theme JSON:", e); 155 } 156 } 157
158 // Load Markers 159 self.loadMarkers(id); 160 161 // Initialize Search and Directions once map is ready 162 self.initSearch(id); 163 self.initDirections(id); 164 165 // Trigger hooks for themes or other extensions 166 $(window).trigger("wgm_map_init_" + id, { map: map, data: data }); 167 } catch (e) { 168 console.error("WGM: Error during map initialization:", e); 169 } 170 }; 171 172 if (typeof google === "object" && typeof google.maps === "object") { 173 console.log("WGM: Google Maps API ready, initializing immediately."); 174 initFn(); 175 } else { 176 console.log("WGM: Google Maps API not ready, queuing initialization."); 177 window.wgm_map_queue = window.wgm_map_queue || []; 178 window.wgm_map_queue.push(initFn); 179 } 180 }, 181 182 loadMarkers: function (id) { 183 var data = this.instances[id]; 184 var self = this; 185 186 var ajaxData = { 187 action: "wpgmapembed_p_get_markers_by_map_id", 188 _wgm_p_nonce: data.nonces.marker_render, 189 data: { 190 map_id: data.map_id, 191 }, 192 }; 193 194 $.post(data.ajax_url, ajaxData, function (response) { 195 try { 196 response = 197 typeof response === "string" ? JSON.parse(response) : response; 198 } catch (e) { 199 console.error("WGM: Failed to parse markers response", e); 200 return; 201 } 202 203 if (response.markers && response.markers.length > 0) { 204 if (!window.custom_markers) window.custom_markers = {}; 205 if (!window.custom_marker_infowindows) 206 window.custom_marker_infowindows = {}; 207 window.custom_markers[id] = []; 208 window.custom_marker_infowindows[id] = []; 209 210 // Legacy config global for themes 211 window["wgm_config_" + id] = data.config; 212 213 response.markers.forEach(function (markerData, i) { 214 markerData.marker_idx = i; 215 self.addMarker(id, markerData, i); 216 }); 217 218 // Trigger event for themes to consume markers 219 $(window).trigger("wgm_markers_loaded_" + id, { 220 markers: response.markers, 221 }); 222 223 // Search pre-fill logic if single marker 224 if ( 225 response.markers.length === 1 && 226 data.config.enable_modern_direction 227 ) { 228 var singleAddr = response.markers[0].marker_name || ""; 229 $("#srm_gmap_to_" + id).val( 230 self.escapeHTML(singleAddr.replace(/(<([^>]+)>)/gi, "")) 231 ); 232 } 233 } 234 }).fail(function (xhr, status, error) { 235 console.error("WGM: Failed to load markers: " + error); 236 }); 237 }, 238 239 addMarker: function (id, markerData, index) { 240 var data = this.instances[id]; 241 var self = this; 242 var latlng = (markerData.lat_lng || "0,0").split(","); 243 if (latlng.length < 2) { 244 console.warn("WGM: Invalid coordinates for marker", markerData); 245 return; 246 } 247 248 var anim = null; 249 if (markerData.animation === "BOUNCE") 250 anim = google.maps.Animation.BOUNCE; 251 if (markerData.animation === "DROP") anim = google.maps.Animation.DROP; 252 253 var marker = new google.maps.Marker({ 254 position: new google.maps.LatLng( 255 parseFloat(latlng[0]), 256 parseFloat(latlng[1]) 257 ), 258 title: self.escapeHTML(markerData.marker_name), 259 animation: anim, 260 icon: markerData.icon === "" ? data.default_icon : markerData.icon, 261 visible: !data.config.hide_markers, 262 map: data.map, 263 }); 264 265 marker.wgm_data = markerData; 266 window.custom_markers[id][index] = marker; 267 268 // Infowindow preparation 269 var infowindow = new google.maps.InfoWindow({ 270 content: self.getInfoWindowContent(id, markerData), 271 maxWidth: 320, 272 }); 273 window.custom_marker_infowindows[id][index] = infowindow; 274 275 // Auto-open infowindow if configured 276 if (markerData.show_desc_by_default === "1") { 277 self.closeAllInfoWindows(id); 278 infowindow.open({ anchor: marker, shouldFocus: false }); 279 } 280 281 // Handle marker click behavior 282 if (markerData.have_marker_link === "1") { 283 // If marker has a link, navigate to it on click 284 marker.addListener("click", function () { 285 var target = 286 markerData.marker_link_new_tab === "1" ? "_blank" : "_self"; 287 window.open(markerData.marker_link, target); 288 }); 289 } else { 290 // If no link, always open infowindow on click (regardless of show_desc_by_default) 291 marker.addListener("click", function () { 292 self.closeAllInfoWindows(id); 293 infowindow.open({ anchor: marker, shouldFocus: false }); 294 }); 295 } 296 }, 297 298 getInfoWindowContent: function (id, markerData) { 299 var data = this.instances[id]; 300 var self = this; 301 var hasImg = markerData.marker_image && markerData.marker_image !== ""; 302 if (data.marker_listing_type === "basic_list") hasImg = false; 303 304 var imgHtml = hasImg 305 ? '<div class="wgm-iw-img-wrap"><img src="' + 306 markerData.marker_image + 307 '" class="wgm-infowindow-img wgm-lightbox-trigger" /></div>' 308 : ""; 309 var titleHtml = markerData.marker_name 310 ? '<p class="info_content_title wgm-infowindow-title">' + 311 self.escapeHTML(markerData.marker_name) + 312 "</p>" 313 : ""; 314 315 var dirLink = ""; 316 if (data.config.enable_direction_link) { 317 var dirAddr = (markerData.address || markerData.lat_lng).replace( 318 /'/g, 319 "'" 320 ); 321 dirLink = 322 '<br><a href="#" class="wgm-get-dir-link wgm-dir-link-' + 323 id + 324 '" data-addr="' + 325 dirAddr + 326 '" style="display:inline-block;margin-top:5px;font-size:13px;">' + 327 data.i18n.get_directions + 328 "</a>"; 329 } 330 331 var wrapperClass = 332 "wgm-infowindow-wrapper" + (hasImg ? " wgm-has-img" : " wgm-no-img"); 333 return ( 334 '<div class="' + 335 wrapperClass + 336 '">' + 337 imgHtml + 338 '<div class="wgm-infowindow-content">' + 339 titleHtml + 340 '<div class="wgm-iw-desc">' + 341 markerData.marker_desc + 342 "</div>" + 343 dirLink + 344 "</div>" + 345 "</div>" 346 ); 347 }, 348 349 closeAllInfoWindows: function (id) { 350 if ( 351 window.custom_marker_infowindows && 352 window.custom_marker_infowindows[id] 353 ) {
354 window.custom_marker_infowindows[id].forEach(function (iw) { 355 if (iw) iw.close(); 356 }); 357 } 358 }, 359 360 initSearch: function (id) { 361 var data = this.instances[id]; 362 var self = this; 363 var controlDiv = document.getElementById("wpgmap_search_control_" + id); 364 var mapWrapper = document.getElementById("srm_gmp_embed_" + id); 365 366 if (!controlDiv || !mapWrapper) return; 367 368 // Sanitize classes to remove theme injection 369 self.sanitizeClasses(controlDiv, ["wgm-search-control-container"]); 370 var $cd = $(controlDiv); 371 self.sanitizeClasses($cd.find(".wpgmap_search_box"), [ 372 "wpgmap_search_box", 373 ]); 374 self.sanitizeClasses($cd.find(".wpgmap_search_input"), [ 375 "wpgmap_search_input", 376 ]); 377 self.sanitizeClasses($cd.find(".wpgmap_search_select"), [ 378 "wpgmap_search_select", 379 ]); 380 self.sanitizeClasses($cd.find(".wpgmap_search_btn"), [ 381 "wpgmap_search_btn", 382 ]); 383 self.sanitizeClasses($cd.find(".wpgmap_search_clear"), [ 384 "wpgmap_search_clear", 385 ]); 386 387 // Placement logic 388 if (data.config.placement === "above_map") { 389 $(mapWrapper).before(controlDiv); 390 $(controlDiv).show(); 391 } else if (data.config.placement === "below_map") { 392 $(mapWrapper).after(controlDiv); 393 $(controlDiv).show(); 394 } else { 395 data.map.controls[google.maps.ControlPosition.TOP_CENTER].push( 396 controlDiv 397 ); 398 $(controlDiv).show(); 399 } 400 401 var btn = $("#wpgmap_search_btn_" + id); 402 var locInput = $("#wpgmap_search_loc_" + id); 403 var keyInput = $("#wpgmap_search_key_" + id); 404 var radSelect = $("#wpgmap_search_radius_" + id); 405 406 var catInput = $("#wpgmap_search_cat_" + id); 407 var catPanel = $("#wpgmap_filter_panel_" + id); 408 var catToggle = $("#wpgmap_filter_toggle_" + id); 409 410 var spinner = $("#wpgmap_search_spinner_" + id); 411 var notFoundMsg = $("#wpgmap_not_found_msg_" + id); 412 413 // Bind Close Event Globally for this instance 414 notFoundMsg.find(".wgm-not-found-close").on("click", function (e) { 415 e.preventDefault(); 416 notFoundMsg.stop(true, false).animate({ opacity: 0 }, 500, function () { 417 $(this).removeClass("wgm-show").css("opacity", ""); 418 }); 419 if (window["wgm_not_found_timer_" + id]) { 420 clearTimeout(window["wgm_not_found_timer_" + id]); 421 } 422 }); 423 var searchCircle = null; 424 425 var adjustWidth = function () { 426 var container = $(mapWrapper).closest(".wgm-map-listing-container"); 427 var containerWidth = container.width(); 428 var mapWidth = mapWrapper.offsetWidth; 429 430 // Adjust input visibility based on map width 431 var container = $(mapWrapper).closest(".wgm-map-listing-container"); 432 var containerWidth = container.width(); 433 var mapWidth = mapWrapper.offsetWidth; 434 435 // Container-Aware Stacking Logic 436 if (containerWidth < 600) { 437 container.addClass("wgm-force-stack"); 438 } else { 439 container.removeClass("wgm-force-stack"); 440 } 441 442 if (mapWidth < 480) { 443 if (keyInput.length) keyInput.hide(); 444 } else { 445 if (keyInput.length) keyInput.show(); 446 } 447 if (mapWidth < 380) { 448 if (radSelect.length) radSelect.hide(); 449 if (catToggle.length) catToggle.hide(); 450 } else { 451 if (radSelect.length) radSelect.show(); 452 if (catToggle.length) catToggle.show(); 453 } 454 }; 455 456 adjustWidth(); 457 458 // Use ResizeObserver if available for better performance and theme compatibility 459 if (typeof ResizeObserver !== "undefined") { 460 var ro = new ResizeObserver(function () { 461 adjustWidth(); 462 }); 463 ro.observe(mapWrapper); 464 } else { 465 $(window).on("resize", adjustWidth); 466 } 467 468 google.maps.event.addListenerOnce(data.map, "idle", adjustWidth); 469 470 if (locInput.length && typeof google.maps.places !== "undefined") { 471 var autocomplete = new google.maps.places.Autocomplete(locInput[0]); 472 autocomplete.bindTo("bounds", data.map); 473 } 474 475 var getDistance = function (lat1, lon1, lat2, lon2) { 476 var R = 6371; // km 477 var dLat = ((lat2 - lat1) * Math.PI) / 180; 478 var dLon = ((lon2 - lon1) * Math.PI) / 180; 479 var a = 480 Math.sin(dLat / 2) * Math.sin(dLat / 2) + 481 Math.cos((lat1 * Math.PI) / 180) * 482 Math.cos((lat2 * Math.PI) / 180) * 483 Math.sin(dLon / 2) * 484 Math.sin(dLon / 2); 485 return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 486 }; 487 488 var updateMapAndList = function ( 489 centerLat, 490 centerLng, 491 radius, 492 keyword, 493 catId 494 ) { 495 var markers = window.custom_markers[id] || []; 496 var filteredData = []; 497 var bounds = new google.maps.LatLngBounds(); 498 var hasVisible = false; 499 var hasLocation = centerLat !== null && centerLng !== null; 500 501 if (searchCircle) searchCircle.setMap(null); 502 if (window["wgm_center_marker_" + id]) 503 window["wgm_center_marker_" + id].setMap(null); 504 505 if (hasLocation) { 506 if (radius) { 507 searchCircle = new google.maps.Circle({ 508 strokeColor: data.circle.stroke_color, 509 strokeOpacity: data.circle.stroke_opacity, 510 strokeWeight: data.circle.stroke_weight, 511 fillColor: data.circle.fill_color, 512 fillOpacity: data.circle.fill_opacity, 513 map: data.map, 514 center: { lat: centerLat, lng: centerLng }, 515 radius: radius * 1000, 516 }); 517 } 518 if (data.config.show_center_icon) { 519 window["wgm_center_marker_" + id] = new google.maps.Marker({ 520 position: { lat: centerLat, lng: centerLng }, 521 map: data.map, 522 title: "Search Center", 523 icon: "https://maps.google.com/mapfiles/ms/icons/blue-dot.png", 524 }); 525 } 526 } 527
528 markers.forEach(function (m, i) { 529 var d = Object.assign({}, m.wgm_data); 530 d.marker_idx = i; 531 var isVisible = true; 532 var distVal = 0; 533 534 if (hasLocation) { 535 var mLatLng = m.getPosition(); 536 var distKm = getDistance( 537 centerLat, 538 centerLng, 539 mLatLng.lat(), 540 mLatLng.lng() 541 ); 542 if (radius && distKm > radius) isVisible = false; 543 544 if (isVisible && data.config.show_distance) { 545 distVal = 546 data.config.distance_unit === "miles" 547 ? distKm * 0.621371 548 : distKm; 549 var unit = data.config.distance_unit === "miles" ? "miles" : "km"; 550 d.distance = distVal.toFixed(2) + " " + unit; 551 d.distance_html = 552 '<span class="wgm-distance-badge">' + d.distance + "</span>"; 553 } 554 } 555 556 if (isVisible && keyword) { 557 var hay = ( 558 (d.marker_name || "") + 559 " " + 560 (d.marker_desc || "") + 561 " " + 562 (d.address || "") 563 ).toLowerCase(); 564 if (hay.indexOf(keyword) === -1) isVisible = false; 565 } 566 567 if (isVisible && catId && catId.length > 0) { 568 var markerCats = d.category_id 569 ? d.category_id.toString().split(",") 570 : []; 571 var selectedCats = Array.isArray(catId) 572 ? catId 573 : [catId.toString()]; 574 var logic = data.config.category_selection_logic || "OR"; 575 576 if (logic === "AND") { 577 // Show if marker has ALL selected categories 578 for (var j = 0; j < selectedCats.length; j++) { 579 if (markerCats.indexOf(selectedCats[j].toString()) === -1) { 580 isVisible = false; 581 break; 582 } 583 } 584 } else { 585 // OR logic: Show if marker has ANY of the selected categories 586 var found = false; 587 for (var k = 0; k < selectedCats.length; k++) { 588 if (markerCats.indexOf(selectedCats[k].toString()) !== -1) { 589 found = true; 590 break; 591 } 592 } 593 if (!found) isVisible = false; 594 } 595 } 596 597 m.setVisible(isVisible); 598 if (isVisible) { 599 filteredData.push(d); 600 bounds.extend(m.getPosition()); 601 hasVisible = true; 602 } 603 }); 604 605 if (hasLocation && data.config.sort_by_distance) { 606 filteredData.sort(function (a, b) { 607 return ( 608 (parseFloat(a.distance) || 0) - (parseFloat(b.distance) || 0) 609 ); 610 }); 611 } 612 613 $(window).trigger("wgm_search_update_" + id, { markers: filteredData }); 614 615 if (hasVisible) { 616 notFoundMsg.removeClass("wgm-show"); 617 if (hasLocation && radius) { 618 data.map.fitBounds(searchCircle.getBounds()); 619 } else { 620 data.map.fitBounds(bounds); 621 if (data.map.getZoom() > 15) data.map.setZoom(15); 622 } 623 } else { 624 notFoundMsg.addClass("wgm-show"); 625 626 // Clear any existing timeout 627 if (window["wgm_not_found_timer_" + id]) { 628 clearTimeout(window["wgm_not_found_timer_" + id]); 629 } 630 631 // Set 8s auto-hide timer 632 window["wgm_not_found_timer_" + id] = setTimeout(function () { 633 notFoundMsg 634 .stop(true, false) 635 .animate({ opacity: 0 }, 500, function () { 636 $(this).removeClass("wgm-show").css("opacity", ""); 637 }); 638 }, 8000); 639 640 if (hasLocation) { 641 data.map.setCenter({ lat: centerLat, lng: centerLng }); 642 data.map.setZoom(10); 643 } 644 } 645 }; 646 647 var executeSearch = function () { 648 var loc = locInput.length ? locInput.val().trim() : ""; 649 var key = keyInput.length ? keyInput.val().trim().toLowerCase() : ""; 650 var rad = 651 radSelect.length && radSelect.val() 652 ? parseFloat(radSelect.val()) 653 : null; 654 var cat = 655 catInput.length && catInput.val()
656 ? catInput.val().split(",") // Pass array of strings 657 : null; 658 659 if (loc) { 660 spinner.show(); 661 var geocoder = new google.maps.Geocoder(); 662 geocoder.geocode({ address: loc }, function (results, status) { 663 spinner.hide(); 664 if (status === "OK") { 665 var pos = results[0].geometry.location; 666 updateMapAndList(pos.lat(), pos.lng(), rad, key, cat); 667 } else { 668 alert("Geocode was not successful: " + status); 669 } 670 }); 671 } else { 672 updateMapAndList(null, null, null, key, cat); 673 } 674 }; 675 676 btn.on("click", executeSearch); 677 678 // Clear button functionality 679 var clearBtn = $("#wpgmap_search_clear_" + id); 680 681 // Show/hide clear button based on input values 682 var toggleClearBtn = function () { 683 var hasLocation = locInput.length && locInput.val().trim() !== ""; 684 var hasKeyword = keyInput.length && keyInput.val().trim() !== ""; 685 var hasRadius = radSelect.length && radSelect.val() !== ""; 686 var hasCategory = catInput.length && catInput.val() !== ""; 687 688 if (hasLocation || hasKeyword || hasRadius || hasCategory) { 689 clearBtn.show(); 690 } else { 691 clearBtn.hide(); 692 } 693 }; 694 695 // Monitor input changes 696 if (locInput.length) locInput.on("input", toggleClearBtn); 697 if (keyInput.length) keyInput.on("input", toggleClearBtn); 698 if (radSelect.length) radSelect.on("change", toggleClearBtn); 699 if (catInput.length) catInput.on("change", toggleClearBtn); 700 701 // Check initial values (for pre-loaded defaults) 702 toggleClearBtn(); 703 704 // Clear button click handler 705 clearBtn.on("click", function () { 706 // Clear all inputs 707 if (locInput.length) locInput.val(""); 708 if (keyInput.length) keyInput.val(""); 709 if (radSelect.length) radSelect.val(""); 710 if (catInput.length) catInput.val(""); 711 if (catPanel.length) 712 catPanel.find(".wgm-front-cat-checkbox").prop("checked", false); 713 714 // Hide clear button 715 clearBtn.hide(); 716 717 // Remove search circle and center marker 718 if (searchCircle) searchCircle.setMap(null); 719 if (window["wgm_center_marker_" + id]) 720 window["wgm_center_marker_" + id].setMap(null); 721 722 // Reset all markers to visible 723 var markers = window.custom_markers[id] || []; 724 markers.forEach(function (m) { 725 m.setVisible(true); 726 }); 727 728 // Hide not found message 729 notFoundMsg.removeClass("wgm-show"); 730 731 // Trigger listing update with all markers 732 var allMarkers = markers.map(function (m, i) { 733 var d = Object.assign({}, m.wgm_data); 734 d.marker_idx = i; 735 return d; 736 }); 737 $(window).trigger("wgm_search_update_" + id, { markers: allMarkers }); 738 739 // Fit bounds to show all markers 740 if (markers.length > 0) { 741 var bounds = new google.maps.LatLngBounds(); 742 markers.forEach(function (m) { 743 bounds.extend(m.getPosition()); 744 }); 745 data.map.fitBounds(bounds); 746 } 747 }); 748 749 var triggerSearch = function (e) { 750 if (e.key === "Enter") executeSearch(); 751 }; 752 locInput.on("keydown", triggerSearch); 753 if (keyInput.length) keyInput.on("keydown", triggerSearch); 754 755 // Category Tree Builder 756 if (data.categories && catPanel.length) { 757 var buildCategoryTree = function (parentId) { 758 var html = ""; 759 var children = data.categories.filter(function (cat) { 760 return cat.parent_id == parentId; 761 }); 762 763 if (children.length > 0) { 764 if (parentId != 0) { 765 html += '<ul class="wgm-filter-sublist">'; 766 } 767 children.forEach(function (cat) { 768 html += '<li class="wgm-filter-item">'; 769 html += "<label>"; 770 html += 771 '<input type="checkbox" class="wgm-front-cat-checkbox" value="' + 772 self.escapeHTML(cat.id) + 773 '"> '; 774 html += 775 '<span class="wgm-cat-name">' + 776 self.escapeHTML(cat.name) + 777 "</span>"; 778 html += "</label>"; 779 html += buildCategoryTree(cat.id); 780 html += "</li>"; 781 }); 782 if (parentId != 0) { 783 html += "</ul>"; 784 } 785 } 786 return html; 787 }; 788 789 var listHtml = 790 '<div class="wgm-filter-header">' + 791 '<span class="wgm-filter-close" title="Close">×</span>' + 792 "</div>" + 793 '<ul class="wgm-filter-list">' + 794 buildCategoryTree(0) + 795 "</ul>"; 796 797 if (data.categories.length === 0) 798 listHtml = '<p class="wgm-no-cats">No categories</p>'; 799 catPanel.html(listHtml); 800 801 // Toggle Panel 802 catToggle.on("click", function (e) { 803 e.preventDefault(); 804 e.stopPropagation(); 805 catPanel.toggleClass("hidden"); 806 }); 807 808 // Prevent closing when clicking inside panel 809 catPanel.on("click", function (e) { 810 e.stopPropagation(); 811 }); 812 813 // Close Icon Click 814 catPanel.on("click", ".wgm-filter-close", function (e) { 815 e.preventDefault(); 816 e.stopPropagation(); 817 catPanel.addClass("hidden"); 818 }); 819 820 // Close on click outside 821 // Prevent closing when clicking on labels/inputs specifically (extra safety) 822 catPanel.on("click", "label, input", function (e) { 823 e.stopPropagation(); 824 }); 825 826 // Close on click outside 827 $(document).on("click", function (e) { 828 if ( 829 $(e.target).closest("#wpgmap_filter_panel_" + id).length === 0 && 830 $(e.target).closest("#wpgmap_filter_toggle_" + id).length === 0 831 ) { 832 catPanel.addClass("hidden"); 833 } 834 }); 835 836 // Handle Checkbox Change ->
836 Update Hidden Input -> Trigger Search 837 catPanel.on("change", ".wgm-front-cat-checkbox", function () { 838 var $this = $(this); 839 var isChecked = $this.prop("checked"); 840 var val = $this.val(); 841 842 // Cascading Logic: Check/Uncheck all children 843 // 1. Find the parent <li> of the changed checkbox 844 var $parentLi = $this.closest("li"); 845 // 2. Find any nested <ul> which contains children 846 var $childrenList = $parentLi.find("ul"); 847 // 3. Find all checkboxes within that list and update them 848 if ($childrenList.length) { 849 $childrenList 850 .find(".wgm-front-cat-checkbox") 851 .prop("checked", isChecked); 852 } 853 854 var selectedIds = []; 855 catPanel.find(".wgm-front-cat-checkbox:checked").each(function () { 856 selectedIds.push($(this).val()); 857 }); 858 catInput.val(selectedIds.join(",")); 859 executeSearch(); // Trigger search/filter 860 }); 861 } 862 863 // Check initial values (for pre-loaded defaults) 864 if (catInput.length && catInput.val()) { 865 var preselected = catInput.val().split(","); 866 preselected.forEach(function (pid) { 867 catPanel 868 .find('.wgm-front-cat-checkbox[value="' + pid + '"]') 869 .prop("checked", true); 870 }); 871 executeSearch(); 872 } 873 }, 874 875 initDirections: function (id) { 876 var data = this.instances[id]; 877 var self = this; 878 var drawer = $("#wgm_direction_drawer_" + id); 879 var legacyFromInput = $("#srm_gmap_from_" + id); 880 var legacyToInput = $("#srm_gmap_to_" + id); 881 882 // Define the open directions function for both modern drawer and legacy box 883 window["wgm_open_directions_" + id] = function (destination) { 884 if (drawer.length) { 885 // Modern drawer exists 886 var toInput = $("#wgm_dir_to_" + id); 887 var fromInput = $("#wgm_dir_from_" + id); 888 var toggleBtn = $("#wgm_drawer_toggle_" + id); 889 toInput.val(destination); 890 drawer.addClass("active"); 891 toggleBtn.fadeOut(200); 892 fromInput.focus(); 893 } else if (legacyToInput.length) { 894 // Legacy direction box exists 895 legacyToInput.val(destination); 896 legacyFromInput.focus(); 897 // Scroll to the legacy direction box 898 $("html, body").animate( 899 { 900 scrollTop: 901 legacyToInput.closest(".wgm-legacy-direction-box").offset() 902 .top - 20, 903 }, 904 500 905 ); 906 } 907 }; 908 909 // Delegate events from infowindow and listing 910 $(document).on("click", ".wgm-dir-link-" + id, function (e) { 911 e.preventDefault(); 912 self.closeAllInfoWindows(id); 913 window["wgm_open_directions_" + id]($(this).data("addr")); 914 }); 915 916 $(document).on( 917 "click", 918 "#wgm_listing_area_" + id + " .wgm-get-direction-btn", 919 function (e) { 920 e.preventDefault(); 921 var idx = $(this).data("idx"); 922 var m = window.custom_markers[id][idx]; 923 if (m) 924 window["wgm_open_directions_" + id]( 925 m.wgm_data.address || m.wgm_data.lat_lng 926 ); 927 } 928 ); 929 930 // Modern drawer initialization 931 if (!drawer.length) { 932 // No modern drawer, skip to legacy directions 933 this.initLegacyDirections(id); 934 return; 935 } 936 937 var toggleBtn = $("#wgm_drawer_toggle_" + id); 938 var fromInput = $("#wgm_dir_from_" + id); 939 var toInput = $("#wgm_dir_to_" + id); 940 var goBtn = drawer.find(".wgm-dir-go-btn");
941 var travelMode = "DRIVING"; 942 var resultsPanel = $("#wgm_dir_results_" + id); 943 944 var directionsService = new google.maps.DirectionsService(); 945 var directionsRenderer = new google.maps.DirectionsRenderer({ 946 map: data.map, 947 suppressMarkers: false, 948 panel: resultsPanel[0], 949 }); 950 951 toggleBtn.on("click", function () { 952 drawer.addClass("active"); 953 toggleBtn.fadeOut(200); 954 }); 955 drawer.find(".wgm-drawer-close").on("click", function () { 956 drawer.removeClass("active"); 957 toggleBtn.fadeIn(200); 958 directionsRenderer.setDirections({ routes: [] }); 959 resultsPanel.empty().removeClass("active"); 960 }); 961 962 if (typeof google.maps.places !== "undefined") { 963 new google.maps.places.Autocomplete(fromInput[0]).bindTo( 964 "bounds", 965 data.map 966 ); 967 new google.maps.places.Autocomplete(toInput[0]).bindTo( 968 "bounds", 969 data.map 970 ); 971 } 972 973 drawer.find(".wgm-travel-btn").on("click", function () { 974 drawer.find(".wgm-travel-btn").removeClass("active"); 975 $(this).addClass("active"); 976 travelMode = $(this).data("mode"); 977 }); 978 979 drawer.find(".wgm-dir-adv-toggle").on("click", function () { 980 drawer.find(".wgm-dir-adv-options").toggleClass("active"); 981 }); 982 983 var waypointsContainer = $("#wgm_waypoints_container_" + id); 984 $("#wgm_add_waypoint_" + id).on("click", function () { 985 var wpId = "wgm_wp_" + Date.now(); 986 var html = 987 '<div class="wgm-dir-input-group wgm-waypoint-item" id="' + 988 wpId + 989 '">' + 990 '<div class="wgm-dir-field-wrap">' + 991 '<input type="text" class="wgm-dir-input wgm-waypoint-input" placeholder="' + 992 data.i18n.via + 993 '">' + 994 '<button type="button" class="wgm-remove-waypoint-btn" title="' + 995 data.i18n.remove_waypoint + 996 '">' + 997 '<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"></path></svg>' + 998 "</button>" + 999 "</div>" + 1000 "</div>"; 1001 waypointsContainer.append(html); 1002 if (typeof google.maps.places !== "undefined") { 1003 new google.maps.places.Autocomplete( 1004 waypointsContainer.find("#" + wpId + " input")[0] 1005 ).bindTo("bounds", data.map); 1006 } 1007 $("#" + wpId + " .wgm-remove-waypoint-btn").on("click", function () { 1008 $("#" + wpId).remove(); 1009 }); 1010 }); 1011 1012 drawer.find(".wgm-current-loc-btn").on("click", function () { 1013 var btn = $(this); 1014 var targetInput = btn.siblings("input"); 1015 if (navigator.geolocation) { 1016 btn.css("opacity", "0.5"); 1017 navigator.geolocation.getCurrentPosition( 1018 function (position) { 1019 btn.css("opacity", "1"); 1020 var geocoder = new google.maps.Geocoder(); 1021 geocoder.geocode( 1022 { 1023 location: { 1024 lat: position.coords.latitude, 1025 lng: position.coords.longitude, 1026 }, 1027 }, 1028 function (results, status) { 1029 if (status === "OK" && results[0]) 1030 targetInput.val(results[0].formatted_address); 1031 else 1032 targetInput.val( 1033 position.coords.latitude + "," + position.coords.longitude 1034 ); 1035 } 1036 ); 1037 }, 1038 function (error) { 1039 btn.css("opacity", "1"); 1040 console.error("Geolocation error:", error); 1041 alert( 1042 (data.i18n.geo_fail || "Unable to get your location") + 1043 ": " + 1044 error.message 1045 ); 1046 } 1047 ); 1048 } else { 1049 alert(data.i18n.geo_not_supported); 1050 } 1051 }); 1052 1053 goBtn.on("click", function () { 1054 var origin = fromInput.val().trim(); 1055 var destination = toInput.val().trim(); 1056 var waypoints = []; 1057 drawer.find(".wgm-waypoint-input").each(function () { 1058 var val = $(this).val().trim(); 1059 if (val) waypoints.push({ location: val, stopover: true });
1060 }); 1061 1062 if (!origin || !destination) { 1063 alert(data.i18n.origin_dest_required); 1064 return; 1065 } 1066 1067 var request = { 1068 origin: origin, 1069 destination: destination, 1070 waypoints: waypoints, 1071 optimizeWaypoints: true, 1072 travelMode: google.maps.TravelMode[travelMode], 1073 avoidTolls: drawer.find(".wgm-avoid-tolls").is(":checked"), 1074 avoidHighways: drawer.find(".wgm-avoid-highways").is(":checked"), 1075 avoidFerries: drawer.find(".wgm-avoid-ferries").is(":checked"), 1076 }; 1077 1078 directionsService.route(request, function (result, status) { 1079 if (status == "OK") { 1080 directionsRenderer.setDirections(result); 1081 resultsPanel.addClass("active"); 1082 } else { 1083 alert("Directions request failed: " + status); 1084 } 1085 }); 1086 }); 1087 1088 this.initLegacyDirections(id); 1089 }, 1090 1091 initLegacyDirections: function (id) { 1092 var data = this.instances[id]; 1093 var legacySubmit = $("#wp_gmap_submit_" + id); 1094 var directionsService = new google.maps.DirectionsService(); 1095 1096 if (legacySubmit.length) { 1097 var legacyRenderer = new google.maps.DirectionsRenderer({ 1098 map: data.map, 1099 panel: document.getElementById("wp_gmap_directions_" + id), 1100 }); 1101 1102 // Initialize Google Places Autocomplete on inputs (if Places API is available) 1103 var fromInput = document.getElementById("srm_gmap_from_" + id); 1104 var toInput = document.getElementById("srm_gmap_to_" + id); 1105 1106 // Enable autocomplete on legacy direction inputs 1107 if ( 1108 typeof google.maps.places !== "undefined" && 1109 data.options.enable_direction_form_auto_complete 1110 ) { 1111 if (fromInput) { 1112 var fromAutocomplete = new google.maps.places.Autocomplete( 1113 fromInput, 1114 { 1115 fields: ["formatted_address", "geometry", "name"], 1116 } 1117 ); 1118 fromAutocomplete.bindTo("bounds", data.map); 1119 } 1120 1121 if (toInput) { 1122 var toAutocomplete = new google.maps.places.Autocomplete(toInput, { 1123 fields: ["formatted_address", "geometry", "name"], 1124 }); 1125 toAutocomplete.bindTo("bounds", data.map); 1126 } 1127 } 1128 1129 // Travel mode switcher for legacy box 1130 $(".wgm-legacy-travel-btn").on("click", function () { 1131 $(".wgm-legacy-travel-btn").removeClass("active"); 1132 $(this).addClass("active"); 1133 var mode = $(this).data("mode"); 1134 $("#srm_gmap_mode_" + id).val(mode); 1135 }); 1136 1137 // Location buttons for legacy box 1138 $(".wgm-legacy-loc-btn").on("click", function () { 1139 var btn = $(this); 1140 var targetInput = btn.siblings("input"); 1141 if (navigator.geolocation) { 1142 btn.css("opacity", "0.5"); 1143 navigator.geolocation.getCurrentPosition( 1144 function (position) { 1145 btn.css("opacity", "1"); 1146 var geocoder = new google.maps.Geocoder(); 1147 geocoder.geocode( 1148 { 1149 location: { 1150 lat: position.coords.latitude, 1151 lng: position.coords.longitude, 1152 }, 1153 }, 1154 function (results, status) { 1155 if (status === "OK" && results[0]) 1156 targetInput.val(results[0].formatted_address); 1157 else 1158 targetInput.val( 1159 position.coords.latitude + 1160 "," + 1161 position.coords.longitude 1162 ); 1163 } 1164 ); 1165 }, 1166 function (error) { 1167 btn.css("opacity", "1"); 1168 console.error("Geolocation error:", error); 1169 alert( 1170 (data.i18n.geo_fail || "Unable to get your location") + 1171 ": " + 1172 error.message 1173 ); 1174 } 1175 ); 1176 } else { 1177 alert( 1178 data.i18n.geo_not_supported || "Geolocation is not supported" 1179 ); 1180 } 1181 }); 1182 1183 legacySubmit.on("click", function () { 1184 var mode = $("#srm_gmap_mode_" + id).val(), 1185 from = $("#srm_gmap_from_" + id).val(), 1186 to = $("#srm_gmap_to_" + id).val(); 1187 if (!from || !to) { 1188 $("#wp_gmap_results_" + id).hide(); 1189 return; 1190 } 1191 $("#wp_gmap_loading_" + id).addClass("wgm-show"); 1192 directionsService.route( 1193 { 1194 origin: from, 1195 destination: to, 1196 travelMode: google.maps.DirectionsTravelMode[mode], 1197 unitSystem: 1198 data.distance_unit_system === "METRIC" 1199 ? google.maps.UnitSystem.METRIC 1200 : google.maps.UnitSystem.IMPERIAL, 1201 }, 1202 function (response, status) { 1203 $("#wp_gmap_loading_" + id).removeClass("wgm-show"); 1204 if (status === "OK") { 1205 legacyRenderer.setDirections(response); 1206 $("#wp_gmap_results_" + id).show(); 1207 } else { 1208 $("#wp_gmap_results_" + id).hide(); 1209 alert("Directions request failed: " + status); 1210 } 1211 } 1212 ); 1213 }); 1214 } 1215 }, 1216 1217 initLightbox: function (id) { 1218 var data = this.instances[id];
1219 var overlay = $("#wgm-lightbox-overlay_" + id); 1220 var img = $("#wgm-lightbox-img_" + id); 1221 if (!overlay.length) return; 1222 1223 // Scope to this instance's container to prevent conflicts with multiple maps 1224 var container = $(".wgm-map-id-" + data.map_id).eq( 1225 $(".wgm-map-id-" + data.map_id).index( 1226 $("#srm_gmp_embed_" + id).closest(".wgm-map-id-" + data.map_id) 1227 ) 1228 ); 1229 1230 // Use instance-specific container instead of document 1231 container.on("click", ".wgm-lightbox-trigger", function (e) { 1232 e.stopPropagation(); 1233 img.attr("src", $(this).attr("src")); 1234 overlay.addClass("show"); 1235 }); 1236 1237 overlay.on("click", function (e) { 1238 if (e.target !== img[0]) overlay.removeClass("show"); 1239 }); 1240 overlay.find(".wgm-lightbox-close").on("click", function () { 1241 overlay.removeClass("show"); 1242 }); 1243 $(document).on("keydown", function (e) { 1244 if (e.key === "Escape") overlay.removeClass("show"); 1245 }); 1246 }, 1247 }; 1248})(jQuery);
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.