/**
* @namespace WPGMZA
* @module AtlasMajorLivePreviewPro
* @requires WPGMZA.AtlasMajorLivePreview
*
* Pro extension for the Atlas Major live preview.
* Adds: Directions Box, Marker Listings, Category Filter, Category Legends.
*
* Uses the same JS classes as the frontend — no code duplication.
* Components are initialized by calling the real Pro init methods after
* placing the correct DOM elements into the preview.
*/
jQuery(function($) {
if(!document.querySelector('.wpgmza-atlas-major'))
return;
/* PHP class name for each marker listing style */
var LISTING_STYLE_CLASS = {
1: 'WPGMZA\\MarkerListing\\BasicTable',
2: 'WPGMZA\\MarkerListing\\AdvancedTable',
3: 'WPGMZA\\MarkerListing\\Carousel',
4: 'WPGMZA\\MarkerListing\\BasicList',
6: 'WPGMZA\\MarkerListing\\Modern',
7: 'WPGMZA\\MarkerListing\\Grid',
8: 'WPGMZA\\MarkerListing\\Panel',
9: 'WPGMZA\\MarkerListing\\CategoryGroupedList',
10: 'WPGMZA\\MarkerListing\\CategoryGroupedTabs'
};
/* CSS classes that PHP adds to the listing element per style */
var LISTING_STYLE_EXTRA_CLASSES = {
3: 'owl-carousel owl-theme wpgmza_marker_carousel',
7: 'wpgmza-marker-grid'
};
function initProLivePreview(){
var lp = WPGMZA.atlasMajorLivePreview;
if(!lp){
return;
}
var map = lp.getMap();
if(!map){
return;
}
var templates = lp.getTemplatesContainer();
var mapId = map.id;
/* ========================================================
INJECT PRO COMPONENT TEMPLATES
======================================================== */
/* Directions Box — real PHP template injected by class.pro-map-edit-page.php */
/* Build the ajax-parameters JSON for the marker listing. Starts with
the required map_id and layers on the live-preview override params
(order_markers_by/choice) so the server sorts per the live form
values instead of the saved map record. Server reads the overrides
in class.marker-listing.php filterOrderBy/Direction and
class.advanced-table.php getOrderBy/getOrderDirection. */
function buildMarkerListingAjaxParams(){
var params = {map_id: String(mapId)};
var orderBy = $('select[name="order_markers_by"]').val();
var orderDir = $('select[name="order_markers_choice"]').val();
if(orderBy) params.override_order_by = orderBy;
if(orderDir) params.override_order_dir = orderDir;
/* Always force-render the image-placeholder element into the
preview's listing rows. Its visibility is then toggled live
via the .wpgmza-image-placeholders-off class (see
syncImagePlaceholderPreview) when the "Image Placeholders"
dropdown changes, with no re-fetch needed. */
params.image_placeholder_preview = '1';
return params;
}
/* Marker Listing — container with AJAX attributes.
Rebuilt each render because the listing style (and thus PHP class) can change. */
function buildMarkerListingTemplate(){
templates.find('[data-preview-component="marker-listing"]').remove();
var listStyle = parseInt($('input[name="wpgmza_listmarkers_by"]:checked').val()) || 0;
var phpClass = LISTING_STYLE_CLASS[listStyle] || 'WPGMZA\\MarkerListing\\BasicList';
var extraClasses = LISTING_STYLE_EXTRA_CLASSES[listStyle] || '';
var ajaxParams = buildMarkerListingAjaxParams();
/* Advanced Table: use PHP-rendered table if available, fallback to JS-built */
if(listStyle === 2){
var advTableSrc = templates.find('[data-preview-advanced-table]');
if(advTableSrc.length && advTableSrc.children().length){
var mlDiv = $('
');
var clone = advTableSrc.children().first().clone();
/* Overwrite the PHP-rendered ajax-parameters so the override
params are included on the live-preview clone. */
clone.find('[data-wpgmza-ajax-parameters]').addBack('[data-wpgmza-ajax-parameters]')
.attr('data-wpgmza-ajax-parameters', JSON.stringify(ajaxParams));
mlDiv.append(clone);
templates.append(mlDiv);
return;
}
/* Fallback: build a basic table structure */
var mlDiv = $('');
var mlInner = $('');
mlInner.attr('data-wpgmza-marker-listing', 'true');
mlInner.attr('id', 'wpgmza_marker_list_' + mapId);
mlInner.attr('data-map-id', mapId);
mlInner.attr('data-wpgmza-php-class', phpClass);
mlInner.attr('data-wpgmza-ajax-parameters', JSON.stringify(ajaxParams));
mlInner.attr('data-wpgmza-rest-api-route', '/datatables/');
mlInner.attr('data-wpgmza-datatable', 'true');
mlInner.attr('data-wpgmza-table', mapId);
mlInner.addClass('wpgmza_marker_list_class wpgmza_marker_holder');
var tableHtml = '
';
mlInner.append(tableHtml);
mlDiv.append(mlInner);
templates.append(mlDiv);
return;
}
var mlDiv = $('');
var mlInner = $('');
mlInner.attr('data-wpgmza-marker-listing', 'true');
mlInner.attr('id', 'wpgmza_marker_list_' + mapId);
mlInner.attr('data-map-id', mapId);
mlInner.attr('data-wpgmza-php-class', phpClass);
mlInner.attr('data-wpgmza-ajax-parameters', JSON.stringify(ajaxParams));
mlInner.attr('data-wpgmza-rest-api-route', '/marker-listing/');
/* Add the base class that PHP always adds + any style-specific classes */
mlInner.addClass('wpgmza_marker_list_class');
if(extraClasses){
mlInner.addClass(extraClasses);
}
mlDiv.append(mlInner);
templates.append(mlDiv);
}
buildMarkerListingTemplate();
/* Rebuild template when listing style changes */
$(document.body).on('change', 'input[name="wpgmza_listmarkers_by"]', function(){
buildMarkerListingTemplate();
});
/* Category Filter — PHP-rendered template injected by
class.pro-map-edit-page.php using CategoryFilterWidget.
Respects the global filterbycat_type setting (dropdown vs checkboxes). */
/* Category Legends — built from live category tree data */
if(!templates.find('[data-preview-component="category-legends"]').length){
var legendsHtml = buildCategoryLegendsHtml(mapId);
templates.append(
'
' +
legendsHtml +
'
'
);
}
/* MarkerListing looks up `.wpgmza-marker-listing-category-filter[data-map-id="X"]`
globally. The PHP-rendered filter-widget template also has data-map-id, so it
would match alongside any live clone and cause duplicate-handler wiring. Strip
data-map-id from the template source once; each onInit re-adds it to the clone. */
templates.find('[data-preview-component="category-filter"] .wpgmza-marker-listing-category-filter').removeAttr('data-map-id');
/* ========================================================
REGISTER PRO COMPONENTS
======================================================== */
lp.registerComponent('directions-box', {
enabledCheck: function(){ return $('input[name="directions_enabled"]').is(':checked'); },
anchorField: 'select[name="directions_box_component_anchor"]',
defaultAnchor: 1,
proOnly: true,
onInit: function(map, clone){
map.directionsEnabled = true;
map.settings.directions_enabled = "1";
if(map.directionsBox) map.directionsBox = null;
/* Set the ID that DirectionsBox constructor looks for */
clone.find('.wpgmza-directions-box').addBack('.wpgmza-directions-box')
.attr('id', 'wpgmaps_directions_edit_' + map.id);
/* For left/right anchors, wrap in viewport-grouping structure */
var anchor = lp.getComponentAnchor('directions-box');
var anchorName = {0:'top',1:'left',2:'center',3:'right',4:'bottom',5:'top_left',6:'top_right',7:'bottom_left',8:'bottom_right'}[anchor] || 'left';
if(anchorName === 'left' || anchorName === 'right'){
var mapEl = $(map.element);
var stack = mapEl.children('.wpgmza-inner-stack.' + anchorName);
/* Remove the directly-placed clone from base render */
clone.detach();
if(!stack.length){
stack = $('');
mapEl.append(stack);
WPGMZA.AtlasMajorLivePreview.registerInnerStack(stack);
}
/* Check if open by default (dbox setting: 1=No, 6=Yes).
Also support live form state — the dropdown may change
before map.settings is synced */
var dboxVal = $('select[name="dbox"]').val() || map.settings.dbox;
var openByDefault = (!map.settings.dbox_open_external && parseInt(dboxVal) === WPGMZA.DirectionsBox.OPEN_BY_DEFAULT);
var visibleClass = openByDefault ? ' visible' : '';
stack.addClass('viewport-grouping');
if(openByDefault) stack.addClass('expanded');
var grouping = $('');
var groupingItem = $('');
var groupingHandle = $('
');
groupingItem.append(clone);
grouping.append(groupingItem);
stack.append(grouping);
stack.append(groupingHandle);
stack.css('display', 'flex');
if(map.viewportGroupings){
/* Unbind any existing click handlers on grouping-handles
before re-init, or initGroups stacks duplicate handlers */
$(map.element).find('.grouping-handle').off('click');
map.viewportGroupings.initGroups();
}
}
/* Set IDs that the DirectionsBox JS expects — the PHP DirectionsBox
class normally sets these, but the live preview template is loaded
via raw loadPHPFile without going through the class constructor */
clone.find('.wpgmza-directions-notifications').attr('id', 'wpgmaps_directions_notification_' + map.id);
clone.find('.wpgmza-directions-output-panel').attr('id', 'directions_panel_' + map.id);
var origAdmin = WPGMZA.is_admin;
WPGMZA.is_admin = "0";
try {
WPGMZA.ProMap.prototype.initDirectionsBox.call(map, clone[0]);
} catch(e){
console.warn('LP-PRO: initDirectionsBox failed', e);
}
WPGMZA.is_admin = origAdmin;
},
onTeardown: function(map){
if(map.directionsBox){
map.directionsBox = null;
}
map.directionsEnabled = false;
/* Remove the grouping wrapper */
$('.wpgmza-lp-directions-grouping').closest('.wpgmza-inner-stack').each(function(){
$(this).find('.wpgmza-lp-directions-grouping, .wpgmza-lp-directions-grouping ~ .grouping-handle').remove();
$(this).removeClass('viewport-grouping expanded');
});
/* Remove orphaned autocomplete list divs from body */
$('body > .wpgmza-internal-autocomplete-list').remove();
}
});
/* ========================================================
CENTRALIZED SETTINGS SYNC + REBUILD
Fields are classified as "rebuild" (structural changes
requiring full teardown/re-render) or "light" (value
synced to map.settings only — existing sync handlers
in the base live preview handle the UI update).
======================================================== */
var REBUILD_SELECTORS = [
'input[name="directions_enabled"]',
'select[name="dbox"]',
'input[name="wpgmza_listmarkers_by"]',
'input[name="filterbycat"]',
'input[name="category_legends_enabled"]',
'input.wpgmza-enable-custom-field-filter',
'input[name="wpgmza_iw_type"]',
'select[name="iw_anchor_panel_card"]',
'select[name="dataTable_pagination_style"]',
'input[name="remove_search_box_datables"]',
'select[name="category_legends_style"]',
'input[name="category_legends_label_string"]',
'input[name="store_locator_nearby_searches"]',
'input[name="marker_share_links"]',
'input[name="wpgmza_store_locator_use_their_location"]',
'input[name="store_locator_category"]',
'input[name="wpgmza_store_locator_hide_before_search"]',
'.wpgmza-anchor-control',
/* Marker Listing — General */
'input[name="marker_listing_component_auto_open"]',
/* Ordering — rebuild updates the AJAX params so the server sorts per live values */
'select[name="order_markers_by"]',
'select[name="order_markers_choice"]',
/* Marker Listing — Filtering */
'input[name="category_filter_label_string"]',
/* Marker Listing — DataTables text strings (rebuild reinitialises the DataTable with new language) */
'input[name="datatable_no_result_message"]',
'input[name="datatable_search_string"]',
'input[name="datatable_result_start"]',
'input[name="datatable_result_of"]',
'input[name="datatable_result_to"]',
'input[name="datatable_result_total"]',
'input[name="datatable_result_show"]',
'input[name="datatable_result_entries"]'
];
function syncSettingsFromForm(){
/* Form ID is 'wpgmaps_options', name attribute is 'wpgmza_map_form' */
$('#wpgmaps_options').find('input, select, textarea').each(function(){
var name = $(this).attr('name');
if(!name) return;
if($(this).is(':radio') && !$(this).is(':checked')) return;
if($(this).is(':checkbox')){
/* Unchecked must be '' (falsy) — NOT '0'. The string
* '0' is truthy in JS (`if('0')` === true), so any
* downstream code that does `if(map.settings.foo)`
* treats unchecked checkboxes as enabled. Notably
* pro-info-window.js:509 / 513 do exactly that for
* store_locator_nearby_searches and marker_share_links,
* which is why "Find Nearby" / "Share Location" links
* appeared in live preview info windows even when
* the user had unchecked the corresponding settings
* — frontend was correct because PHP localizes
* unchecked as int 0 (falsy in JS). */
map.settings[name] = $(this).is(':checked') ? '1' : '';
} else {
map.settings[name] = $(this).val();
}
});
/* Sync convenience properties */
map.directionsEnabled = $('input[name="directions_enabled"]').is(':checked');
}
var rebuildTimer = null;
function debouncedRebuild(){
clearTimeout(rebuildTimer);
rebuildTimer = setTimeout(function(){
syncSettingsFromForm();
rebuildLegendsTemplate();
buildMarkerListingTemplate();
lp.render();
}, 250);
}
/* Bind rebuild triggers */
$(document.body).on('change input', REBUILD_SELECTORS.join(','), debouncedRebuild);
/* ------------------------------------------------------------------
Image Placeholders (per-map override) — live toggle
------------------------------------------------------------------
The placeholder element is force-rendered into both the
info-window clone templates and the listing rows (see
class.pro-map-edit-page.php and buildMarkerListingAjaxParams),
so we only need to show/hide it. Resolve the effective value
(per-map dropdown overrides the global setting) and toggle the
.wpgmza-image-placeholders-off class on the editor root — the
CSS hides every placeholder beneath it. No rebuild/re-fetch. */
function imagePlaceholdersEnabled(){
var override = $('select[name="image_placeholder"]').val();
if(override === 'enabled') return true;
if(override === 'disabled') return false;
/* Default — follow the global setting. */
var g = (WPGMZA.settings && typeof WPGMZA.settings.image_placeholder_enabled !== 'undefined')
? WPGMZA.settings.image_placeholder_enabled : false;
return g === true || g === 1 || g === '1' || g === 'yes';
}
function syncImagePlaceholderPreview(){
$('.wpgmza-editor').toggleClass('wpgmza-image-placeholders-off', !imagePlaceholdersEnabled());
}
$(document.body).on('change', 'select[name="image_placeholder"]', syncImagePlaceholderPreview);
/* Initial state from the loaded form value. */
syncImagePlaceholderPreview();
/* ------------------------------------------------------------------
Advanced tab — Pro/Gold controls with custom live wiring
(map reset control, KML URL, marker ratings)
------------------------------------------------------------------ */
/* Remove any existing reset-map control DOM across all engines */
function removeResetMapControl(){
/* Google — iterate the LEFT_BOTTOM controls MVCArray */
if(map.googleMap && map.googleMap.controls && typeof google !== 'undefined' && google.maps){
var pos = google.maps.ControlPosition.LEFT_BOTTOM;
var arr = map.googleMap.controls[pos];
if(arr && typeof arr.getLength === 'function'){
for(var i = arr.getLength() - 1; i >= 0; i--){
var el = arr.getAt(i);
if(el && el.classList && el.classList.contains('google-maps-control-reset-map')){
arr.removeAt(i);
}
}
}
}
/* Leaflet / OpenLayers — DOM removal is enough; the control wrapper
is a plain element appended into the map DOM. */
$(map.element).find('.leaflet-control-reset-map, .ol-control-reset-map').each(function(){
/* Walk up to the Leaflet control container if present */
var $leafletWrap = $(this).closest('.leaflet-control');
if($leafletWrap.length){
$leafletWrap.remove();
} else {
$(this).remove();
}
});
}
$(document.body).on('change', 'input[name="enable_map_reset_control"]', function(){
var enabled = $(this).is(':checked');
map.settings.enable_map_reset_control = enabled ? 1 : 0;
if(enabled){
/* Guard against double-add if already present */
removeResetMapControl();
if(typeof map.addResetMapControl === 'function'){
map.addResetMapControl();
}
} else {
removeResetMapControl();
}
});
/* KML/GeoRSS URL — loadKMLLayers() already handles both add and remove
based on map.settings.kml. Debounce so we don't refetch on every keystroke. */
var kmlTimer = null;
$(document.body).on('input change', 'input[name="kml"]', function(){
var val = $(this).val();
clearTimeout(kmlTimer);
kmlTimer = setTimeout(function(){
map.settings.kml = val;
if(typeof map.loadKMLLayers === 'function'){
map.loadKMLLayers();
}
}, 500);
});
/* Marker ratings (Gold) — read at infowindow open and markerlistingupdated
events. Light sync into map.settings; next IW open / listing refresh
picks up the change. */
$(document.body).on('change', 'input[name="enable_marker_ratings"]', function(){
map.settings.enable_marker_ratings = $(this).is(':checked') ? 1 : 0;
});
/* ------------------------------------------------------------------
Store Locator → Style: center-point marker live update
The marker is placed by the store locator after a user search
(onGeocodeComplete → onFilteringComplete). These handlers update
the marker in place when its settings change AFTER a search has
been performed — changing the icon or animation immediately.
If no search has run yet, nothing happens; the next search picks
up the new settings via the lazy marker getter.
Mirrors the existing destroyAndRedrawCircle pattern.
------------------------------------------------------------------ */
function destroyAndRedrawCenterMarker(){
if(!map.storeLocator) return;
/* Drop cached marker so the lazy `marker` getter rebuilds it
with the current icon + animation on next filter cycle. */
if(map.storeLocator._marker){
try { map.storeLocator._marker.setVisible(false); } catch(e){}
try { if(map.removeMarker) map.removeMarker(map.storeLocator._marker); } catch(e){}
map.storeLocator._marker = null;
}
/* Sync current form values into map.settings so the lazy getter
builds the new marker correctly. */
map.settings.wpgmza_store_locator_bounce = $('input[name="wpgmza_store_locator_bounce"]').is(':checked') ? 1 : 0;
map.settings.upload_default_sl_marker = $('.wpgmza-store-locator-marker-icon-picker-container input.wpgmza-marker-icon-url').val() || '';
map.settings.wpgmza_sl_animation = parseInt($('select[name="wpgmza_sl_animation"]').val(), 10) || 0;
/* Only re-trigger the filter cycle if a search has actually been
applied — otherwise there's nothing to place. */
if(map.storeLocator.state === WPGMZA.StoreLocator.STATE_APPLIED && map.markerFilter){
map.markerFilter.update({}, map.storeLocator);
}
}
$(document.body).on('change', 'input[name="wpgmza_store_locator_bounce"]', destroyAndRedrawCenterMarker);
$(document.body).on('change', 'select[name="wpgmza_sl_animation"]', destroyAndRedrawCenterMarker);
$(document.body).on('change input', '.wpgmza-store-locator-marker-icon-picker-container input.wpgmza-marker-icon-url', destroyAndRedrawCenterMarker);
/* ------------------------------------------------------------------
Store Locator → Enable Title Search (keyword container).
The .wpgmza-keywords container is injected into the store locator
template by PHP at render time (class.pro-store-locator.php:47),
based on the SAVED `store_locator_name_search` value. Since the
live-preview template is stale once the page has loaded, toggling
the setting in the editor wouldn't add or remove the input on its
own. We inject/remove it client-side so the live preview reflects
the live toggle state. Re-applied after each rebuild via the
`wpgmza_live_preview_rendered` event.
------------------------------------------------------------------ */
function applyKeywordSearchVisibility(){
var enabled = $('input[name="store_locator_name_search"]').is(':checked');
map.settings.store_locator_name_search = enabled ? '1' : '0';
$('.wpgmza-live-preview-component.wpgmza-store-locator, .wpgmza-live-preview-component .wpgmza-store-locator').each(function(){
var $sl = $(this);
var $existing = $sl.children('.wpgmza-keywords');
if(enabled){
if($existing.length) return;
/* Build the same HTML the PHP fragment produces. Keyword
id + label "for" follow the `nameInput_{mapId}` pattern
from class.pro-store-locator.php:204-205. */
var labelText = $('input[name="store_locator_name_string"]').val() || '';
/* "Enter a title" is already registered for translation via
esc_attr_e() in the map editor template, so it resolves
through the plugin's PO catalog at runtime. */
var placeholderText = labelText || 'Enter a title';
var $wrap = $(
'
' +
'' +
'' +
'
'
);
$wrap.children('label').text(labelText);
$wrap.children('input').attr('placeholder', placeholderText);
/* Insert before the radius container if present, else at the top */
var $radius = $sl.children('.wpgmza-radius-container').first();
if($radius.length){
$radius.before($wrap);
} else {
$sl.prepend($wrap);
}
} else {
$existing.remove();
}
});
/* If the StoreLocator instance is already constructed, refresh
its cached categoryDropdown/categoryCheckboxes — and in this
case, grab the new keyword input too — so its filter-change
handler binds correctly. */
if(map.storeLocator){
try {
var $keywordInput = $('.wpgmza-live-preview-component.wpgmza-store-locator input.wpgmza-keywords');
map.storeLocator.keywordSearchInput = $keywordInput[0];
} catch(e){}
}
}
$(document.body).on('change', 'input[name="store_locator_name_search"]', applyKeywordSearchVisibility);
$(document.body).on('click', 'label[for="wpgmza_store_locator_name_search"]', function(){
setTimeout(applyKeywordSearchVisibility, 50);
});
/* Re-apply after every rebuild so the clone reflects live state */
$(document.body).on('wpgmza_live_preview_rendered', function(){
applyKeywordSearchVisibility();
});
/* ------------------------------------------------------------------
Directions → live route updates
- default_to / default_from: mirror into the directions-box inputs
so the user sees the pre-fill without saving.
- stroke color / weight / opacity: if a route is currently drawn,
call setOptions on Google's DirectionsRenderer (or on the polyline
for ORS/Route renderers) to restyle it live.
- origin / destination icons: if a route is drawn, setIcon on the
cached start/end markers.
- fit_bounds_to_route: if toggled on while a route is active, re-fit.
------------------------------------------------------------------ */
/* Keep the directions-box input fields in sync with default_to/from.
Only prefill when the input is currently empty so we don't clobber
what the user is actively editing. */
function applyDefaultDirectionsAddresses(){
var defaultTo = $('input[name="default_to"]').val() || '';
var defaultFrom = $('input[name="default_from"]').val() || '';
map.settings.default_to = defaultTo;
map.settings.default_from = defaultFrom;
/* Find the directions box inputs inside the live preview */
var $box = $('.wpgmza-directions-box, #wpgmaps_directions_edit_' + map.id);
var $toInput = $box.find('input[data-name="directions_to"], input.wpgmza-address.wpgmza-address-to, input.wpgmza-directions-to-input').first();
var $fromInput = $box.find('input[data-name="directions_from"], input.wpgmza-address.wpgmza-address-from, input.wpgmza-directions-from-input').first();
if($toInput.length && !$toInput.val()) $toInput.val(defaultTo);
if($fromInput.length && !$fromInput.val()) $fromInput.val(defaultFrom);
}
$(document.body).on('change input', 'input[name="default_to"], input[name="default_from"]', applyDefaultDirectionsAddresses);
/* Live route styling — stroke + icons + fit bounds */
function getDirectionsRenderer(){
if(!map.directionsBox) return null;
return map.directionsBox.renderer || null;
}
function isRouteDrawn(renderer){
if(!renderer) return false;
/* Google keeps directionStartMarker cached after setDirections;
ORS/Route create polyline + markers. Either signals an active route. */
return !!(renderer.directionStartMarker || renderer.polyline);
}
function applyDirectionsPolylineOptions(){
var renderer = getDirectionsRenderer();
if(!isRouteDrawn(renderer)) return;
var color = $('input[name="directions_route_stroke_color"]').val();
var weight = $('input[name="directions_route_stroke_weight"]').val();
var opacity = $('input[name="directions_route_stroke_opacity"]').val();
if(color !== undefined) map.settings.directions_route_stroke_color = color;
if(weight !== undefined) map.settings.directions_route_stroke_weight = weight;
if(opacity !== undefined) map.settings.directions_route_stroke_opacity = opacity;
var polyOpts = {};
if(color) polyOpts.strokeColor = color;
if(weight) polyOpts.strokeWeight = parseFloat(weight);
if(opacity) polyOpts.strokeOpacity = parseFloat(opacity);
/* Google DirectionsRenderer — restyle via setOptions({polylineOptions}) */
if(renderer.googleDirectionsDisplay && typeof renderer.googleDirectionsDisplay.setOptions === 'function'){
renderer.googleDirectionsDisplay.setOptions({polylineOptions: polyOpts});
}
/* Route/ORS renderers — the polyline is a WPGMZA.Polyline that supports setOptions */
if(renderer.polyline && typeof renderer.polyline.setOptions === 'function'){
renderer.polyline.setOptions(polyOpts);
}
}
$(document.body).on('change input',
'input[name="directions_route_stroke_color"], input[name="directions_route_stroke_weight"], input[name="directions_route_stroke_opacity"]',
applyDirectionsPolylineOptions
);
function applyDirectionsMarkerIcons(){
var renderer = getDirectionsRenderer();
if(!renderer) return;
var originIcon = $('#directions_origin_icon_picker_container input.wpgmza-marker-icon-url').val() || '';
var destIcon = $('#directions_destination_icon_picker_container input.wpgmza-marker-icon-url').val() || '';
map.settings.directions_route_origin_icon = originIcon;
map.settings.directions_route_destination_icon = destIcon;
if(renderer.directionStartMarker && typeof renderer.directionStartMarker.setIcon === 'function'){
renderer.directionStartMarker.setIcon(originIcon);
}
if(renderer.directionEndMarker && typeof renderer.directionEndMarker.setIcon === 'function'){
renderer.directionEndMarker.setIcon(destIcon);
}
}
$(document.body).on('change input',
'#directions_origin_icon_picker_container input.wpgmza-marker-icon-url, #directions_destination_icon_picker_container input.wpgmza-marker-icon-url',
applyDirectionsMarkerIcons
);
/* ------------------------------------------------------------------
User Location — live wiring
- show_user_location: toggle ON triggers navigator.geolocation
(one-shot), drops the marker with the configured icon at the
user's real lat/lng (no faking). Toggle OFF removes the marker.
Uses getCurrentPosition (not watchPosition) so we have clean
teardown without leaving a background watcher running during
editing.
- User-location icon: change destroys + re-places the marker.
- enable_user_location_control: add/remove the native map
control button (same pattern as the reset-map control).
Note: the ProMap constructor's initUserLocationMarker is
explicitly no-op'd on the admin page (is_admin guard + no-op in
pro-map.js:604), so we don't double-place.
------------------------------------------------------------------ */
function removeUserLocationMarker(){
if(map.userLocationMarker){
try { if(map.removeMarker) map.removeMarker(map.userLocationMarker); } catch(e){}
try { map.userLocationMarker.setVisible(false); } catch(e){}
map.userLocationMarker = null;
}
}
function placeUserLocationMarker(){
removeUserLocationMarker();
map.settings.show_user_location = $('input[name="show_user_location"]').is(':checked') ? 1 : 0;
map.settings.upload_default_ul_marker = $('#wpgmza_show_user_location_conditional input.wpgmza-marker-icon-url').val() || '';
if(!map.settings.show_user_location) return;
WPGMZA.getCurrentPosition(function(position){
/* Guard: user may have toggled OFF (or changed icon) while the
geolocation request was in flight. Only place if still on. */
if(!map.settings.show_user_location) return;
if(map.userLocationMarker) return; /* Already placed by a later call */
var icon = map.settings.upload_default_ul_marker;
var options = {
id: WPGMZA.guid(),
animation: WPGMZA.Marker.ANIMATION_DROP,
user_location: true
};
if(icon && icon.length) options.icon = icon;
if(map.settings.upload_default_ul_marker_retina) options.retina = true;
var marker = WPGMZA.Marker.createInstance(options);
marker.isFilterable = false;
marker.setOptions({zIndex: 999999});
if(marker._icon) marker._icon.retina = marker.retina;
marker.setPosition({
lat: position.coords.latitude,
lng: position.coords.longitude
});
map.addMarker(marker);
map.userLocationMarker = marker;
}, function(err){
/* Permission denied or unavailable — no marker. User will see
no visual change; browser console carries the reason. */
});
}
$(document.body).on('change', 'input[name="show_user_location"]', function(){
var enabled = $(this).is(':checked');
map.settings.show_user_location = enabled ? 1 : 0;
if(enabled) placeUserLocationMarker();
else removeUserLocationMarker();
});
$(document.body).on('change input',
'#wpgmza_show_user_location_conditional input.wpgmza-marker-icon-url',
function(){
if($('input[name="show_user_location"]').is(':checked')){
placeUserLocationMarker();
}
}
);
/* Initial state on load — if the saved map had show_user_location on,
place the marker (requests browser geolocation the same way the
frontend would). */
if($('input[name="show_user_location"]').is(':checked')){
placeUserLocationMarker();
}
/* Enable user-location map control — add/remove the native control button */
function removeUserLocationControl(){
if(map.googleMap && map.googleMap.controls && typeof google !== 'undefined' && google.maps){
var pos = google.maps.ControlPosition.RIGHT;
var arr = map.googleMap.controls[pos];
if(arr && typeof arr.getLength === 'function'){
for(var i = arr.getLength() - 1; i >= 0; i--){
var el = arr.getAt(i);
if(el && el.classList && el.classList.contains('google-maps-control-user-location')){
arr.removeAt(i);
}
}
}
}
$(map.element).find('.leaflet-control-user-location, .ol-control-user-location').each(function(){
var $leafletWrap = $(this).closest('.leaflet-control');
if($leafletWrap.length) $leafletWrap.remove();
else $(this).remove();
});
}
$(document.body).on('change', 'input[name="enable_user_location_control"]', function(){
var enabled = $(this).is(':checked');
map.settings.enable_user_location_control = enabled ? 1 : 0;
if(enabled){
removeUserLocationControl();
if(typeof map.addUserLocationControl === 'function'){
map.addUserLocationControl();
}
} else {
removeUserLocationControl();
}
});
/* Shared getter: reuse already-known user position (from #1 marker or
earlier geolocation call) before re-prompting the browser. */
function getCachedUserLocation(){
if(map.userLocation && typeof map.userLocation.lat !== 'undefined') return map.userLocation;
if(map.userLocationMarker && typeof map.userLocationMarker.getPosition === 'function'){
return map.userLocationMarker.getPosition();
}
return null;
}
function withUserLocation(callback){
var cached = getCachedUserLocation();
if(cached){ callback(cached); return; }
WPGMZA.getCurrentPosition(function(position){
var loc = new WPGMZA.LatLng({
lat: position.coords.latitude,
lng: position.coords.longitude
});
map.userLocation = loc;
callback(loc);
});
}
/* Automatically pan to user location (+ override zoom level).
Mirrors pro-map.js:199-218 (which is admin-guarded). Pans only
once per "enable" event — doesn't keep hijacking the viewport as
the user interacts with the preview. */
function applyAutoPan(){
if(!$('input[name="automatically_pan_to_users_location"]').is(':checked')) return;
withUserLocation(function(loc){
map.setCenter(loc);
if($('input[name="override_users_location_zoom_level"]').is(':checked')){
var zoom = parseInt($('input[name="override_users_location_zoom_levels"]').val(), 10);
if(!isNaN(zoom)) map.setZoom(zoom);
}
});
}
$(document.body).on('change', 'input[name="automatically_pan_to_users_location"]', function(){
map.settings.automatically_pan_to_users_location = $(this).is(':checked') ? '1' : '';
if($(this).is(':checked')) applyAutoPan();
});
$(document.body).on('change', 'input[name="override_users_location_zoom_level"]', function(){
map.settings.override_users_location_zoom_level = $(this).is(':checked') ? 1 : 0;
applyAutoPan();
});
$(document.body).on('change input', 'input[name="override_users_location_zoom_levels"]', function(){
var v = parseInt($(this).val(), 10);
if(!isNaN(v)) map.settings.override_users_location_zoom_levels = v;
applyAutoPan();
});
/* If saved map had auto-pan on, apply on init (after a brief delay so
the map centre/zoom from the saved state has already been applied). */
if($('input[name="automatically_pan_to_users_location"]').is(':checked')){
setTimeout(applyAutoPan, 300);
}
/* Show distance from user location (in info windows + marker listings).
Mirrors pro-map.js:268-304. */
function applyShowDistance(){
var enabled = $('input[name="show_distance_from_location"]').is(':checked');
map.settings.show_distance_from_location = enabled ? 1 : 0;
if(enabled){
withUserLocation(function(loc){
map.userLocation = loc;
if(WPGMZA.ProMap && WPGMZA.ProMap.SHOW_DISTANCE_FROM_USER_LOCATION){
map.userLocation.source = WPGMZA.ProMap.SHOW_DISTANCE_FROM_USER_LOCATION;
}
map.showDistanceFromLocation = loc;
if(typeof map.updateInfoWindowDistances === 'function'){
map.updateInfoWindowDistances();
}
if(map.markerListing && typeof map.markerListing.reload === 'function'){
map.markerListing.reload();
}
});
} else {
map.showDistanceFromLocation = null;
if(map.markerListing && typeof map.markerListing.reload === 'function'){
map.markerListing.reload();
}
}
}
$(document.body).on('change', 'input[name="show_distance_from_location"]', applyShowDistance);
if($('input[name="show_distance_from_location"]').is(':checked')){
setTimeout(applyShowDistance, 300);
}
/* Fit bounds to route — when toggled on while a route exists, re-fit immediately */
$(document.body).on('change', 'input[name="directions_fit_bounds_to_route"]', function(){
var enabled = $(this).is(':checked');
map.settings.directions_fit_bounds_to_route = enabled ? 1 : 0;
if(!enabled) return;
var renderer = getDirectionsRenderer();
if(!isRouteDrawn(renderer)) return;
if(renderer.directionStartMarker && renderer.directionEndMarker && typeof renderer.fitBoundsToRoute === 'function'){
renderer.fitBoundsToRoute(
renderer.directionStartMarker.getPosition(),
renderer.directionEndMarker.getPosition()
);
}
});
/* cmn-toggle labels (click fires before the checkbox change) */
$(document.body).on('click',
'label[for="directions_enabled"], label[for="filterbycat"], label[for="category_legends_enabled"], label[for="store_locator_enabled"]',
function(){ setTimeout(debouncedRebuild, 50); }
);
/* After each render, toggle store locator elements based on live settings.
The template was pre-rendered with whatever state was saved. For
settings we can hide/show without re-rendering the template, do it
here using live form values. */
$(document.body).on('wpgmza_live_preview_rendered', function(){
var catEnabled = $('input[name="store_locator_category"]').is(':checked');
$('.wpgmza-store-locator .wpgmza-category-filter-container, .wpgmza-store-locator .wpgmza-category-filter-toggle').each(function(){
$(this).toggleClass('wpgmza-hidden', !catEnabled);
});
/* If any grouping is visible (open-by-default), ensure its handle
is un-hidden. ViewportGroupingPanel.findComponents hides the handle
when the default component requires a feature — but for open-by-default
we need the user to be able to click it to close. */
$(map.element).find('.wpgmza-inner-stack .grouping.visible').each(function(){
$(this).siblings('.grouping-handle').removeClass('wpgmza-hidden');
});
});
/* Delegated close handler for .wpgmza-clear inside the live preview
IW panel. Bound on document.body once, survives populatePanel's
$(el).off('click') calls. Uses capture-first priority via namespaced
event + stopPropagation to ensure we always close. */
$(document.body).on('click.lp-iw-close', '.wpgmza-live-preview-iw-panel .wpgmza-clear, .wpgmza-inner-stack .wpgmza-lp-iw-grouping-item .wpgmza-clear', function(e){
e.stopPropagation();
var thisStack = $(this).closest('.wpgmza-inner-stack');
thisStack.removeClass('expanded');
thisStack.find('.grouping').removeClass('visible');
$(this).closest('.wpgmza-panel-info-window').removeClass('wpgmza-panel-info-window-populated');
});
/* Auto-close panel info window when its Delete button is
* clicked. AdminMarkerDataTable's onDeleteMarker fires the AJAX
* deletion + removes the marker from the map, but leaves the
* panel open showing the now-deleted marker's content. Trigger
* the panel's own .wpgmza-clear button so the close goes
* through the established close path (same DOM mutations as a
* user-initiated close). setTimeout(0) so the existing
* delete-handler click fires first and we don't suppress the
* AJAX request. */
$(document.body).on('click', '.wpgmza-panel-info-window .wpgmza_del_btn', function(){
var $panel = $(this).closest('.wpgmza-panel-info-window');
var $clear = $panel.find('.wpgmza-clear').first();
if(!$clear.length) return;
setTimeout(function(){ $clear.trigger('click'); }, 0);
});
/* Auto-refresh panel info window when the underlying marker is
* saved from the sidebar editor.
*
* `sidebar-delegate-saved` is fired by FeaturePanel.onSave
* AFTER the save flow has:
* 1. removed the old marker from the map
* 2. created a new marker from the REST response
* 3. added the new marker
* 4. reloaded the datatable
*
* IMPORTANT: the event's `feature` property is the feature
* type STRING ("marker"), not the marker instance — see
* feature-panel.js:771-774's `sidebarTriggerDelegate(type)`
* which does `$(el).trigger({type, feature: this.featureType})`.
* Using `event.feature` as a marker reference doesn't work.
*
* Approach: find the populated panel by its data-marker-id
* attribute (set during populatePanel), look up the newly-
* created marker via map.getMarkerByID, and re-run
* populatePanel on its infoWindow. The new marker has a fresh
* infoWindow but populatePanel finds the existing
* `.wpgmza-panel-info-window-populated[data-map="X"]` element
* in the DOM and updates its content in-place. */
$('.wpgmza-feature-accordion[data-wpgmza-feature-type="marker"]').on('sidebar-delegate-saved', function(event){
var $panel = $('.wpgmza-panel-info-window-populated[data-marker-id]').first();
if(!$panel.length) return;
var markerId = parseInt($panel.attr('data-marker-id'), 10);
if(!markerId) return;
var liveMap = (WPGMZA.maps && WPGMZA.maps[0]) || (WPGMZA.mapEditPage && WPGMZA.mapEditPage.map);
if(!liveMap || typeof liveMap.getMarkerByID !== 'function') return;
/* Small delay so the save flow's marker reconciliation
* (remove old → addMarker new → addMarker triggers icon
* resolution etc.) settles before we re-populate. */
setTimeout(function(){
var marker = liveMap.getMarkerByID(markerId);
if(!marker) return;
/* Fresh marker may not have an infoWindow lazily-created
* yet — the original instance only got one when the user
* clicked the marker. Force-create via the engine's
* createInfoWindow path the same way a click would. */
if(!marker.infoWindow && typeof WPGMZA.InfoWindow !== 'undefined' && typeof WPGMZA.InfoWindow.createInstance === 'function'){
marker.infoWindow = WPGMZA.InfoWindow.createInstance(marker);
}
if(marker.infoWindow && typeof marker.infoWindow.populatePanel === 'function'){
/* feature ref needed by populatePanel internals —
* normally set by InfoWindow.open(map, feature). */
marker.infoWindow.feature = marker;
marker.infoWindow.populatePanel();
}
}, 150);
});
/* Register BEFORE marker-listing so the filter clone is in the DOM by
the time MarkerListing's constructor scans for it. MarkerListing
binds change handlers on `.wpgmza-marker-listing-category-filter
[data-map-id="X"] select/input[type="checkbox"]` in its ctor, and
caches the jQuery collection — if the clone isn't placed yet, the
collection is empty and the handlers are never bound, which is why
category clicks did nothing in the live preview. */
lp.registerComponent('category-filter', {
enabledCheck: function(){ return $('input[name="filterbycat"]').is(':checked'); },
anchorField: 'select[name="category_filter_component_anchor"]',
defaultAnchor: 0,
proOnly: true,
onInit: function(map, clone){
/* Re-apply data-map-id (stripped from the template source at LP init
so only the live clone matches MarkerListing's selector). */
var $filter = clone.find('.wpgmza-marker-listing-category-filter')
.addBack('.wpgmza-marker-listing-category-filter');
$filter.attr('data-map-id', map.id);
/* Override the label from the live form value. The PHP template
bakes the saved value in at page-load time, so rebuild alone
doesn't reflect edits to "Filter Label" — we patch it here. */
var label = $('input[name="category_filter_label_string"]').val() || 'Filter by';
$filter.children('label[for^="wpgmza_filter_select_"]').first().text(label);
},
onTeardown: function(map){
/* Cleanup handled by marker listing teardown */
}
});
lp.registerComponent('marker-listing', {
enabledCheck: function(){
var val = $('input[name="wpgmza_listmarkers_by"]:checked').val();
return val && parseInt(val) !== 0;
},
anchorField: 'select[name="marker_listing_component_anchor"]',
defaultAnchor: 10, // BELOW (matches data-default="BELOW" on the select in map-edit-page.html.php)
proOnly: true,
onInit: function(map, clone){
/* Sync form value into map.settings so MarkerListing.createInstance reads the right style */
var listStyle = $('input[name="wpgmza_listmarkers_by"]:checked').val();
if(listStyle !== undefined){
map.settings.wpgmza_listmarkers_by = parseInt(listStyle);
}
if(map.markerListing){
/* Clean up previous listing */
try {
if(map.markerListing.paginationElement){
$(map.markerListing.paginationElement).pagination('destroy');
}
if(map.markerListing.dataTable && map.markerListing.dataTable.dataTable){
map.markerListing.dataTable.dataTable.destroy(true);
}
} catch(e){}
map.markerListing = null;
}
/* Rebuild the template with the correct PHP class for this style */
buildMarkerListingTemplate();
/* Ensure the visible clone's listing element has the correct attributes.
initMarkerListing() and AdvancedTableMarkerListing both do global DOM
searches by ID, so we must de-dupe: only the visible clone should match.
Note: clone IS the listing element itself (not a wrapper around it),
so .find() won't match it — use .addBack() to include the clone. */
var listEl = clone.find('[data-wpgmza-table], [data-wpgmza-marker-listing]')
.addBack('[data-wpgmza-table], [data-wpgmza-marker-listing]').first();
if(!listEl.length) listEl = clone;
listEl.attr('data-wpgmza-marker-listing', 'true');
listEl.attr('id', 'wpgmza_marker_list_' + mapId);
listEl.css('width', '100%');
/* Remove duplicate IDs from hidden templates container */
templates.find('#wpgmza_marker_list_' + mapId).removeAttr('id');
templates.find('#wpgmza_marker_holder_' + mapId).removeAttr('id');
/* Ensure
elements have wpgmza_table_* classes — the AdvancedTableDataTable
drawCallback expects them but initTableDOM only adds them in legacy HTML mode */
listEl.find('thead th[data-wpgmza-column-name]').each(function(){
var name = $(this).attr('data-wpgmza-column-name');
if(name === 'icon') name = 'marker';
if(name === 'title') $(this).addClass('all');
if(!$(this).hasClass('wpgmza_table_' + name)){
$(this).addClass('wpgmza_table_' + name);
}
});
/* STYLE_PANEL (8) + LEFT/RIGHT anchor → route into the
grouped panel (viewport-grouping), mirroring the
frontend PHP shortcode compiler logic in
class.pro-shortcodes.php:306-311 which puts
$panels->left/right['listing'] into the inner-stack
panel system. Live preview was placing it in the
regular `.wpgmza-inner-stack.{left|right}` slot
instead, which doesn't match the frontend rendering.
Same pattern as directions-box (line ~185 above) and
iw-panel Panel style (line ~1238 below) — when those
three components share LEFT (or RIGHT), they all
merge into the same `.grouping` with a single shared
toggle, matching the frontend stacked-panel UX. */
var lpListStyle = parseInt($('input[name="wpgmza_listmarkers_by"]:checked').val()) || 0;
var lpAnchor = lp.getComponentAnchor('marker-listing');
var lpAnchorName = ({1: 'left', 3: 'right'})[lpAnchor];
if(lpListStyle === 8 && lpAnchorName){
/* Detach the clone from wherever base render() placed it */
clone.detach();
var lpMapEl = $(map.element);
var lpStack = lpMapEl.children('.wpgmza-inner-stack.' + lpAnchorName);
if(!lpStack.length){
lpStack = $('');
lpMapEl.append(lpStack);
WPGMZA.AtlasMajorLivePreview.registerInnerStack(lpStack);
}
lpStack.addClass('viewport-grouping');
/* IMPORTANT: marker listing is ALWAYS-VISIBLE (unlike
directions / iw-panel which are feature-triggered).
Intentionally OMIT `data-requires-feature` here —
`ViewportGroupingPanel.findComponents()` adds
`.wpgmza-hidden` to the toggle handle when the
FIRST grouping-item has data-requires-feature=true.
The listing wants the handle always visible, so we
skip the attribute. See viewport-grouping-panel.js
lines ~76-80. */
var lpGroupingItem = $('');
lpGroupingItem.append(clone);
/* If a grouping already exists (created by directions /
iw-panel earlier in this render's component loop),
PREPEND our item so it becomes the FIRST child.
ViewportGroupingPanel picks the first grouping-item
as the defaultView — we want listing to be the
default since it's always-visible. Without the
prepend, directions/iw-panel would win that slot
and the handle would be hidden again. */
var lpExistingGrouping = lpStack.children('.grouping').first();
if(lpExistingGrouping.length){
lpExistingGrouping.prepend(lpGroupingItem);
} else {
var lpGrouping = $('');
var lpGroupingHandle = $('
');
lpGrouping.append(lpGroupingItem);
lpStack.append(lpGrouping);
lpStack.append(lpGroupingHandle);
}
lpStack.css('display', 'flex');
/* Defensive: clear `wpgmza-hidden` on the handle
before re-init. A previous render (or a
directions/iw-panel onInit running earlier this
render) could have added it via their
requires-feature path; the new ViewportGroupingPanel
instance only ADDS the class conditionally, it
never removes a stale one. With listing now
prepended as the first grouping-item the new
instance won't re-add it, so wiping here makes
the handle reliably visible. */
lpStack.find('.grouping-handle').removeClass('wpgmza-hidden');
if(map.viewportGroupings){
/* Unbind existing handlers before re-init, or
initGroups stacks duplicate handlers (same
defensive cleanup directions/iw-panel do). */
$(map.element).find('.grouping-handle').off('click');
map.viewportGroupings.initGroups();
}
}
var origAdmin = WPGMZA.is_admin;
WPGMZA.is_admin = "0";
try {
WPGMZA.ProMap.prototype.initMarkerListing.call(map);
} catch(e){
console.warn('LP-PRO: initMarkerListing failed', e);
}
WPGMZA.is_admin = origAdmin;
},
onTeardown: function(map){
if(map.markerListing){
try {
if(map.markerListing.dataTable && map.markerListing.dataTable.dataTable){
map.markerListing.dataTable.dataTable.destroy(true);
}
if(map.markerListing.paginationElement){
$(map.markerListing.paginationElement).pagination('destroy');
}
} catch(e){}
/* Neuter the orphaned filteringcomplete listener — the
MarkerListing constructor binds an anonymous handler on
document.body that we can't unbind by reference. Nulling
the element prevents reload() from sending requests. */
map.markerListing.element = null;
map.markerListing = null;
}
/* Remove Panel-style listing grouping-items so the next
render doesn't double-up when directions / iw-panel
recreate the shared grouping. Mirrors the cleanup
iw-panel does for `.wpgmza-lp-iw-grouping-item`. */
$('.wpgmza-lp-listing-grouping-item').remove();
}
});
/* Marker listing radio — handled by centralized rebuild */
/* category-filter is registered earlier (before marker-listing) so the
clone is in the DOM by the time MarkerListing's constructor runs its
global `.wpgmza-marker-listing-category-filter[data-map-id="X"]` lookup.
See registration block above the marker-listing component. */
/* Rebuild legends template from current settings */
function rebuildLegendsTemplate(){
templates.find('[data-preview-component="category-legends"]').remove();
var legendsHtml = buildCategoryLegendsHtml(mapId);
templates.append(
'
' +
legendsHtml +
'
'
);
}
lp.registerComponent('category-legends', {
enabledCheck: function(){ return $('input[name="category_legends_enabled"]').is(':checked'); },
anchorField: 'select[name="category_legends_component_anchor"]',
defaultAnchor: 8,
proOnly: true,
onInit: function(map, clone){
if(map.categoryLegends) map.categoryLegends = null;
if(WPGMZA.CategoryLegends){
/* Find only the clone in the live preview, not the hidden template */
var els = clone.find('.wpgmza-category-legends').addBack('.wpgmza-category-legends');
if(!els.length){
els = clone.filter('.wpgmza-category-legends');
}
if(els.length){
els.attr('data-map-id', map.id);
map.categoryLegends = [];
els.each(function(i, el){
map.categoryLegends.push(new WPGMZA.CategoryLegends(el));
});
}
}
},
onTeardown: function(map){
if(map.categoryLegends){
map.categoryLegends = null;
}
}
});
/* Category legends toggle — handled by centralized rebuild */
/* ========================================================
MARKER FIELDS (Custom Field Filters)
======================================================== */
lp.registerComponent('marker-fields', {
enabledCheck: function(){
return $('input.wpgmza-enable-custom-field-filter:checked').length > 0;
},
anchorField: 'select[name="marker_fields_component_anchor"]',
defaultAnchor: 9, /* ABOVE */
proOnly: true,
onInit: function(map, clone){
/* Show only widgets for fields that are checked in the editor settings */
clone.find('[data-field-id]').each(function(){
var fieldId = $(this).attr('data-field-id');
var isEnabled = $('input[name="enable_filter_custom_field_' + fieldId + '"]').is(':checked');
$(this).toggle(isEnabled);
});
/* Init the custom field filter controller — it auto-discovers
[data-wpgmza-filter-widget-class] elements in the DOM */
if(map.customFieldFilterController){
map.customFieldFilterController = null;
}
var origAdmin = WPGMZA.is_admin;
WPGMZA.is_admin = "0";
try {
WPGMZA.ProMap.prototype.initCustomFieldFilterController.call(map);
} catch(e){
console.warn('LP-PRO: initCustomFieldFilterController failed', e);
}
WPGMZA.is_admin = origAdmin;
},
onTeardown: function(map){
if(map.customFieldFilterController){
/* Remove the