Add configurable target event list ID for event filter module

This commit is contained in:
Jürgen Mummert
2026-02-22 11:27:26 +01:00
parent 3a24b24b84
commit e3cc85115b
5 changed files with 223 additions and 3 deletions
@@ -1,5 +1,5 @@
<div id="eventfilters">
<button type="button" data-filter-tag="all">Alle</button>
<div id="eventfilters" data-eventlist-id="{{ targetEventListId|default('eventlist')|e('html_attr') }}">
<button type="button" data-filter-tag="all" class="active">Alle</button>
{% for tag in tagButtons|default([]) %}
<button type="button" data-filter-tag="{{ tag.id }}">{{ tag.title }} ({{ tag.count }})</button>
@@ -25,3 +25,182 @@
</select>
</div>
</div>
<style>
.event-filter-target-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 1rem;
}
.event-filter-target-list .event {
opacity: 1;
transform: translateY(0);
transition: opacity 220ms ease, transform 220ms ease;
will-change: opacity, transform;
}
.event-filter-target-list .event.is-filtered-out {
opacity: 0;
transform: translateY(6px);
pointer-events: none;
}
#eventfilters .active,
#eventfilters select.active {
outline: 2px solid currentColor;
outline-offset: 2px;
}
</style>
<script type="module">
const filters = document.getElementById('eventfilters');
if (!filters) {
throw new Error('Event filter requires #eventfilters.');
}
const targetEventListId = filters.dataset.eventlistId || 'eventlist';
const list = document.getElementById(targetEventListId);
if (!list) {
console.warn(`[event_filter] Target event list #${targetEventListId} was not found.`);
}
list?.classList.add('event-filter-target-list');
const events = list ? Array.from(list.querySelectorAll(':scope > .event')) : [];
const tagButtons = Array.from(filters.querySelectorAll('button[data-filter-tag]'));
const locationSelect = filters.querySelector('#location-filter');
const orgSelect = filters.querySelector('#org-filter');
const animationMs = 220;
let hideTimers = new WeakMap();
let currentFilter = { type: 'tag', value: 'all' };
const clearHideTimer = (eventItem) => {
const timer = hideTimers.get(eventItem);
if (timer) {
window.clearTimeout(timer);
hideTimers.delete(eventItem);
}
};
const showEvent = (eventItem) => {
clearHideTimer(eventItem);
eventItem.hidden = false;
requestAnimationFrame(() => {
eventItem.classList.remove('is-filtered-out');
});
};
const hideEvent = (eventItem) => {
clearHideTimer(eventItem);
eventItem.classList.add('is-filtered-out');
const timer = window.setTimeout(() => {
eventItem.hidden = true;
hideTimers.delete(eventItem);
}, animationMs);
hideTimers.set(eventItem, timer);
};
const setActiveControl = ({ type, value }) => {
tagButtons.forEach((button) => {
button.classList.toggle('active', type === 'tag' && button.dataset.filterTag === value);
});
if (locationSelect) {
locationSelect.classList.toggle('active', type === 'location');
}
if (orgSelect) {
orgSelect.classList.toggle('active', type === 'org');
}
};
const parseIdList = (rawValue) => (rawValue ?? '')
.split(',')
.map((value) => value.trim())
.filter(Boolean);
const matches = (eventItem, filterState) => {
if (filterState.value === 'all') {
return true;
}
if (filterState.type === 'tag') {
return parseIdList(eventItem.dataset.tags).includes(filterState.value);
}
if (filterState.type === 'location') {
return eventItem.dataset.location === filterState.value;
}
if (filterState.type === 'org') {
return parseIdList(eventItem.dataset.org).includes(filterState.value);
}
return true;
};
const applyFilter = (filterState) => {
currentFilter = filterState;
setActiveControl(filterState);
events.forEach((eventItem) => {
if (matches(eventItem, filterState)) {
showEvent(eventItem);
} else {
hideEvent(eventItem);
}
});
};
tagButtons.forEach((button) => {
button.addEventListener('click', () => {
if (locationSelect) {
locationSelect.value = 'all';
}
if (orgSelect) {
orgSelect.value = 'all';
}
applyFilter({ type: 'tag', value: button.dataset.filterTag ?? 'all' });
});
});
locationSelect?.addEventListener('change', () => {
const selectedValue = locationSelect.value;
if (orgSelect) {
orgSelect.value = 'all';
}
applyFilter(
selectedValue === 'all'
? { type: 'tag', value: 'all' }
: { type: 'location', value: selectedValue.replace('location-', '') },
);
});
orgSelect?.addEventListener('change', () => {
const selectedValue = orgSelect.value;
if (locationSelect) {
locationSelect.value = 'all';
}
applyFilter(
selectedValue === 'all'
? { type: 'tag', value: 'all' }
: { type: 'org', value: selectedValue.replace('org-', '') },
);
});
applyFilter(currentFilter);
</script>