diff --git a/contao/dca/tl_module.php b/contao/dca/tl_module.php
index 3aaa269..b5a3abf 100644
--- a/contao/dca/tl_module.php
+++ b/contao/dca/tl_module.php
@@ -9,7 +9,7 @@ use Contao\StringUtil;
$GLOBALS['TL_DCA']['tl_module']['palettes']['member_organizations'] = '{title_legend},name,headline,type;{eventmanager_legend},editPage;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
$GLOBALS['TL_DCA']['tl_module']['palettes']['member_events'] = '{title_legend},name,headline,type;{eventmanager_legend},editPage;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
$GLOBALS['TL_DCA']['tl_module']['palettes']['event_filter'] = '{title_legend},name,headline,type;{eventmanager_legend},cal_calendar,eventListDomId;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
-$GLOBALS['TL_DCA']['tl_module']['palettes']['eventmanager_map'] = '{title_legend},name,headline,type;{eventmanager_legend},mapShowOrganizations,mapShowExternalOrganizations,mapShowEvents,mapEventColor,mapOrganizationColorScheme,mapCenterMode;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
+$GLOBALS['TL_DCA']['tl_module']['palettes']['eventmanager_map'] = '{title_legend},name,headline,type;{eventmanager_legend},mapShowOrganizations,mapShowExternalOrganizations,mapShowEvents,mapEventColor,mapOrganizationColor,mapCenterMode;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
$GLOBALS['TL_DCA']['tl_module']['palettes']['organization_edit'] = '{title_legend},name,headline,type;{eventmanager_legend},listPage,logoFolder,organizationTypeTags;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
$GLOBALS['TL_DCA']['tl_module']['palettes']['event_edit'] = '{title_legend},name,headline,type;{eventmanager_legend},listPage,eventFolder,termsPage,frontendAuthorId,frontendArchiveId,eventTypeTags;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
@@ -195,12 +195,12 @@ $GLOBALS['TL_DCA']['tl_module']['fields']['mapEventColor'] = [
'sql' => ['type' => 'string', 'length' => 7, 'default' => '#BC5067'],
];
-$GLOBALS['TL_DCA']['tl_module']['fields']['mapOrganizationColorScheme'] = [
- 'label' => &$GLOBALS['TL_LANG']['tl_module']['mapOrganizationColorScheme'],
+$GLOBALS['TL_DCA']['tl_module']['fields']['mapOrganizationColor'] = [
+ 'label' => &$GLOBALS['TL_LANG']['tl_module']['mapOrganizationColor'],
'exclude' => true,
'inputType' => 'text',
- 'eval' => ['maxlength' => 1024, 'tl_class' => 'clr long'],
- 'sql' => ['type' => 'string', 'length' => 1024, 'default' => ''],
+ 'eval' => ['maxlength' => 7, 'rgxp' => 'hexcolor', 'colorpicker' => true, 'tl_class' => 'w50'],
+ 'sql' => ['type' => 'string', 'length' => 7, 'default' => '#BC5067'],
];
$GLOBALS['TL_DCA']['tl_module']['fields']['mapCenterMode'] = [
diff --git a/contao/languages/de/tl_module.php b/contao/languages/de/tl_module.php
index 6d9dedd..17729e3 100644
--- a/contao/languages/de/tl_module.php
+++ b/contao/languages/de/tl_module.php
@@ -17,7 +17,7 @@ $GLOBALS['TL_LANG']['tl_module']['mapShowOrganizations'] = ['Organisationen anze
$GLOBALS['TL_LANG']['tl_module']['mapShowExternalOrganizations'] = ['Externe Organisationen anzeigen', 'Wenn aktiviert, werden externe Organisationen (isExternal=1) zusätzlich auf der Karte dargestellt. Standard: nein.'];
$GLOBALS['TL_LANG']['tl_module']['mapShowEvents'] = ['Veranstaltungen anzeigen', 'Wenn aktiviert, werden Event- (inkl. Orts-) Marker auf der Karte dargestellt.'];
$GLOBALS['TL_LANG']['tl_module']['mapEventColor'] = ['Event-Farbe (Kreise/Linien)', 'Farbe für Event-Cluster, Event-Punkte und Spiderfy-Verbindungslinien (Hex, z. B. #BC5067).'];
-$GLOBALS['TL_LANG']['tl_module']['mapOrganizationColorScheme'] = ['Farbschema (Organisationstypen)', 'Kommagetrennte Farben für Organisationstypen/Tags, z. B. ff6600,77dd33,ff99bb. Markerfarbe richtet sich nach dem ersten Tag.'];
+$GLOBALS['TL_LANG']['tl_module']['mapOrganizationColor'] = ['Organisationsfarbe', 'Einheitliche Farbe für alle Organisations-Marker (Hex, z. B. #BC5067).'];
$GLOBALS['TL_LANG']['tl_module']['mapCenterMode'] = ['Karten-Zentrierung', 'Wählen Sie, ob die Karte anhand der Marker oder mit festen Koordinaten zentriert werden soll.'];
$GLOBALS['TL_LANG']['tl_module']['mapCenterMode_options'] = [
'markers' => 'Anhand der Marker (alle Marker sichtbar)',
diff --git a/contao/languages/en/tl_module.php b/contao/languages/en/tl_module.php
index 4a69d86..cfdb33e 100644
--- a/contao/languages/en/tl_module.php
+++ b/contao/languages/en/tl_module.php
@@ -17,7 +17,7 @@ $GLOBALS['TL_LANG']['tl_module']['mapShowOrganizations'] = ['Show organizations'
$GLOBALS['TL_LANG']['tl_module']['mapShowExternalOrganizations'] = ['Show external organizations', 'If enabled, external organizations (isExternal=1) are additionally rendered on the map. Default: no.'];
$GLOBALS['TL_LANG']['tl_module']['mapShowEvents'] = ['Show events', 'If enabled, event markers (including related locations) are rendered on the map.'];
$GLOBALS['TL_LANG']['tl_module']['mapEventColor'] = ['Event color (circles/lines)', 'Color for event clusters, event points and spiderfy connector lines (hex, e.g. #BC5067).'];
-$GLOBALS['TL_LANG']['tl_module']['mapOrganizationColorScheme'] = ['Color scheme (organization types)', 'Comma-separated colors for organization type tags, e.g. ff6600,77dd33,ff99bb. Marker color follows the first tag.'];
+$GLOBALS['TL_LANG']['tl_module']['mapOrganizationColor'] = ['Organization color', 'Unified color for all organization markers (hex, e.g. #BC5067).'];
$GLOBALS['TL_LANG']['tl_module']['mapCenterMode'] = ['Map centering', 'Choose whether the map should center by markers or fixed coordinates.'];
$GLOBALS['TL_LANG']['tl_module']['mapCenterMode_options'] = [
'markers' => 'By markers (fit all visible markers)',
diff --git a/contao/templates/frontend/event_map.html.twig b/contao/templates/frontend/event_map.html.twig
index 5749aa7..e426462 100644
--- a/contao/templates/frontend/event_map.html.twig
+++ b/contao/templates/frontend/event_map.html.twig
@@ -9,12 +9,13 @@
>
+
+
{% if tags is iterable and tags|length > 0 %}
{% for tag in tags %}
{% endfor %}
{% endif %}
-
-
-
-
-
-
+
+
+
@@ -57,7 +72,7 @@
data-map-style="{{ mapStyleUrl|e('html_attr') }}"
data-map-data-id="{{ mapDataElementId|e('html_attr') }}"
data-map-event-color="{{ mapEventColor|default('#BC5067')|e('html_attr') }}"
- data-map-organization-colors="{{ mapOrganizationColorScheme|default('')|e('html_attr') }}"
+ data-map-organization-color="{{ mapOrganizationColor|default('#BC5067')|e('html_attr') }}"
data-map-center-mode="{{ mapCenterMode|default('markers')|e('html_attr') }}"
data-map-center-lat="{{ mapCenterLat|default('')|e('html_attr') }}"
data-map-center-lng="{{ mapCenterLng|default('')|e('html_attr') }}"
@@ -71,8 +86,7 @@
margin-bottom: .75rem;
}
- .eventmanager-map-filter__group,
- .eventmanager-map-filter__actions {
+ .eventmanager-map-filter__group {
display: flex;
flex-wrap: wrap;
gap: .5rem;
@@ -89,4 +103,4 @@
-
+
diff --git a/public/assets/map-module.js b/public/assets/map-module.js
index f51c1c2..bed0213 100644
--- a/public/assets/map-module.js
+++ b/public/assets/map-module.js
@@ -199,70 +199,6 @@ const normalizeHexColor = (value, fallback = DEFAULT_EVENT_COLOR) => {
return fallback;
};
-const normalizeHexColorOrNull = (value) => {
- const fallback = '__INVALID__';
- const normalized = normalizeHexColor(value, fallback);
-
- return normalized === fallback ? null : normalized;
-};
-
-const parseOrganizationColorScheme = (value) => {
- const raw = String(value || '').trim();
-
- if ('' === raw) {
- return [];
- }
-
- return raw
- .split(',')
- .map((part) => normalizeHexColorOrNull(part))
- .filter((part) => null !== part);
-};
-
-const buildOrganizationTagColorMap = (container, colors, organizationItems) => {
- if (!colors.length) {
- return {
- byTagId: {},
- fallbackColor: null,
- };
- }
-
- const fallbackColor = colors[0];
- const wrapperId = container.dataset.mapFilterWrapperId || '';
- const wrapper = wrapperId ? document.getElementById(wrapperId) : null;
- let orderedTagIds = [];
-
- if (wrapper) {
- orderedTagIds = Array.from(wrapper.querySelectorAll('[data-map-tag-filter]'))
- .map((button) => String(button.dataset.mapTagFilter || '').trim())
- .filter((value) => /^\d+$/.test(value));
- }
-
- if (!orderedTagIds.length) {
- const seen = new Set();
-
- organizationItems.forEach((item) => {
- const firstTagId = String(item?.extra?.organizationTagIds?.[0] || '').trim();
-
- if (/^\d+$/.test(firstTagId) && !seen.has(firstTagId)) {
- seen.add(firstTagId);
- orderedTagIds.push(firstTagId);
- }
- });
- }
-
- const byTagId = {};
-
- orderedTagIds.forEach((tagId, index) => {
- byTagId[tagId] = colors[index] || fallbackColor;
- });
-
- return {
- byTagId,
- fallbackColor,
- };
-};
-
const applyDefaultProjection = (map) => {
if (!map || typeof map.setProjection !== 'function') {
return;
@@ -441,21 +377,18 @@ const toFeature = (item) => ({
},
});
-const initOrganizationMarkers = (map, organizationItems, organizationTagColorMap) => {
+const initOrganizationMarkers = (map, organizationItems, organizationColor) => {
if (!organizationItems.length) {
return {
- setActiveTagIds: () => {},
+ setOnlyTagId: () => {},
setAllVisible: () => {},
+ setVisible: () => {},
};
}
const markerEntries = organizationItems.map((item) => {
const popupHtml = popupHtmlFor(item);
- const primaryTagId = String(item?.extra?.organizationTagIds?.[0] || '').trim();
- const configuredColor = /^\d+$/.test(primaryTagId)
- ? (organizationTagColorMap?.byTagId?.[primaryTagId] || organizationTagColorMap?.fallbackColor || null)
- : null;
- const marker = new maplibregl.Marker(configuredColor ? { color: configuredColor } : undefined)
+ const marker = new maplibregl.Marker(organizationColor ? { color: organizationColor } : undefined)
.setLngLat([item.longitude, item.latitude]);
if (popupHtml) {
@@ -471,8 +404,9 @@ const initOrganizationMarkers = (map, organizationItems, organizationTagColorMap
});
return {
- setActiveTagIds: (activeTagIds) => {
- const activeSet = new Set(normalizeTagIds(activeTagIds));
+ setOnlyTagId: (activeTagId) => {
+ const normalizedTagId = String(activeTagId ?? '').trim();
+ const hasActiveTag = /^\d+$/.test(normalizedTagId);
markerEntries.forEach((entry) => {
const markerElement = entry.marker.getElement();
@@ -482,8 +416,9 @@ const initOrganizationMarkers = (map, organizationItems, organizationTagColorMap
}
const hasTags = entry.tagIds.length > 0;
- const isVisible = !hasTags
- || entry.tagIds.some((tagId) => activeSet.has(tagId));
+ const isVisible = !hasActiveTag
+ || !hasTags
+ || entry.tagIds.includes(normalizedTagId);
markerElement.style.display = isVisible ? '' : 'none';
});
@@ -499,6 +434,17 @@ const initOrganizationMarkers = (map, organizationItems, organizationTagColorMap
markerElement.style.display = '';
});
},
+ setVisible: (isVisible) => {
+ markerEntries.forEach((entry) => {
+ const markerElement = entry.marker.getElement();
+
+ if (!markerElement) {
+ return;
+ }
+
+ markerElement.style.display = isVisible ? '' : 'none';
+ });
+ },
};
};
@@ -517,17 +463,13 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event
const tagButtons = Array.from(wrapper.querySelectorAll('[data-map-tag-filter]'));
- const collectActiveTagIds = () => tagButtons
- .filter((button) => button.getAttribute('aria-pressed') === 'true')
- .map((button) => String(button.dataset.mapTagFilter || '').trim())
- .filter((value) => /^\d+$/.test(value));
-
const setButtonState = (button, isActive) => {
button.setAttribute('aria-pressed', isActive ? 'true' : 'false');
button.classList.toggle('is-active', isActive);
};
const eventToggleButton = wrapper.querySelector('[data-map-event-toggle="1"]');
+ const actionAll = wrapper.querySelector('[data-map-filter-action="all"]');
const setEventButtonState = (isActive) => {
if (!eventToggleButton) {
@@ -538,48 +480,80 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event
eventToggleButton.classList.toggle('is-active', isActive);
};
+ const setAllButtonState = (isActive) => {
+ if (!actionAll) {
+ return;
+ }
+
+ actionAll.setAttribute('aria-pressed', isActive ? 'true' : 'false');
+ actionAll.classList.toggle('is-active', isActive);
+ };
+
+ let activeTagId = null;
+ let eventsOnly = false;
+
const applyFilter = () => {
- if (!tagButtons.length) {
- organizationMarkerManager.setAllVisible();
+ if (eventsOnly) {
+ organizationMarkerManager.setVisible(false);
+ setAllButtonState(false);
+
+ if (eventLayerManager) {
+ eventLayerManager.setVisible(true);
+ }
return;
}
- organizationMarkerManager.setActiveTagIds(collectActiveTagIds());
+ organizationMarkerManager.setVisible(true);
+
+ if (activeTagId) {
+ organizationMarkerManager.setOnlyTagId(activeTagId);
+ setAllButtonState(false);
+
+ if (eventLayerManager) {
+ eventLayerManager.setVisible(false);
+ }
+
+ return;
+ }
+
+ organizationMarkerManager.setAllVisible();
+ setAllButtonState(true);
+
+ if (eventLayerManager) {
+ eventLayerManager.setVisible(true);
+ }
};
tagButtons.forEach((button) => {
button.addEventListener('click', () => {
+ const clickedTagId = String(button.dataset.mapTagFilter || '').trim();
+
+ if (!/^\d+$/.test(clickedTagId)) {
+ return;
+ }
+
const isActive = button.getAttribute('aria-pressed') === 'true';
- setButtonState(button, !isActive);
+ activeTagId = isActive ? null : clickedTagId;
+ eventsOnly = false;
+
+ tagButtons.forEach((otherButton) => {
+ const otherTagId = String(otherButton.dataset.mapTagFilter || '').trim();
+ setButtonState(otherButton, !!activeTagId && otherTagId === activeTagId);
+ });
+
+ setEventButtonState(false);
applyFilter();
});
});
- const actionAll = wrapper.querySelector('[data-map-filter-action="all"]');
- const actionNone = wrapper.querySelector('[data-map-filter-action="none"]');
-
if (actionAll) {
actionAll.addEventListener('click', () => {
- tagButtons.forEach((button) => setButtonState(button, true));
- applyFilter();
-
- if (eventLayerManager) {
- setEventButtonState(true);
- eventLayerManager.setVisible(true);
- }
- });
- }
-
- if (actionNone) {
- actionNone.addEventListener('click', () => {
+ activeTagId = null;
+ eventsOnly = false;
tagButtons.forEach((button) => setButtonState(button, false));
+ setEventButtonState(false);
applyFilter();
-
- if (eventLayerManager) {
- setEventButtonState(false);
- eventLayerManager.setVisible(false);
- }
});
}
@@ -593,17 +567,30 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event
toggleButton.setAttribute('aria-expanded', nextExpanded ? 'true' : 'false');
filterGroup.hidden = !nextExpanded;
- toggleButton.textContent = nextExpanded ? 'Bereiche ausblenden' : 'Bereiche anzeigen';
+ toggleButton.classList.toggle('is-expanded', nextExpanded);
+ toggleButton.classList.toggle('is-collapsed', !nextExpanded);
+
+ wrapper.classList.toggle('is-filter-expanded', nextExpanded);
+ wrapper.classList.toggle('is-filter-collapsed', !nextExpanded);
});
+
+ const initiallyExpanded = toggleButton.getAttribute('aria-expanded') !== 'false';
+ filterGroup.hidden = !initiallyExpanded;
+ toggleButton.classList.toggle('is-expanded', initiallyExpanded);
+ toggleButton.classList.toggle('is-collapsed', !initiallyExpanded);
+ wrapper.classList.toggle('is-filter-expanded', initiallyExpanded);
+ wrapper.classList.toggle('is-filter-collapsed', !initiallyExpanded);
}
if (eventToggleButton && eventLayerManager) {
eventToggleButton.addEventListener('click', () => {
const isActive = eventToggleButton.getAttribute('aria-pressed') === 'true';
- const nextActive = !isActive;
+ eventsOnly = !isActive;
+ activeTagId = null;
- setEventButtonState(nextActive);
- eventLayerManager.setVisible(nextActive);
+ tagButtons.forEach((button) => setButtonState(button, false));
+ setEventButtonState(eventsOnly);
+ applyFilter();
});
} else if (eventToggleButton) {
eventToggleButton.disabled = true;
@@ -1137,10 +1124,12 @@ const initSingleMap = (container) => {
const locationItems = items.filter((item) => item.type === 'location');
const eventItems = items.filter((item) => item.type === 'event');
const allCoordinates = items.map((item) => [item.longitude, item.latitude]);
- const organizationColors = parseOrganizationColorScheme(container.dataset.mapOrganizationColors);
- const organizationTagColorMap = buildOrganizationTagColorMap(container, organizationColors, organizationItems);
+ const organizationColor = normalizeHexColor(
+ container.dataset.mapOrganizationColor,
+ eventColor,
+ );
- const organizationMarkerManager = initOrganizationMarkers(map, organizationItems, organizationTagColorMap);
+ const organizationMarkerManager = initOrganizationMarkers(map, organizationItems, organizationColor);
const locationLayerManager = initLocationLayers(map, locationItems, eventColor);
let eventLayerManager = null;
diff --git a/src/Controller/Frontend/EventMapController.php b/src/Controller/Frontend/EventMapController.php
index d3e6db1..33bc5d6 100644
--- a/src/Controller/Frontend/EventMapController.php
+++ b/src/Controller/Frontend/EventMapController.php
@@ -35,7 +35,7 @@ class EventMapController extends AbstractFrontendModuleController
$showEvents = '1' === (string) ($model->mapShowEvents ?? '');
$centerMode = (string) ($model->mapCenterMode ?? self::DEFAULT_CENTER_MODE);
$eventColor = $this->normalizeHexColor((string) ($model->mapEventColor ?? self::DEFAULT_EVENT_COLOR));
- $organizationColorScheme = trim((string) ($model->mapOrganizationColorScheme ?? ''));
+ $organizationColor = $this->normalizeHexColor((string) ($model->mapOrganizationColor ?? $eventColor), $eventColor);
if (!in_array($centerMode, ['markers', 'custom'], true)) {
$centerMode = self::DEFAULT_CENTER_MODE;
@@ -48,7 +48,7 @@ class EventMapController extends AbstractFrontendModuleController
$template->set('mapStyleUrl', self::MAP_STYLE_URL);
$template->set('mapCenterMode', $centerMode);
$template->set('mapEventColor', $eventColor);
- $template->set('mapOrganizationColorScheme', $organizationColorScheme);
+ $template->set('mapOrganizationColor', $organizationColor);
$template->set('mapCenterLat', trim((string) ($model->mapCenterLat ?? '')));
$template->set('mapCenterLng', trim((string) ($model->mapCenterLng ?? '')));
$template->set('mapCenterZoom', (int) ($model->mapCenterZoom ?? 12));
@@ -61,7 +61,7 @@ class EventMapController extends AbstractFrontendModuleController
return $template->getResponse();
}
- private function normalizeHexColor(string $value): string
+ private function normalizeHexColor(string $value, string $fallback = self::DEFAULT_EVENT_COLOR): string
{
$normalized = strtoupper(trim($value));
@@ -91,6 +91,6 @@ class EventMapController extends AbstractFrontendModuleController
);
}
- return self::DEFAULT_EVENT_COLOR;
+ return $fallback;
}
}