Phase 0+1: Scaffold, Datenmodell, Karte und Liste
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)
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// Ergebnisliste (Cards), rendert aus store.state.places. Filterung folgt
|
||||
// in einer späteren Phase - aktuell werden alle Places angezeigt.
|
||||
// Rendering ausschließlich über textContent/DOM-APIs (el()), niemals
|
||||
// innerHTML mit Place-Daten.
|
||||
|
||||
(function () {
|
||||
const { subscribe, getState, setState } = EventMap.store;
|
||||
const { getCategoryById } = EventMap.config;
|
||||
const { el, clearChildren } = EventMap.utils;
|
||||
|
||||
function createCard(place) {
|
||||
const category = getCategoryById(place.category);
|
||||
|
||||
const select = () => setState({ selectedPlaceId: place.id });
|
||||
|
||||
return el(
|
||||
'li',
|
||||
{
|
||||
className: 'place-card',
|
||||
attrs: { tabindex: '0', role: 'button' },
|
||||
dataset: { placeId: place.id },
|
||||
on: {
|
||||
click: select,
|
||||
keydown: (event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
select();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
[
|
||||
el('h3', { className: 'place-card__name', text: place.name }),
|
||||
el('span', {
|
||||
className: 'place-card__category',
|
||||
style: { backgroundColor: category.color },
|
||||
text: `${category.icon} ${category.label}`,
|
||||
}),
|
||||
el('p', { className: 'place-card__desc', text: place.shortDescription }),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
function render(container, places) {
|
||||
clearChildren(container);
|
||||
|
||||
if (!places || places.length === 0) {
|
||||
container.appendChild(
|
||||
el('p', { className: 'list-empty', text: 'Keine Ausflugsziele vorhanden.' })
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const list = el('ul', { className: 'place-list' });
|
||||
for (const place of places) {
|
||||
list.appendChild(createCard(place));
|
||||
}
|
||||
container.appendChild(list);
|
||||
}
|
||||
|
||||
/** Initialisiert die Liste im Element mit der übergebenen id und abonniert den Store. */
|
||||
function initList(containerId) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) {
|
||||
console.error(`EventMap: List-Container #${containerId} nicht gefunden.`);
|
||||
return;
|
||||
}
|
||||
|
||||
render(container, getState().places);
|
||||
|
||||
subscribe((state) => {
|
||||
render(container, state.places);
|
||||
});
|
||||
}
|
||||
|
||||
EventMap.ui.initList = initList;
|
||||
})();
|
||||
Reference in New Issue
Block a user