map input
This commit is contained in:
parent
0aec36908b
commit
06285fc533
|
|
@ -51,6 +51,12 @@ return [
|
|||
'notification' => 'Notification',
|
||||
'confirm_delete_address' => 'Are you sure you want to delete this address?',
|
||||
'this_action_cannot_be_undone' => 'This action cannot be undone.',
|
||||
'select_location_on_map' => 'Select location on map',
|
||||
'search_for_location' => 'Search for location',
|
||||
'search' => 'Search',
|
||||
'type_to_search' => 'Start typing to search for locations...',
|
||||
'map_will_load_here' => 'Interactive map will load here when modal opens',
|
||||
'drag_marker_to_adjust' => 'Drag the marker to adjust the exact location',
|
||||
'regions' => [
|
||||
'africa' => 'Africa',
|
||||
'asia' => 'Asia',
|
||||
|
|
|
|||
|
|
@ -51,6 +51,12 @@ return [
|
|||
'confirm_delete_address' => 'Apakah Anda yakin ingin menghapus alamat ini?',
|
||||
'this_action_cannot_be_undone' => 'Tindakan ini tidak dapat dibatalkan.',
|
||||
'notification' => 'Notifikasi',
|
||||
'select_location_on_map' => 'Pilih lokasi di peta',
|
||||
'search_for_location' => 'Cari lokasi',
|
||||
'search' => 'Cari',
|
||||
'type_to_search' => 'Mulai mengetik untuk mencari lokasi...',
|
||||
'map_will_load_here' => 'Peta interaktif akan dimuat di sini saat modal dibuka',
|
||||
'drag_marker_to_adjust' => 'Seret penanda untuk menyesuaikan lokasi yang tepat',
|
||||
'regions' => [
|
||||
'africa' => 'Afrika',
|
||||
'asia' => 'Asia',
|
||||
|
|
|
|||
|
|
@ -174,22 +174,19 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.latitude') }}</label>
|
||||
<input type="text" class="form-control new-latitude" step="0.000001" required>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_enter_latitude') }}</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<x-address.address-map
|
||||
:latitude="null"
|
||||
:longitude="null"
|
||||
latitudeInputName="latitude"
|
||||
longitudeInputName="longitude"
|
||||
:searchPlaceholder="__('addresses.search_for_location')"
|
||||
:searchButtonText="__('addresses.search')"
|
||||
:mapLabel="__('addresses.select_location_on_map')"
|
||||
:instructionText="__('addresses.drag_marker_to_adjust')"
|
||||
:typeToSearchText="__('addresses.type_to_search')" />
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.longitude') }}</label>
|
||||
<input type="text" class="form-control new-longitude" step="0.000001" required>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_enter_longitude') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<div class="col-12">
|
||||
<div class="position-relative">
|
||||
<label for="new-address" class="form-label">{{ __('addresses.address') }}</label>
|
||||
<textarea class="form-control" id="new-address" rows="3" required></textarea>
|
||||
|
|
@ -447,26 +444,37 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.latitude') }}</label>
|
||||
<input type="text" class="form-control latitude" value="${address.latitude ?? ''}" step="0.000001" required>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_enter_latitude') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="address-map-component">
|
||||
<!-- Hidden inputs for latitude and longitude -->
|
||||
<input type="hidden" name="latitude" id="latitude" value="${address.latitude ?? ''}" class="latitude address-latitude-input" step="0.000001">
|
||||
<input type="hidden" name="longitude" id="longitude" value="${address.longitude ?? ''}" class="longitude address-longitude-input" step="0.000001">
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.longitude') }}</label>
|
||||
<input type="text" class="form-control longitude" value="${address.longitude ?? ''}" step="0.000001" required>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_enter_longitude') }}</div>
|
||||
<!-- Map interface -->
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.select_location_on_map') }}</label>
|
||||
<div class="mb-3 position-relative">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control address-location-search" placeholder="{{ __('addresses.search_for_location') }}" autocomplete="off">
|
||||
<button class="btn btn-outline-secondary address-search-btn" type="button">
|
||||
<i class="ci-search"></i> {{ __('addresses.search') }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- Search suggestions dropdown -->
|
||||
<div class="address-search-suggestions position-absolute w-100 bg-white border rounded-top-0 rounded-bottom shadow-sm" style="z-index: 1050; max-height: 200px; overflow-y: auto; display: none;">
|
||||
<div class="p-2 text-muted small">{{ __('addresses.type_to_search') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="address-map-container" style="height: 400px; width: 100%; border-radius: 8px; overflow: hidden;"></div>
|
||||
<small class="text-muted">{{ __('addresses.drag_marker_to_adjust') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="col-12">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.address') }}</label>
|
||||
<input type="text" class="form-control address-input" value="${address.address}" required>
|
||||
<textarea class="form-control address-input" rows="3" required>${address.address}</textarea>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_enter_address') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -523,6 +531,15 @@
|
|||
// Load provinces and set initial values
|
||||
loadProvinces(select, addressId, provinceId, cityId, districtId, villageId);
|
||||
});
|
||||
|
||||
// Initialize map components (for dynamically generated content)
|
||||
document.querySelectorAll('.address-map-component').forEach(component => {
|
||||
// Trigger initialization for this component
|
||||
if (window.initializeAddressMap) {
|
||||
const componentIndex = Date.now() + Math.random();
|
||||
window.initializeAddressMap(component, componentIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Load addresses on page load
|
||||
|
|
|
|||
|
|
@ -0,0 +1,438 @@
|
|||
@props([
|
||||
'latitude' => null,
|
||||
'longitude' => null,
|
||||
'latitudeInputName' => 'latitude',
|
||||
'longitudeInputName' => 'longitude',
|
||||
'searchPlaceholder' => 'Search for location',
|
||||
'searchButtonText' => 'Search',
|
||||
'mapLabel' => 'Select location on map',
|
||||
'instructionText' => 'Drag the marker to adjust the exact location',
|
||||
'typeToSearchText' => 'Start typing to search for locations...',
|
||||
])
|
||||
|
||||
<div class="address-map-component">
|
||||
<!-- Hidden inputs for latitude and longitude -->
|
||||
<input type="hidden" name="{{ $latitudeInputName }}" id="{{ $latitudeInputName }}" value="{{ $latitude ?? '' }}"
|
||||
class="address-latitude-input" step="0.000001">
|
||||
|
||||
<input type="hidden" name="{{ $longitudeInputName }}" id="{{ $longitudeInputName }}" value="{{ $longitude ?? '' }}"
|
||||
class="address-longitude-input" step="0.000001">
|
||||
|
||||
<!-- Map interface -->
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ $mapLabel }}</label>
|
||||
<div class="mb-3 position-relative">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control address-location-search"
|
||||
placeholder="{{ $searchPlaceholder }}" autocomplete="off">
|
||||
<button class="btn btn-outline-secondary address-search-btn" type="button">
|
||||
<i class="ci-search"></i> {{ $searchButtonText }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- Search suggestions dropdown -->
|
||||
<div class="address-search-suggestions position-absolute w-100 bg-white border rounded-top-0 rounded-bottom shadow-sm"
|
||||
style="z-index: 1050; max-height: 200px; overflow-y: auto; display: none;">
|
||||
<div class="p-2 text-muted small">{{ $typeToSearchText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="address-map-container" style="height: 400px; width: 100%; border-radius: 8px; overflow: hidden;">
|
||||
</div>
|
||||
<small class="text-muted">{{ $instructionText }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize all address map components
|
||||
const addressMapComponents = document.querySelectorAll('.address-map-component');
|
||||
|
||||
addressMapComponents.forEach(function(component, index) {
|
||||
initializeAddressMap(component, index);
|
||||
});
|
||||
|
||||
// Initialize maps from placeholders (for dynamically generated content)
|
||||
function initializeMapFromPlaceholder(placeholder) {
|
||||
const component = createMapComponentFromPlaceholder(placeholder);
|
||||
if (component) {
|
||||
initializeAddressMap(component, Date.now());
|
||||
}
|
||||
}
|
||||
|
||||
function createMapComponentFromPlaceholder(placeholder) {
|
||||
// Get data attributes from placeholder
|
||||
const latitude = placeholder.dataset.latitude;
|
||||
const longitude = placeholder.dataset.longitude;
|
||||
const latitudeInputName = placeholder.dataset.latitudeInput || 'latitude';
|
||||
const longitudeInputName = placeholder.dataset.longitudeInput || 'longitude';
|
||||
const searchPlaceholder = placeholder.dataset.searchPlaceholder || 'Search for location';
|
||||
const searchButtonText = placeholder.dataset.searchButton || 'Search';
|
||||
const mapLabel = placeholder.dataset.mapLabel || 'Select location on map';
|
||||
const instructionText = placeholder.dataset.instructionText ||
|
||||
'Drag the marker to adjust the exact location';
|
||||
const typeToSearchText = placeholder.dataset.typeToSearch ||
|
||||
'Start typing to search for locations...';
|
||||
|
||||
// Create component HTML
|
||||
const componentHtml = `
|
||||
<div class="address-map-component">
|
||||
<!-- Hidden inputs for latitude and longitude -->
|
||||
<input type="hidden"
|
||||
name="${latitudeInputName}"
|
||||
id="${latitudeInputName}"
|
||||
value="${latitude || ''}"
|
||||
class="address-latitude-input"
|
||||
step="0.000001">
|
||||
|
||||
<input type="hidden"
|
||||
name="${longitudeInputName}"
|
||||
id="${longitudeInputName}"
|
||||
value="${longitude || ''}"
|
||||
class="address-longitude-input"
|
||||
step="0.000001">
|
||||
|
||||
<!-- Map interface -->
|
||||
<div class="position-relative">
|
||||
<label class="form-label">${mapLabel}</label>
|
||||
<div class="mb-3 position-relative">
|
||||
<div class="input-group">
|
||||
<input type="text"
|
||||
class="form-control address-location-search"
|
||||
placeholder="${searchPlaceholder}"
|
||||
autocomplete="off">
|
||||
<button class="btn btn-outline-secondary address-search-btn" type="button">
|
||||
<i class="ci-search"></i> ${searchButtonText}
|
||||
</button>
|
||||
</div>
|
||||
<!-- Search suggestions dropdown -->
|
||||
<div class="address-search-suggestions position-absolute w-100 bg-white border rounded-top-0 rounded-bottom shadow-sm"
|
||||
style="z-index: 1050; max-height: 200px; overflow-y: auto; display: none;">
|
||||
<div class="p-2 text-muted small">${typeToSearchText}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="address-map-container" style="height: 400px; width: 100%; border-radius: 8px; overflow: hidden;"></div>
|
||||
<small class="text-muted">${instructionText}</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Replace placeholder with component
|
||||
placeholder.outerHTML = componentHtml;
|
||||
|
||||
// Return the new component element
|
||||
return placeholder.parentElement.querySelector('.address-map-component');
|
||||
}
|
||||
|
||||
function initializeAddressMap(component, componentIndex) {
|
||||
let map = null;
|
||||
let marker = null;
|
||||
let searchTimeout;
|
||||
|
||||
const mapContainer = component.querySelector('.address-map-container');
|
||||
const searchInput = component.querySelector('.address-location-search');
|
||||
const searchBtn = component.querySelector('.address-search-btn');
|
||||
const suggestionsDiv = component.querySelector('.address-search-suggestions');
|
||||
const latInput = component.querySelector('.address-latitude-input');
|
||||
const lngInput = component.querySelector('.address-longitude-input');
|
||||
|
||||
// Generate unique IDs for this component
|
||||
const mapId = 'address-map-' + componentIndex;
|
||||
mapContainer.id = mapId;
|
||||
|
||||
// Initialize map
|
||||
function initializeMap() {
|
||||
if (typeof L !== 'undefined' && mapContainer) {
|
||||
// Get initial coordinates or default to Jakarta
|
||||
let initialLat = -6.2088;
|
||||
let initialLng = 106.8456;
|
||||
let initialZoom = 13;
|
||||
|
||||
if (latInput.value && lngInput.value) {
|
||||
const lat = parseFloat(latInput.value);
|
||||
const lng = parseFloat(lngInput.value);
|
||||
if (!isNaN(lat) && !isNaN(lng)) {
|
||||
initialLat = lat;
|
||||
initialLng = lng;
|
||||
initialZoom = 15;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize map
|
||||
map = L.map(mapId).setView([initialLat, initialLng], initialZoom);
|
||||
|
||||
// Add OpenStreetMap tiles
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: ' OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
// Add initial marker if coordinates exist
|
||||
if (initialLat !== -6.2088 || initialLng !== 106.8456) {
|
||||
marker = L.marker([initialLat, initialLng], {
|
||||
draggable: true
|
||||
}).addTo(map);
|
||||
|
||||
// Add drag event listener
|
||||
marker.on('dragend', function(e) {
|
||||
const position = e.target.getLatLng();
|
||||
latInput.value = position.lat.toFixed(6);
|
||||
lngInput.value = position.lng.toFixed(6);
|
||||
});
|
||||
} else {
|
||||
// Add draggable marker at center
|
||||
marker = L.marker([-6.2088, 106.8456], {
|
||||
draggable: true
|
||||
}).addTo(map);
|
||||
|
||||
// Add drag event listener
|
||||
marker.on('dragend', function(e) {
|
||||
const position = e.target.getLatLng();
|
||||
latInput.value = position.lat.toFixed(6);
|
||||
lngInput.value = position.lng.toFixed(6);
|
||||
});
|
||||
}
|
||||
|
||||
// Add click event to place marker
|
||||
map.on('click', function(e) {
|
||||
const position = e.latlng;
|
||||
|
||||
if (marker) {
|
||||
marker.setLatLng(position);
|
||||
} else {
|
||||
marker = L.marker(position, {
|
||||
draggable: true
|
||||
}).addTo(map);
|
||||
|
||||
// Add drag event listener
|
||||
marker.on('dragend', function(e) {
|
||||
const pos = e.target.getLatLng();
|
||||
latInput.value = pos.lat.toFixed(6);
|
||||
lngInput.value = pos.lng.toFixed(6);
|
||||
});
|
||||
}
|
||||
|
||||
latInput.value = position.lat.toFixed(6);
|
||||
lngInput.value = position.lng.toFixed(6);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Search functionality
|
||||
function searchLocation(query) {
|
||||
if (!query) {
|
||||
console.log('Empty query, skipping search');
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure map is initialized
|
||||
if (!map) {
|
||||
console.log('Map not initialized, trying to initialize...');
|
||||
initializeMap();
|
||||
// Wait a bit for map to initialize
|
||||
setTimeout(() => {
|
||||
if (map) {
|
||||
performSearch(query);
|
||||
} else {
|
||||
console.error('Map still not initialized after retry');
|
||||
}
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
performSearch(query);
|
||||
}
|
||||
|
||||
function performSearch(query) {
|
||||
console.log('Performing search for:', query);
|
||||
|
||||
// Using Nominatim API for geocoding
|
||||
fetch(
|
||||
`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=5`)
|
||||
.then(response => {
|
||||
console.log('Search response received:', response.status);
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Search results:', data);
|
||||
if (data && data.length > 0) {
|
||||
displaySearchSuggestions(data);
|
||||
} else {
|
||||
displaySearchSuggestions([]);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Search error:', error);
|
||||
displaySearchSuggestions([]);
|
||||
});
|
||||
}
|
||||
|
||||
function displaySearchSuggestions(results) {
|
||||
console.log('Displaying suggestions:', results);
|
||||
|
||||
if (!suggestionsDiv) {
|
||||
console.error('Suggestions div not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
suggestionsDiv.innerHTML = '<div class="p-2 text-muted small">No locations found</div>';
|
||||
suggestionsDiv.style.display = 'block';
|
||||
console.log('No results found, showing message');
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
results.forEach(result => {
|
||||
const displayName = result.display_name || result.name;
|
||||
html += `
|
||||
<div class="suggestion-item p-2 border-bottom hover-bg-light cursor-pointer"
|
||||
data-lat="${result.lat}" data-lng="${result.lon}" data-name="${displayName}">
|
||||
<div class="fw-medium">${result.name}</div>
|
||||
<div class="text-muted small">${displayName}</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
suggestionsDiv.innerHTML = html;
|
||||
suggestionsDiv.style.display = 'block';
|
||||
console.log('Suggestions displayed, HTML length:', html.length);
|
||||
|
||||
// Add click handlers to suggestions
|
||||
suggestionsDiv.querySelectorAll('.suggestion-item').forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
const lat = parseFloat(this.dataset.lat);
|
||||
const lng = parseFloat(this.dataset.lng);
|
||||
const name = this.dataset.name;
|
||||
|
||||
console.log('Suggestion clicked:', {
|
||||
lat,
|
||||
lng,
|
||||
name
|
||||
});
|
||||
|
||||
// Update map view
|
||||
map.setView([lat, lng], 15);
|
||||
|
||||
// Update or create marker
|
||||
if (marker) {
|
||||
marker.setLatLng([lat, lng]);
|
||||
} else {
|
||||
marker = L.marker([lat, lng], {
|
||||
draggable: true
|
||||
}).addTo(map);
|
||||
|
||||
// Add drag event listener
|
||||
marker.on('dragend', function(e) {
|
||||
const position = e.target.getLatLng();
|
||||
latInput.value = position.lat.toFixed(6);
|
||||
lngInput.value = position.lng.toFixed(6);
|
||||
});
|
||||
}
|
||||
|
||||
// Update input fields
|
||||
latInput.value = lat.toFixed(6);
|
||||
lngInput.value = lng.toFixed(6);
|
||||
searchInput.value = name;
|
||||
|
||||
// Hide suggestions
|
||||
suggestionsDiv.style.display = 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function hideSuggestions() {
|
||||
if (suggestionsDiv) {
|
||||
suggestionsDiv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
if (searchBtn && searchInput) {
|
||||
console.log('Search elements found:', {
|
||||
searchBtn,
|
||||
searchInput
|
||||
});
|
||||
|
||||
searchBtn.addEventListener('click', function() {
|
||||
console.log('Search button clicked, value:', searchInput.value);
|
||||
searchLocation(searchInput.value);
|
||||
});
|
||||
|
||||
searchInput.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
console.log('Enter key pressed, value:', searchInput.value);
|
||||
e.preventDefault();
|
||||
hideSuggestions();
|
||||
searchLocation(searchInput.value);
|
||||
}
|
||||
});
|
||||
|
||||
// Add debounced search for real-time suggestions
|
||||
searchInput.addEventListener('input', function(e) {
|
||||
const query = e.target.value.trim();
|
||||
console.log('Input event, query:', query);
|
||||
|
||||
// Clear previous timeout
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
|
||||
if (query.length < 2) {
|
||||
hideSuggestions();
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounce search (wait 300ms after user stops typing)
|
||||
searchTimeout = setTimeout(() => {
|
||||
console.log('Debounced search triggered for:', query);
|
||||
searchLocation(query);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Hide suggestions when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!e.target.closest('.address-map-component')) {
|
||||
hideSuggestions();
|
||||
}
|
||||
});
|
||||
|
||||
// Hide suggestions on ESC key
|
||||
searchInput.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
hideSuggestions();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('Search elements not found:', {
|
||||
searchBtn,
|
||||
searchInput
|
||||
});
|
||||
}
|
||||
|
||||
// Update marker when coordinates are manually entered
|
||||
[latInput, lngInput].forEach(input => {
|
||||
input.addEventListener('change', function() {
|
||||
const lat = parseFloat(latInput.value);
|
||||
const lng = parseFloat(lngInput.value);
|
||||
|
||||
if (!isNaN(lat) && !isNaN(lng) && map && marker) {
|
||||
marker.setLatLng([lat, lng]);
|
||||
map.setView([lat, lng], 15);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize map when component is ready
|
||||
setTimeout(() => {
|
||||
initializeMap();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Make functions globally available for dynamic initialization
|
||||
window.initializeMapFromPlaceholder = initializeMapFromPlaceholder;
|
||||
window.initializeAddressMap = initializeAddressMap;
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in New Issue