Files
Hugo_MAP-NSU-Komplex/themes/op-dz/assets/js/map.js
T
2026-08-18 15:32:41 +02:00

196 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =========================================================================
OP·Karte Leaflet + OpenStreetMap
- farbige Kreis-Marker je Kategorie, Clustering (markercluster)
- Kategorie-Filter (Checkboxen) + Textsuche, synchron mit der Card-Liste
- Klick auf Card öffnet den zugehörigen Marker
Daten kommen aus <script type="application/json" id="op-map-data">.
========================================================================= */
(function () {
"use strict";
function init() {
var el = document.getElementById("op-map");
if (!el || typeof L === "undefined") return;
var dataNode = document.getElementById("op-map-data");
if (!dataNode) return;
var payload = JSON.parse(dataNode.textContent || "{}");
var places = payload.places || [];
var colors = payload.colors || {};
var defaultColor = payload.defaultColor || "#1e2758";
var tileUrl = payload.tileUrl || "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
var attribution = payload.attribution ||
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>-Mitwirkende';
var map = L.map(el, { scrollWheelZoom: false });
L.tileLayer(tileUrl, { maxZoom: 19, attribution: attribution }).addTo(map);
map.on("focus", function () { map.scrollWheelZoom.enable(); });
map.on("blur", function () { map.scrollWheelZoom.disable(); });
var useCluster = places.length > 1 && typeof L.markerClusterGroup === "function";
var group = useCluster
? L.markerClusterGroup({ maxClusterRadius: 45, showCoverageOnHover: false })
: L.featureGroup();
map.addLayer(group);
var byUrl = {};
var records = [];
places.forEach(function (p) {
if (typeof p.lat !== "number" || typeof p.lng !== "number") return;
var cat = (p.categories && p.categories[0]) || "";
var color = colors[cat] || defaultColor;
var marker = L.marker([p.lat, p.lng], {
icon: pinIcon(color),
riseOnHover: true,
keyboard: true,
title: p.title, // nativer Browser-Tooltip / a11y
alt: "Schauplatz: " + p.title
});
// Popup ohne autoPan, damit die Karte beim Hover nicht springt
marker.bindPopup(popupHtml(p, cat, color), { autoPan: false, closeButton: true, minWidth: 250, maxWidth: 250 });
// Beim Drüberfahren (Desktop) öffnet sich die Kurzinfo; Tap/Klick ebenso
marker.on("mouseover", function () { marker.openPopup(); });
var rec = { p: p, marker: marker, cats: p.categories || [], text: (p.title + " " + (p.subtitle || "") + " " + (p.address || "")).toLowerCase() };
records.push(rec);
if (p.url) byUrl[p.url] = rec;
});
var allBounds = records.length ? L.latLngBounds(records.map(function (r) { return r.marker.getLatLng(); })) : null;
// --- Filter-Status ---
var activeCats = null; // null = alle
var query = "";
function visible(rec) {
if (query && rec.text.indexOf(query) === -1) return false;
if (activeCats) {
var ok = rec.cats.some(function (c) { return activeCats[c]; });
if (!ok) return false;
}
return true;
}
function apply() {
group.clearLayers();
var shown = 0, bounds = L.latLngBounds([]);
records.forEach(function (rec) {
var v = visible(rec);
if (v) { group.addLayer(rec.marker); shown++; bounds.extend(rec.marker.getLatLng()); }
});
// Cards synchronisieren
cards.forEach(function (card) {
var url = card.getAttribute("data-url");
var rec = url && byUrl[url];
var show;
if (rec) show = visible(rec);
else {
// Cards ohne Koordinaten: nur Textsuche greift
var t = (card.getAttribute("data-text") || "").toLowerCase();
show = !query || t.indexOf(query) !== -1;
}
card.classList.toggle("is--hidden", !show);
});
var countCards = cards.filter(function (c) { return !c.classList.contains("is--hidden"); }).length;
if (countEl) countEl.textContent = countCards;
if (bounds.isValid()) map.fitBounds(bounds.pad(0.15));
else if (allBounds) map.fitBounds(allBounds.pad(0.15));
}
// --- DOM-Hooks ---
var cards = Array.prototype.slice.call(document.querySelectorAll("[data-op-card]"));
var countEl = document.querySelector("[data-op-count]");
var searchEl = document.querySelector("[data-op-search]");
var filterEl = document.querySelector("[data-op-map-filter]");
if (filterEl) {
var stateEl = document.querySelector("[data-op-filter-state]");
var wordAll = (stateEl && stateEl.getAttribute("data-word-all")) || "alle";
var wordOf = (stateEl && stateEl.getAttribute("data-word-of")) || "von";
var updateState = function () {
if (!stateEl) return;
var boxes = filterEl.querySelectorAll("input[type=checkbox]");
var total = boxes.length, on = 0;
boxes.forEach(function (cb) { if (cb.checked) on++; });
stateEl.textContent = on === total ? "· " + wordAll : "· " + on + " " + wordOf + " " + total;
};
var updateCats = function () {
var boxes = filterEl.querySelectorAll("input[type=checkbox]");
activeCats = {};
var anyUnchecked = false;
boxes.forEach(function (cb) { activeCats[cb.value] = cb.checked; if (!cb.checked) anyUnchecked = true; });
if (!anyUnchecked) activeCats = null; // alle aktiv → kein Filter
updateState();
apply();
};
filterEl.addEventListener("change", updateCats);
updateState(); // Anfangszustand im eingeklappten Filter anzeigen
// Schnellauswahl: alle auswählen / abwählen
var setAll = function (state) {
filterEl.querySelectorAll("input[type=checkbox]").forEach(function (cb) { cb.checked = state; });
updateCats();
};
var btnAll = document.querySelector("[data-op-filter-all]");
var btnNone = document.querySelector("[data-op-filter-none]");
if (btnAll) btnAll.addEventListener("click", function () { setAll(true); });
if (btnNone) btnNone.addEventListener("click", function () { setAll(false); });
}
if (searchEl) {
var deb;
searchEl.addEventListener("input", function () {
clearTimeout(deb);
deb = setTimeout(function () { query = searchEl.value.trim().toLowerCase(); apply(); }, 150);
});
}
// Cards sind normale Links → Klick führt direkt zur Detailseite.
// (Die Verortung passiert über die Marker/Popups auf der Karte.)
// Vorbelegung aus URL (?q=…), z.B. von der Kopf-Suche
try {
var q0 = new URLSearchParams(location.search).get("q");
if (q0) { query = q0.trim().toLowerCase(); if (searchEl) searchEl.value = q0; }
} catch (e) {}
// Erstanzeige (apply() befüllt die Layer-Gruppe und setzt den Ausschnitt)
if (!records.length) map.setView([50.9, 11.6], 6);
apply();
}
function pinIcon(color) {
var svg =
'<svg class="op-pin" width="30" height="42" viewBox="0 0 30 42" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
'<path d="M15 41 C15 41 3 24 3 14 A12 12 0 1 1 27 14 C27 24 15 41 15 41 Z" fill="' + color + '" stroke="#ffffff" stroke-width="2.5"/>' +
'<circle cx="15" cy="14" r="5" fill="#ffffff"/>' +
'</svg>';
return L.divIcon({
html: svg,
className: "op-pinwrap",
iconSize: [30, 42],
iconAnchor: [15, 41],
popupAnchor: [0, -36]
});
}
function popupHtml(p, cat, color) {
var tag = p.url ? "a" : "div";
var href = p.url ? ' href="' + p.url + '"' : "";
var h = "<" + tag + ' class="op-popup"' + href + ">";
if (p.img) h += '<span class="op-popup__img" style="background-image:url(\'' + p.img + '\')"></span>';
h += '<span class="op-popup__body">';
h += '<span class="op-popup__title">' + esc(p.title) + "</span>";
var meta = p.address || p.subtitle || "";
if (meta) h += '<span class="op-popup__addr">' + esc(meta) + "</span>";
h += "</span></" + tag + ">";
return h;
}
function esc(s) {
return String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/</g, "&lt;")
.replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
if (document.readyState !== "loading") init();
else document.addEventListener("DOMContentLoaded", init);
})();