Reines HTML5/CSS3/JavaScript ohne Build-Tooling, lauffaehig direkt per Doppelklick auf index.html (file://, kein Server noetig): - Datenmodell fuer Ausflugsziele mit allen Feldern aus der Anforderung, 15 feste Kategorien inkl. Farbe/Icon - Zentraler Pub/Sub-Store mit LocalStorage-Persistenz und Migrations-Grundgeruest - Interaktive Karte (Leaflet + Leaflet.markercluster, lokal vendored) mit kategoriefarbenen Markern, Clustering, Popups, Geolocation-Button - Kartengrundlage ueber CARTO-Basemaps statt tile.openstreetmap.org, da dessen Referer-Pflicht file://-Aufrufe blockiert - Seed-Daten als eingebettetes Script (data/seed.js) statt JSON-Datei, da fetch() auf lokale Dateien unter file:// nicht zuverlaessig funktioniert - Ergebnisliste mit 10 Beispiel-Ausflugszielen, sicher gerendert ueber textContent/DOM-APIs statt innerHTML (Vorbereitung fuer spaeteren KI-JSON-Import mit Fremddaten) - Responsives Layout (Desktop nebeneinander, Mobile gestapelt)
65 lines
2.0 KiB
JavaScript
65 lines
2.0 KiB
JavaScript
/* global L */
|
|
// Erzeugt Leaflet-Marker für Places inkl. Kategorie-Icon (L.divIcon) und
|
|
// Popup-Inhalt. Popup-Inhalte werden als DOM-Nodes (nicht als HTML-String)
|
|
// erzeugt, damit Place-Daten niemals ungesichert als innerHTML landen.
|
|
|
|
(function () {
|
|
const { getCategoryById } = EventMap.config;
|
|
const { getPlaceCategory, getPlaceLatLng } = EventMap.models;
|
|
const { el } = EventMap.utils;
|
|
|
|
const iconCache = new Map();
|
|
|
|
function buildDivIcon(category) {
|
|
return L.divIcon({
|
|
className: 'eventmap-marker',
|
|
html: `<span class="eventmap-marker__glyph" style="background:${category.color}">${category.icon}</span>`,
|
|
iconSize: [32, 32],
|
|
iconAnchor: [16, 16],
|
|
popupAnchor: [0, -16],
|
|
});
|
|
}
|
|
|
|
function getIconForCategory(categoryId) {
|
|
if (!iconCache.has(categoryId)) {
|
|
iconCache.set(categoryId, buildDivIcon(getCategoryById(categoryId)));
|
|
}
|
|
return iconCache.get(categoryId);
|
|
}
|
|
|
|
function buildPopupContent(place) {
|
|
const category = getPlaceCategory(place);
|
|
|
|
return el('div', { className: 'eventmap-popup' }, [
|
|
el('strong', { className: 'eventmap-popup__title', text: place.name }),
|
|
el('div', {
|
|
className: 'eventmap-popup__category',
|
|
style: { color: category.color },
|
|
text: `${category.icon} ${category.label}`,
|
|
}),
|
|
el('p', { className: 'eventmap-popup__desc', text: place.shortDescription }),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Erzeugt einen Leaflet-Marker für ein Place-Objekt.
|
|
* @param {object} place
|
|
* @param {{onSelect?: (placeId: string) => void}} [handlers]
|
|
*/
|
|
function createMarkerForPlace(place, { onSelect } = {}) {
|
|
const { lat, lng } = getPlaceLatLng(place);
|
|
const marker = L.marker([lat, lng], { icon: getIconForCategory(place.category) });
|
|
|
|
marker.bindPopup(buildPopupContent(place));
|
|
marker.on('click', () => {
|
|
if (onSelect) {
|
|
onSelect(place.id);
|
|
}
|
|
});
|
|
|
|
return marker;
|
|
}
|
|
|
|
EventMap.map.createMarkerForPlace = createMarkerForPlace;
|
|
})();
|