fix: implement form-based coordinate sync for single mode map
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 19s

Fixed marker display and centering in single mode (marker pages) by
changing from props-based to form-based coordinate synchronization.

Issues Fixed:
- Kirby blueprint query syntax {{ page.field }} passed literal strings
  instead of values to component props
- Invalid coordinates (NaN, NaN) caused map initialization errors
- Marker not displaying in marker page position tab
- Map not centering on marker location

Solution:
- Remove latitude/longitude props from marker.yml blueprint
- Read coordinates directly from Panel form fields via DOM
- Add event listeners to sync form changes with map
- Bidirectional sync: drag marker → updates form fields
- Robust coordinate validation (check for NaN, null, 0)

Changes:
- MapEditor.vue: Add form field reading and event listeners
- MapEditor.vue: Replace props-based coords with reactive refs
- MapEditor.vue: Update marker drag handler to modify form inputs
- marker.yml: Remove non-functional query string props
- routes.php: Use data() instead of body() for all routes

Single Mode Flow:
1. Component reads latitude/longitude from form inputs on mount
2. Creates marker and centers map on valid coordinates
3. Form changes → updates marker position
4. Marker drag → updates form fields (triggers save on user action)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-01-29 14:17:01 +01:00
parent 32e8301d91
commit cc44a68e66
3 changed files with 125 additions and 103 deletions

View file

@ -58,6 +58,4 @@ tabs:
label: Position sur la carte
type: map-editor
mode: single
latitude: "{{ page.latitude }}"
longitude: "{{ page.longitude }}"
help: Déplacez le marqueur ou recherchez une adresse

File diff suppressed because one or more lines are too long

View file

@ -128,14 +128,15 @@ export default {
// Computed: markers based on mode
const markers = computed(() => {
if (props.mode === 'single') {
// Single mode: create one marker from props
if (props.latitude !== null && props.longitude !== null) {
// Single mode: create one marker from form values
const lat = singleLat.value;
const lon = singleLon.value;
// Only create marker if we have valid coordinates
if (!isNaN(lat) && !isNaN(lon) && lat !== null && lon !== null && lat !== 0 && lon !== 0) {
return [{
id: 'single-marker',
position: {
lat: parseFloat(props.latitude),
lon: parseFloat(props.longitude),
},
position: { lat, lon },
title: 'Current position',
}];
}
@ -149,6 +150,25 @@ export default {
return markers.value.length < props.maxMarkers;
});
// Single mode: reactive references for coordinates from form fields
const singleLat = ref(null);
const singleLon = ref(null);
// Function to get coordinates from Panel form fields (single mode)
function getCoordinatesFromForm() {
// Find the latitude and longitude input fields in the same form
const form = document.querySelector('.k-form');
if (!form) return { lat: null, lon: null };
const latInput = form.querySelector('input[name*="latitude"]');
const lonInput = form.querySelector('input[name*="longitude"]');
return {
lat: latInput ? parseFloat(latInput.value) : null,
lon: lonInput ? parseFloat(lonInput.value) : null
};
}
// Load data on mount
onMounted(async () => {
if (props.mode === 'multi') {
@ -159,12 +179,25 @@ export default {
console.error('Failed to load markers:', error);
}
} else if (props.mode === 'single') {
// Single mode: center on marker position
if (props.latitude !== null && props.longitude !== null) {
center.value = {
lat: parseFloat(props.latitude),
lon: parseFloat(props.longitude),
};
// Single mode: get coordinates from form
const coords = getCoordinatesFromForm();
singleLat.value = coords.lat;
singleLon.value = coords.lon;
if (!isNaN(coords.lat) && !isNaN(coords.lon) && coords.lat !== 0 && coords.lon !== 0) {
center.value = { lat: coords.lat, lon: coords.lon };
}
// Watch for changes in the form fields
const form = document.querySelector('.k-form');
if (form) {
form.addEventListener('input', (e) => {
if (e.target.name && (e.target.name.includes('latitude') || e.target.name.includes('longitude'))) {
const newCoords = getCoordinatesFromForm();
singleLat.value = newCoords.lat;
singleLon.value = newCoords.lon;
}
});
}
}
@ -188,14 +221,16 @@ export default {
{ deep: true }
);
// Watch latitude/longitude props in single mode
// Watch form coordinates in single mode
watch(
() => [props.latitude, props.longitude],
([newLat, newLon]) => {
if (props.mode === 'single' && newLat !== null && newLon !== null) {
// Center map on new position
() => [singleLat.value, singleLon.value],
([lat, lon]) => {
if (props.mode === 'single') {
// Center map on new position if valid
if (!isNaN(lat) && !isNaN(lon) && lat !== null && lon !== null && lat !== 0 && lon !== 0) {
if (mapPreview.value && mapPreview.value.centerOnPosition) {
mapPreview.value.centerOnPosition(parseFloat(newLat), parseFloat(newLon));
mapPreview.value.centerOnPosition(lat, lon);
}
}
}
}
@ -267,34 +302,23 @@ export default {
*/
async function handleMarkerMoved({ markerId, position }) {
if (props.mode === 'single') {
// Single mode: update current page's coordinates via API
// Extract current page ID from window location
const match = window.location.pathname.match(/\/panel\/pages\/(.+)/);
if (match) {
const currentPageId = match[1];
try {
const csrfToken = document.querySelector('meta[name="csrf"]')?.content || '';
const response = await fetch(`/api/map-editor/pages/${currentPageId}/position`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-CSRF': csrfToken
},
body: JSON.stringify({
latitude: position.lat,
longitude: position.lng
})
});
// Single mode: update form fields directly
const form = document.querySelector('.k-form');
if (form) {
const latInput = form.querySelector('input[name*="latitude"]');
const lonInput = form.querySelector('input[name*="longitude"]');
if (!response.ok) {
throw new Error('Failed to update position');
}
if (latInput && lonInput) {
latInput.value = position.lat;
lonInput.value = position.lng;
// Reload the panel form to reflect updated values
// This is a simple approach - Panel will refresh the form data
console.log('Position updated successfully');
} catch (error) {
console.error('Failed to update position:', error);
// Trigger input event to update Vue/Kirby state
latInput.dispatchEvent(new Event('input', { bubbles: true }));
lonInput.dispatchEvent(new Event('input', { bubbles: true }));
// Update local refs
singleLat.value = position.lat;
singleLon.value = position.lng;
}
}
} else {