Phase 3: Formular zum Anlegen und Bearbeiten
Neues Modal (js/ui/form.js) mit allen Feldern aus anforderung.md Abschnitt 6: Name, Kategorie, Kurz-/Volltext-Beschreibung, Adresse, Ort, Koordinaten, Altersgruppe, Kosten, Oeffnungszeiten, Aufenthaltsdauer, Ausstattungs-Checkboxen, Indoor/Outdoor, Bild-URL, Website, Tags. Validierung: Pflichtfelder (Name, Kategorie, Koordinaten), Koordinaten- Bereichspruefung, Alter min<=max, Bild-/Website-URL nur http/https. Fehler erscheinen inline je Feld (aria-invalid/aria-describedby), erstes fehlerhaftes Feld wird fokussiert. EventMap.ui.openPlaceForm(place) dient als Einstiegspunkt: ohne Argument zum Neuanlegen, mit Place-Objekt zum Bearbeiten. Speichern aktualisiert den Store mit neuer Array-Referenz (Karte/Liste reagieren automatisch) und persistiert nach LocalStorage. Verdrahtung: neuer "+ Neues Ausflugsziel hinzufuegen"-Button in der Karten-Toolbar; der bisherige Bearbeiten-Platzhalter in der Detailansicht (js/ui/detail.js) oeffnet jetzt das Formular vorausgefuellt mit dem aktuell angezeigten Ausflugsziel.
This commit is contained in:
+604
@@ -0,0 +1,604 @@
|
||||
// Formular (Modal) zum manuellen Anlegen und Bearbeiten eines
|
||||
// Ausflugsziels (Phase 3, siehe anforderung.md Abschnitt 6).
|
||||
//
|
||||
// API: EventMap.ui.initForm(containerId) initialisiert das Modal einmalig
|
||||
// (analog zu js/ui/detail.js). EventMap.ui.openPlaceForm(placeOrNull) öffnet
|
||||
// es: ohne Argument im "Neu anlegen"-Modus, mit einem Place-Objekt im
|
||||
// "Bearbeiten"-Modus (überschreibt den bestehenden Eintrag statt einen
|
||||
// neuen anzulegen).
|
||||
//
|
||||
// Rendering ausschließlich über textContent/DOM-APIs (el()), niemals
|
||||
// innerHTML mit Formular-/Place-Daten (siehe js/utils/dom.js).
|
||||
|
||||
(function () {
|
||||
const { getState, setState, persistPlaces } = EventMap.store;
|
||||
const { normalizePlace } = EventMap.models;
|
||||
const { COST_TYPES, ENVIRONMENT_TYPES } = EventMap.models;
|
||||
const { CATEGORIES } = EventMap.config;
|
||||
const { el, isSafeHttpUrl, isValidCoordinate } = EventMap.utils;
|
||||
|
||||
const COST_LABELS = {
|
||||
free: 'Kostenlos',
|
||||
paid: 'Kostenpflichtig',
|
||||
mixed: 'Teilweise kostenpflichtig',
|
||||
unknown: 'Keine Angabe',
|
||||
};
|
||||
|
||||
const ENVIRONMENT_LABELS = {
|
||||
indoor: 'Drinnen (Indoor)',
|
||||
outdoor: 'Draußen (Outdoor)',
|
||||
mixed: 'Drinnen & draußen',
|
||||
};
|
||||
|
||||
// Felder, für die ein eigenes Fehler-Element existiert (siehe fieldWrapper()).
|
||||
const ERROR_FIELDS = [
|
||||
'name',
|
||||
'category',
|
||||
'latitude',
|
||||
'longitude',
|
||||
'ageMin',
|
||||
'ageMax',
|
||||
'duration',
|
||||
'image',
|
||||
'website',
|
||||
];
|
||||
|
||||
let container = null;
|
||||
let refs = null;
|
||||
let editingPlace = null;
|
||||
|
||||
function fieldWrapper(id, labelText, inputNode, { required = false } = {}) {
|
||||
inputNode.id = id;
|
||||
if (required) {
|
||||
inputNode.setAttribute('required', '');
|
||||
inputNode.setAttribute('aria-required', 'true');
|
||||
}
|
||||
|
||||
const errorEl = el('p', {
|
||||
className: 'form-field__error',
|
||||
attrs: { id: `${id}-error`, role: 'alert' },
|
||||
});
|
||||
errorEl.hidden = true;
|
||||
inputNode.setAttribute('aria-describedby', `${id}-error`);
|
||||
|
||||
const labelChildren = [labelText];
|
||||
if (required) {
|
||||
labelChildren.push(
|
||||
el('span', { className: 'form-field__required', attrs: { 'aria-hidden': 'true' } }, [' *'])
|
||||
);
|
||||
}
|
||||
|
||||
const wrapper = el('div', { className: 'form-field' }, [
|
||||
el('label', { className: 'form-field__label', attrs: { for: id } }, labelChildren),
|
||||
inputNode,
|
||||
errorEl,
|
||||
]);
|
||||
|
||||
return { wrapper, input: inputNode, errorEl };
|
||||
}
|
||||
|
||||
function textInput(type = 'text', extraAttrs = {}) {
|
||||
return el('input', { attrs: { type, ...extraAttrs } });
|
||||
}
|
||||
|
||||
function buildCategorySelect() {
|
||||
const select = el('select', {}, [
|
||||
el('option', { attrs: { value: '' }, text: '-- Bitte wählen --' }),
|
||||
...CATEGORIES.map((cat) =>
|
||||
el('option', { attrs: { value: cat.id }, text: `${cat.icon} ${cat.label}` })
|
||||
),
|
||||
]);
|
||||
return select;
|
||||
}
|
||||
|
||||
function buildCostTypeSelect() {
|
||||
return el(
|
||||
'select',
|
||||
{},
|
||||
COST_TYPES.map((type) =>
|
||||
el('option', { attrs: { value: type }, text: COST_LABELS[type] || type })
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function buildEnvironmentSelect() {
|
||||
return el(
|
||||
'select',
|
||||
{},
|
||||
ENVIRONMENT_TYPES.map((type) =>
|
||||
el('option', { attrs: { value: type }, text: ENVIRONMENT_LABELS[type] || type })
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function checkboxField(id, labelText) {
|
||||
const input = el('input', { attrs: { type: 'checkbox', id } });
|
||||
const wrapper = el('label', { className: 'form-checkbox', attrs: { for: id } }, [
|
||||
input,
|
||||
labelText,
|
||||
]);
|
||||
return { wrapper, input };
|
||||
}
|
||||
|
||||
function section(legendText, children) {
|
||||
return el('fieldset', { className: 'place-form__section' }, [
|
||||
el('legend', { className: 'place-form__section-label', text: legendText }),
|
||||
...children,
|
||||
]);
|
||||
}
|
||||
|
||||
function row(children) {
|
||||
return el('div', { className: 'place-form__row' }, children);
|
||||
}
|
||||
|
||||
function buildSkeleton(rootContainer) {
|
||||
const nodeRefs = {};
|
||||
|
||||
nodeRefs.backdrop = el('div', {
|
||||
className: 'place-form-modal__backdrop',
|
||||
on: { click: () => closeForm() },
|
||||
});
|
||||
|
||||
nodeRefs.closeBtn = el(
|
||||
'button',
|
||||
{
|
||||
className: 'place-form-modal__close',
|
||||
attrs: { type: 'button', 'aria-label': 'Formular schließen' },
|
||||
on: { click: () => closeForm() },
|
||||
},
|
||||
['✕']
|
||||
);
|
||||
|
||||
nodeRefs.title = el('h2', {
|
||||
className: 'place-form__title',
|
||||
attrs: { id: 'place-form-title' },
|
||||
});
|
||||
|
||||
// Basisdaten
|
||||
const nameField = fieldWrapper('pf-name', 'Name', textInput('text'), { required: true });
|
||||
const categoryField = fieldWrapper('pf-category', 'Kategorie', buildCategorySelect(), {
|
||||
required: true,
|
||||
});
|
||||
const shortDescriptionField = fieldWrapper(
|
||||
'pf-short-description',
|
||||
'Kurzbeschreibung',
|
||||
textInput('text')
|
||||
);
|
||||
const descriptionField = fieldWrapper(
|
||||
'pf-description',
|
||||
'Ausführliche Beschreibung',
|
||||
el('textarea', {})
|
||||
);
|
||||
|
||||
// Adresse / Position
|
||||
const addressField = fieldWrapper('pf-address', 'Adresse', textInput('text'));
|
||||
const cityField = fieldWrapper('pf-city', 'Ort', textInput('text'));
|
||||
const latitudeField = fieldWrapper(
|
||||
'pf-latitude',
|
||||
'Breitengrad',
|
||||
textInput('number', { step: 'any' }),
|
||||
{ required: true }
|
||||
);
|
||||
const longitudeField = fieldWrapper(
|
||||
'pf-longitude',
|
||||
'Längengrad',
|
||||
textInput('number', { step: 'any' }),
|
||||
{ required: true }
|
||||
);
|
||||
|
||||
// Altersgruppe / Kosten / Öffnungszeiten / Dauer
|
||||
const ageMinField = fieldWrapper(
|
||||
'pf-age-min',
|
||||
'Altersgruppe – ab (Jahre)',
|
||||
textInput('number', { step: '1', min: '0' })
|
||||
);
|
||||
const ageMaxField = fieldWrapper(
|
||||
'pf-age-max',
|
||||
'Altersgruppe – bis (Jahre)',
|
||||
textInput('number', { step: '1', min: '0' })
|
||||
);
|
||||
const costTypeField = fieldWrapper('pf-cost-type', 'Kosten', buildCostTypeSelect());
|
||||
const costDescriptionField = fieldWrapper(
|
||||
'pf-cost-description',
|
||||
'Kosten – Details',
|
||||
textInput('text')
|
||||
);
|
||||
const openingHoursField = fieldWrapper('pf-opening-hours', 'Öffnungszeiten', textInput('text'));
|
||||
const durationField = fieldWrapper(
|
||||
'pf-duration',
|
||||
'Aufenthaltsdauer (Minuten)',
|
||||
textInput('number', { step: '1', min: '0' })
|
||||
);
|
||||
|
||||
// Ausstattung
|
||||
const strollerCheckbox = checkboxField('pf-facility-stroller', 'Kinderwagen geeignet');
|
||||
const wheelchairCheckbox = checkboxField('pf-facility-wheelchair', 'Barrierefrei');
|
||||
const toiletsCheckbox = checkboxField('pf-facility-toilets', 'Toiletten vorhanden');
|
||||
const parkingCheckbox = checkboxField('pf-facility-parking', 'Parkplatz vorhanden');
|
||||
const restaurantCheckbox = checkboxField('pf-facility-restaurant', 'Gastronomie vorhanden');
|
||||
|
||||
const environmentField = fieldWrapper('pf-environment', 'Indoor / Outdoor', buildEnvironmentSelect());
|
||||
|
||||
// Weitere Informationen
|
||||
const imageField = fieldWrapper('pf-image', 'Bild-URL', textInput('url'));
|
||||
const websiteField = fieldWrapper('pf-website', 'Website', textInput('url'));
|
||||
const tagsField = fieldWrapper(
|
||||
'pf-tags',
|
||||
'Tags (kommagetrennt)',
|
||||
textInput('text')
|
||||
);
|
||||
|
||||
nodeRefs.cancelBtn = el(
|
||||
'button',
|
||||
{ className: 'btn btn--secondary', attrs: { type: 'button' }, on: { click: () => closeForm() } },
|
||||
['Abbrechen']
|
||||
);
|
||||
nodeRefs.submitBtn = el(
|
||||
'button',
|
||||
{ className: 'btn btn--primary', attrs: { type: 'submit' } },
|
||||
['Ausflugsziel speichern']
|
||||
);
|
||||
|
||||
nodeRefs.form = el(
|
||||
'form',
|
||||
{ className: 'place-form', attrs: { id: 'place-form', novalidate: '' }, on: { submit: handleSubmit } },
|
||||
[
|
||||
section('Basisdaten', [
|
||||
nameField.wrapper,
|
||||
categoryField.wrapper,
|
||||
shortDescriptionField.wrapper,
|
||||
descriptionField.wrapper,
|
||||
]),
|
||||
section('Adresse & Position', [
|
||||
addressField.wrapper,
|
||||
cityField.wrapper,
|
||||
row([latitudeField.wrapper, longitudeField.wrapper]),
|
||||
]),
|
||||
section('Altersgruppe, Kosten & Zeiten', [
|
||||
row([ageMinField.wrapper, ageMaxField.wrapper]),
|
||||
row([costTypeField.wrapper, costDescriptionField.wrapper]),
|
||||
openingHoursField.wrapper,
|
||||
durationField.wrapper,
|
||||
]),
|
||||
section('Ausstattung', [
|
||||
el('div', { className: 'place-form__checkbox-group' }, [
|
||||
strollerCheckbox.wrapper,
|
||||
wheelchairCheckbox.wrapper,
|
||||
toiletsCheckbox.wrapper,
|
||||
parkingCheckbox.wrapper,
|
||||
restaurantCheckbox.wrapper,
|
||||
]),
|
||||
environmentField.wrapper,
|
||||
]),
|
||||
section('Weitere Informationen', [
|
||||
imageField.wrapper,
|
||||
websiteField.wrapper,
|
||||
tagsField.wrapper,
|
||||
]),
|
||||
el('div', { className: 'place-form__actions' }, [nodeRefs.cancelBtn, nodeRefs.submitBtn]),
|
||||
]
|
||||
);
|
||||
|
||||
nodeRefs.panel = el(
|
||||
'div',
|
||||
{
|
||||
className: 'place-form-modal__panel',
|
||||
attrs: { role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'place-form-title' },
|
||||
},
|
||||
[nodeRefs.closeBtn, nodeRefs.title, nodeRefs.form]
|
||||
);
|
||||
|
||||
rootContainer.appendChild(nodeRefs.backdrop);
|
||||
rootContainer.appendChild(nodeRefs.panel);
|
||||
|
||||
// Flache Referenzen für Lesen/Schreiben/Fehleranzeige der Feldwerte.
|
||||
nodeRefs.name = nameField.input;
|
||||
nodeRefs.nameError = nameField.errorEl;
|
||||
nodeRefs.category = categoryField.input;
|
||||
nodeRefs.categoryError = categoryField.errorEl;
|
||||
nodeRefs.shortDescription = shortDescriptionField.input;
|
||||
nodeRefs.description = descriptionField.input;
|
||||
nodeRefs.address = addressField.input;
|
||||
nodeRefs.city = cityField.input;
|
||||
nodeRefs.latitude = latitudeField.input;
|
||||
nodeRefs.latitudeError = latitudeField.errorEl;
|
||||
nodeRefs.longitude = longitudeField.input;
|
||||
nodeRefs.longitudeError = longitudeField.errorEl;
|
||||
nodeRefs.ageMin = ageMinField.input;
|
||||
nodeRefs.ageMinError = ageMinField.errorEl;
|
||||
nodeRefs.ageMax = ageMaxField.input;
|
||||
nodeRefs.ageMaxError = ageMaxField.errorEl;
|
||||
nodeRefs.costType = costTypeField.input;
|
||||
nodeRefs.costDescription = costDescriptionField.input;
|
||||
nodeRefs.openingHours = openingHoursField.input;
|
||||
nodeRefs.duration = durationField.input;
|
||||
nodeRefs.durationError = durationField.errorEl;
|
||||
nodeRefs.stroller = strollerCheckbox.input;
|
||||
nodeRefs.wheelchair = wheelchairCheckbox.input;
|
||||
nodeRefs.toilets = toiletsCheckbox.input;
|
||||
nodeRefs.parking = parkingCheckbox.input;
|
||||
nodeRefs.restaurant = restaurantCheckbox.input;
|
||||
nodeRefs.environment = environmentField.input;
|
||||
nodeRefs.image = imageField.input;
|
||||
nodeRefs.imageError = imageField.errorEl;
|
||||
nodeRefs.website = websiteField.input;
|
||||
nodeRefs.websiteError = websiteField.errorEl;
|
||||
nodeRefs.tags = tagsField.input;
|
||||
|
||||
return nodeRefs;
|
||||
}
|
||||
|
||||
function parseOptionalNumber(raw) {
|
||||
const trimmed = (raw || '').trim();
|
||||
if (trimmed === '') return null;
|
||||
const num = Number(trimmed);
|
||||
return Number.isFinite(num) ? num : NaN;
|
||||
}
|
||||
|
||||
function readValues() {
|
||||
return {
|
||||
name: refs.name.value.trim(),
|
||||
category: refs.category.value,
|
||||
shortDescription: refs.shortDescription.value.trim(),
|
||||
description: refs.description.value.trim(),
|
||||
address: refs.address.value.trim(),
|
||||
city: refs.city.value.trim(),
|
||||
latitude: parseOptionalNumber(refs.latitude.value),
|
||||
longitude: parseOptionalNumber(refs.longitude.value),
|
||||
ageMin: parseOptionalNumber(refs.ageMin.value),
|
||||
ageMax: parseOptionalNumber(refs.ageMax.value),
|
||||
costType: refs.costType.value,
|
||||
costDescription: refs.costDescription.value.trim(),
|
||||
openingHours: refs.openingHours.value.trim(),
|
||||
duration: parseOptionalNumber(refs.duration.value),
|
||||
stroller: refs.stroller.checked,
|
||||
wheelchair: refs.wheelchair.checked,
|
||||
toilets: refs.toilets.checked,
|
||||
parking: refs.parking.checked,
|
||||
restaurant: refs.restaurant.checked,
|
||||
environment: refs.environment.value,
|
||||
image: refs.image.value.trim(),
|
||||
website: refs.website.value.trim(),
|
||||
tags: refs.tags.value,
|
||||
};
|
||||
}
|
||||
|
||||
/** Validiert die gelesenen Formularwerte. Gibt ein Objekt {feldname: Fehlermeldung} zurück. */
|
||||
function validate(values) {
|
||||
const errors = {};
|
||||
|
||||
if (!values.name) {
|
||||
errors.name = 'Bitte einen Namen angeben.';
|
||||
}
|
||||
if (!values.category) {
|
||||
errors.category = 'Bitte eine Kategorie auswählen.';
|
||||
}
|
||||
|
||||
if (values.latitude === null) {
|
||||
errors.latitude = 'Bitte einen Breitengrad angeben.';
|
||||
} else if (Number.isNaN(values.latitude) || values.latitude < -90 || values.latitude > 90) {
|
||||
errors.latitude = 'Breitengrad muss eine Zahl zwischen -90 und 90 sein.';
|
||||
}
|
||||
|
||||
if (values.longitude === null) {
|
||||
errors.longitude = 'Bitte einen Längengrad angeben.';
|
||||
} else if (Number.isNaN(values.longitude) || values.longitude < -180 || values.longitude > 180) {
|
||||
errors.longitude = 'Längengrad muss eine Zahl zwischen -180 und 180 sein.';
|
||||
}
|
||||
|
||||
if (!errors.latitude && !errors.longitude && !isValidCoordinate(values.latitude, values.longitude)) {
|
||||
errors.latitude = 'Koordinaten sind ungültig.';
|
||||
}
|
||||
|
||||
if (values.ageMin !== null && (Number.isNaN(values.ageMin) || values.ageMin < 0)) {
|
||||
errors.ageMin = 'Bitte ein gültiges Alter (mindestens 0) angeben.';
|
||||
}
|
||||
if (values.ageMax !== null && (Number.isNaN(values.ageMax) || values.ageMax < 0)) {
|
||||
errors.ageMax = 'Bitte ein gültiges Alter (mindestens 0) angeben.';
|
||||
}
|
||||
if (
|
||||
!errors.ageMin &&
|
||||
!errors.ageMax &&
|
||||
values.ageMin !== null &&
|
||||
values.ageMax !== null &&
|
||||
values.ageMin > values.ageMax
|
||||
) {
|
||||
errors.ageMax = 'Der Wert "bis" darf nicht kleiner als "ab" sein.';
|
||||
}
|
||||
|
||||
if (values.duration !== null && (Number.isNaN(values.duration) || values.duration < 0)) {
|
||||
errors.duration = 'Bitte eine gültige Aufenthaltsdauer in Minuten (mindestens 0) angeben.';
|
||||
}
|
||||
|
||||
if (values.image && !isSafeHttpUrl(values.image)) {
|
||||
errors.image = 'Bitte eine gültige Bild-URL (http/https) angeben.';
|
||||
}
|
||||
if (values.website && !isSafeHttpUrl(values.website)) {
|
||||
errors.website = 'Bitte eine gültige Website-URL (http/https) angeben.';
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
function clearErrors() {
|
||||
for (const key of ERROR_FIELDS) {
|
||||
const input = refs[key];
|
||||
const errorEl = refs[`${key}Error`];
|
||||
if (!input || !errorEl) continue;
|
||||
input.removeAttribute('aria-invalid');
|
||||
errorEl.textContent = '';
|
||||
errorEl.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
function showErrors(errors) {
|
||||
clearErrors();
|
||||
for (const [key, message] of Object.entries(errors)) {
|
||||
const input = refs[key];
|
||||
const errorEl = refs[`${key}Error`];
|
||||
if (!input || !errorEl) continue;
|
||||
input.setAttribute('aria-invalid', 'true');
|
||||
errorEl.textContent = message;
|
||||
errorEl.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
function focusFirstError(errors) {
|
||||
const firstKey = ERROR_FIELDS.find((key) => errors[key]);
|
||||
if (firstKey && refs[firstKey]) refs[firstKey].focus();
|
||||
}
|
||||
|
||||
/** Baut aus Formularwerten + optionaler Basis (bestehendes Place-Objekt) ein Rohobjekt für normalizePlace(). */
|
||||
function buildRawPlace(values, base) {
|
||||
return {
|
||||
...base,
|
||||
name: values.name,
|
||||
category: values.category,
|
||||
shortDescription: values.shortDescription,
|
||||
description: values.description,
|
||||
address: values.address,
|
||||
city: values.city,
|
||||
latitude: values.latitude,
|
||||
longitude: values.longitude,
|
||||
ageRecommendation: { min: values.ageMin, max: values.ageMax },
|
||||
cost: { type: values.costType, description: values.costDescription },
|
||||
openingHours: values.openingHours,
|
||||
durationMinutes: values.duration,
|
||||
facilities: {
|
||||
strollerAccessible: values.stroller,
|
||||
wheelchairAccessible: values.wheelchair,
|
||||
toilets: values.toilets,
|
||||
parking: values.parking,
|
||||
restaurant: values.restaurant,
|
||||
},
|
||||
environment: values.environment,
|
||||
image: values.image,
|
||||
website: values.website,
|
||||
tags: values.tags
|
||||
.split(',')
|
||||
.map((tag) => tag.trim())
|
||||
.filter(Boolean),
|
||||
// Explizit zurücksetzen: normalizePlace() setzt bei Bearbeiten dadurch
|
||||
// einen neuen Zeitstempel, statt den aus `base` übernommenen zu behalten.
|
||||
updatedAt: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function savePlace(normalized) {
|
||||
const state = getState();
|
||||
const places = editingPlace
|
||||
? state.places.map((p) => (p.id === normalized.id ? normalized : p))
|
||||
: [...state.places, normalized];
|
||||
|
||||
setState({ places });
|
||||
persistPlaces();
|
||||
}
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const values = readValues();
|
||||
const errors = validate(values);
|
||||
showErrors(errors);
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
focusFirstError(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
const base = editingPlace ? { ...editingPlace } : {};
|
||||
const normalized = normalizePlace(buildRawPlace(values, base));
|
||||
savePlace(normalized);
|
||||
closeForm();
|
||||
}
|
||||
|
||||
function prefillForm(place) {
|
||||
refs.name.value = place.name || '';
|
||||
refs.category.value = place.category || '';
|
||||
refs.shortDescription.value = place.shortDescription || '';
|
||||
refs.description.value = place.description || '';
|
||||
refs.address.value = place.address || '';
|
||||
refs.city.value = place.city || '';
|
||||
refs.latitude.value = place.latitude ?? '';
|
||||
refs.longitude.value = place.longitude ?? '';
|
||||
|
||||
const age = place.ageRecommendation || {};
|
||||
refs.ageMin.value = age.min ?? '';
|
||||
refs.ageMax.value = age.max ?? '';
|
||||
|
||||
const cost = place.cost || {};
|
||||
refs.costType.value = cost.type || 'unknown';
|
||||
refs.costDescription.value = cost.description || '';
|
||||
|
||||
refs.openingHours.value = place.openingHours || '';
|
||||
refs.duration.value = place.durationMinutes ?? '';
|
||||
|
||||
const facilities = place.facilities || {};
|
||||
refs.stroller.checked = Boolean(facilities.strollerAccessible);
|
||||
refs.wheelchair.checked = Boolean(facilities.wheelchairAccessible);
|
||||
refs.toilets.checked = Boolean(facilities.toilets);
|
||||
refs.parking.checked = Boolean(facilities.parking);
|
||||
refs.restaurant.checked = Boolean(facilities.restaurant);
|
||||
|
||||
refs.environment.value = place.environment || 'outdoor';
|
||||
refs.image.value = place.image || '';
|
||||
refs.website.value = place.website || '';
|
||||
refs.tags.value = Array.isArray(place.tags) ? place.tags.join(', ') : '';
|
||||
}
|
||||
|
||||
/** Öffnet das Formular. Ohne Argument (bzw. mit null) im "Neu anlegen"-Modus, sonst im Bearbeiten-Modus. */
|
||||
function openPlaceForm(place) {
|
||||
if (!container || !refs) {
|
||||
console.error('EventMap: Formular wurde noch nicht initialisiert (initForm()).');
|
||||
return;
|
||||
}
|
||||
|
||||
editingPlace = place || null;
|
||||
clearErrors();
|
||||
refs.form.reset();
|
||||
|
||||
if (editingPlace) {
|
||||
refs.title.textContent = 'Ausflugsziel bearbeiten';
|
||||
refs.submitBtn.textContent = 'Änderungen speichern';
|
||||
prefillForm(editingPlace);
|
||||
} else {
|
||||
refs.title.textContent = 'Neues Ausflugsziel hinzufügen';
|
||||
refs.submitBtn.textContent = 'Ausflugsziel speichern';
|
||||
refs.category.value = '';
|
||||
refs.costType.value = 'unknown';
|
||||
refs.environment.value = 'outdoor';
|
||||
}
|
||||
|
||||
container.hidden = false;
|
||||
document.body.classList.add('place-form-modal-open');
|
||||
refs.name.focus();
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
if (!container || container.hidden) return;
|
||||
container.hidden = true;
|
||||
document.body.classList.remove('place-form-modal-open');
|
||||
editingPlace = null;
|
||||
}
|
||||
|
||||
/** Initialisiert das Formular-Modal im Element mit der übergebenen id. */
|
||||
function initForm(containerId) {
|
||||
const containerEl = document.getElementById(containerId);
|
||||
if (!containerEl) {
|
||||
console.error(`EventMap: Formular-Container #${containerId} nicht gefunden.`);
|
||||
return;
|
||||
}
|
||||
|
||||
container = containerEl;
|
||||
refs = buildSkeleton(container);
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape' && !container.hidden) {
|
||||
closeForm();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
EventMap.ui.initForm = initForm;
|
||||
EventMap.ui.openPlaceForm = openPlaceForm;
|
||||
})();
|
||||
Reference in New Issue
Block a user