Compare commits

..

1 Commits

Author SHA1 Message Date
Jürgen Mummert 621ce8dc8b fix(map): simplify filters and restore style toggle behavior 2026-02-26 18:26:20 +01:00
6 changed files with 141 additions and 138 deletions
+5 -5
View File
@@ -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'] = [
+1 -1
View File
@@ -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)',
+1 -1
View File
@@ -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)',
+30 -16
View File
@@ -9,12 +9,13 @@
>
<button
type="button"
class="eventmanager-map-filter__toggle"
class="eventmanager-map-filter__toggle is-expanded"
data-map-filter-toggle="1"
aria-expanded="true"
aria-controls="{{ mapFilterGroupId|e('html_attr') }}"
>
Bereiche ausblenden
<span class="eventmanager-map-filter__toggle-label eventmanager-map-filter__toggle-label--expand">Filter einblenden</span>
<span class="eventmanager-map-filter__toggle-label eventmanager-map-filter__toggle-label--collapse">Filter ausblenden</span>
</button>
<div
@@ -23,29 +24,43 @@
role="group"
aria-label="Organisationstypen"
>
<button
type="button"
class="eventmanager-map-filter__tag is-active"
data-map-filter-action="all"
aria-pressed="true"
>Alle</button>
{% if tags is iterable and tags|length > 0 %}
{% for tag in tags %}
<button
type="button"
class="eventmanager-map-filter__tag is-active"
class="eventmanager-map-filter__tag"
data-map-tag-filter="{{ tag.id|e('html_attr') }}"
aria-pressed="true"
aria-pressed="false"
>{{ tag.label|e }}</button>
{% endfor %}
{% endif %}
<button
type="button"
class="eventmanager-map-filter__tag is-active"
class="eventmanager-map-filter__tag"
data-map-event-toggle="1"
aria-pressed="true"
aria-pressed="false"
>Veranstaltungen</button>
</div>
<div class="eventmanager-map-filter__actions">
<button type="button" data-map-filter-action="all">Alle auswählen</button>
<button type="button" data-map-filter-action="none">Alle abwählen</button>
<button type="button" data-map-style-mode="street" aria-pressed="true">Straße</button>
<button type="button" data-map-style-mode="satellite" aria-pressed="false">Satellit</button>
<button
type="button"
class="eventmanager-map-filter__tag"
data-map-style-mode="street"
aria-pressed="true"
>Straße</button>
<button
type="button"
class="eventmanager-map-filter__tag"
data-map-style-mode="satellite"
aria-pressed="false"
>Satellit</button>
</div>
</section>
@@ -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 @@
</style>
<script type="application/json" id="{{ mapDataElementId|e('html_attr') }}">{{ mapItemsJson|raw }}</script>
<script type="module" src="/bundles/mummertmediaeventmanager/assets/map-module.js"></script>
<script type="module" src="/bundles/mummertmediaeventmanager/assets/map-module.js?v=20260226"></script>
+100 -111
View File
@@ -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;
@@ -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;
}
}