Files
eventmap/js/utils/dom.js
T
dapelza 2812e03530 Phase 2: Detailansicht
Modal, das sich bei Marker- oder Listen-Auswahl (state.selectedPlaceId)
oeffnet: Titelbild/Platzhalter, vollstaendige Beschreibung, Adresse,
Mini-Karte mit Positionsmarker, Oeffnungszeiten, Kosten, Altersempfehlung,
Ausstattung (inkl. klar markierter negativer Werte), Hinweise fuer
Familien, Website-Link. Navigation-Button zu OSM Directions. Loeschen ist
bereits voll funktional (mit Bestaetigung), Bearbeiten ist als Platzhalter
fuer Phase 3 markiert.

Neuer Helper isSafeHttpUrl() in dom.js: nur http/https als Link- oder
Bildquelle zulaessig, schuetzt vor javascript:-URLs in Fremddaten
(Vorbereitung fuer KI-JSON-Import). Rendering weiterhin ausschliesslich
ueber textContent/el(), kein innerHTML mit Place-Daten.
2026-07-24 11:32:32 +02:00

114 lines
3.7 KiB
JavaScript

// Kleine DOM-Helfer. Wichtig: Daten aus Places werden NIE per innerHTML
// eingefügt, sondern immer über textContent bzw. DOM-APIs (el()).
//
// Klassisches Script (kein ES-Modul): Die App wird per file:// direkt aus
// einem synchronisierten Ordner geöffnet, daher können keine ES-Module
// (import/export) verwendet werden (Browser blockieren deren Nachladen
// unter file:// per CORS-Policy). Stattdessen hängen alle Dateien ihre
// öffentliche API an das globale Namespace-Objekt window.EventMap.
// Diese Datei wird als erste geladen und legt den Namespace an.
window.EventMap = window.EventMap || {};
window.EventMap.utils = window.EventMap.utils || {};
window.EventMap.config = window.EventMap.config || {};
window.EventMap.models = window.EventMap.models || {};
window.EventMap.store = window.EventMap.store || {};
window.EventMap.map = window.EventMap.map || {};
window.EventMap.ui = window.EventMap.ui || {};
(function () {
/**
* Erzeugt ein DOM-Element.
* @param {string} tag Tag-Name, z.B. 'div'
* @param {object} [props] Eigenschaften: className, attrs{}, dataset{}, style{},
* text (setzt textContent), on{ click: fn, ... } für Event-Listener
* @param {Array<Node|string>} [children] Kindknoten oder Strings (werden als Text angehängt)
*/
function el(tag, props = {}, children = []) {
const node = document.createElement(tag);
if (props.className) {
node.className = props.className;
}
if (props.text !== undefined) {
node.textContent = props.text;
}
if (props.attrs) {
for (const [key, value] of Object.entries(props.attrs)) {
if (value !== undefined && value !== null) {
node.setAttribute(key, value);
}
}
}
if (props.dataset) {
for (const [key, value] of Object.entries(props.dataset)) {
node.dataset[key] = value;
}
}
if (props.style) {
Object.assign(node.style, props.style);
}
if (props.on) {
for (const [event, handler] of Object.entries(props.on)) {
node.addEventListener(event, handler);
}
}
for (const child of children) {
if (child === null || child === undefined) continue;
if (typeof child === 'string') {
node.appendChild(document.createTextNode(child));
} else {
node.appendChild(child);
}
}
return node;
}
/**
* Escaped HTML-Sonderzeichen. Nur als Sicherheitsnetz gedacht, falls an
* einzelnen Stellen doch einmal mit HTML-Strings gearbeitet werden muss
* (z.B. Leaflet-Popups, die intern innerHTML nutzen). Für normales
* Rendering bitte el()/textContent verwenden.
*/
function escapeHtml(value) {
const str = String(value ?? '');
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
/** Entfernt alle Kindknoten eines Elements. */
function clearChildren(node) {
while (node.firstChild) {
node.removeChild(node.firstChild);
}
}
/**
* Prüft, ob eine URL sicher als Link/Bildquelle verwendet werden darf
* (Scheme-Whitelist: nur http/https). Schützt vor javascript:-URLs & Co.
* in Nutzer-/KI-generierten Feldern wie place.website oder place.image.
*/
function isSafeHttpUrl(value) {
if (typeof value !== 'string' || value.trim() === '') {
return false;
}
try {
const parsed = new URL(value);
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
} catch (err) {
return false;
}
}
EventMap.utils.el = el;
EventMap.utils.escapeHtml = escapeHtml;
EventMap.utils.clearChildren = clearChildren;
EventMap.utils.isSafeHttpUrl = isSafeHttpUrl;
})();