diff --git a/contao/templates/frontend/event_filter.html.twig b/contao/templates/frontend/event_filter.html.twig index 9198a1d..29296d1 100644 --- a/contao/templates/frontend/event_filter.html.twig +++ b/contao/templates/frontend/event_filter.html.twig @@ -124,6 +124,8 @@ const orgWidget = orgSelect?.closest('.widget-select'); const resetButton = filters.querySelector('#eventfilter-reset'); const status = filters.querySelector('#eventfilter-status'); + const stateStorageKey = 'event-filter-state'; + const stateQueryKey = 'event_filter'; const animationMs = 220; let hideTimers = new WeakMap(); @@ -182,6 +184,109 @@ const locationTom = initTomSelect(locationSelect); const orgTom = initTomSelect(orgSelect); + const hasOptionValue = (selectElement, value) => { + if (!selectElement) { + return false; + } + + return Array.from(selectElement.options).some((option) => option.value === value); + }; + + const rawValueToFilterState = (rawValue) => { + const value = (rawValue || '').trim(); + + if (!value) { + return { type: 'all', value: '' }; + } + + if (value.startsWith('tag-') && hasOptionValue(tagSelect, value)) { + return { type: 'tag', value: value.replace('tag-', '') }; + } + + if (value.startsWith('location-') && hasOptionValue(locationSelect, value)) { + return { type: 'location', value: value.replace('location-', '') }; + } + + if (value.startsWith('org-') && hasOptionValue(orgSelect, value)) { + return { type: 'org', value: value.replace('org-', '') }; + } + + return { type: 'all', value: '' }; + }; + + const filterStateToRawValue = (filterState) => { + if (!filterState.value || filterState.type === 'all') { + return ''; + } + + if (filterState.type === 'tag') { + return `tag-${filterState.value}`; + } + + if (filterState.type === 'location') { + return `location-${filterState.value}`; + } + + if (filterState.type === 'org') { + return `org-${filterState.value}`; + } + + return ''; + }; + + const readUrlState = () => { + const url = new URL(window.location.href); + return (url.searchParams.get(stateQueryKey) || '').trim(); + }; + + const writeUrlState = (value) => { + const url = new URL(window.location.href); + + if (value) { + url.searchParams.set(stateQueryKey, value); + } else { + url.searchParams.delete(stateQueryKey); + } + + window.history.replaceState(window.history.state, '', url); + }; + + const readStoredState = () => { + try { + return (window.sessionStorage.getItem(stateStorageKey) || '').trim(); + } catch (error) { + return ''; + } + }; + + const writeStoredState = (value) => { + try { + if (value) { + window.sessionStorage.setItem(stateStorageKey, value); + } else { + window.sessionStorage.removeItem(stateStorageKey); + } + } catch (error) { + // noop + } + }; + + const syncState = (filterState) => { + const rawValue = filterStateToRawValue(filterState); + writeStoredState(rawValue); + writeUrlState(rawValue); + }; + + const applyControlState = (filterState) => { + const tagValue = filterState.type === 'tag' ? `tag-${filterState.value}` : ''; + const locationValue = filterState.type === 'location' ? `location-${filterState.value}` : ''; + const orgValue = filterState.type === 'org' ? `org-${filterState.value}` : ''; + + setSelectValue(tagSelect, tagTom, tagValue); + setSelectValue(locationSelect, locationTom, locationValue); + setSelectValue(orgSelect, orgTom, orgValue); + }; + const setSelectValue = (selectElement, tomInstance, value) => { if (!selectElement) { return; @@ -314,6 +419,7 @@ }); updateStatus(filterState); + syncState(filterState); }; tagSelect?.addEventListener('change', () => { @@ -375,5 +481,16 @@ applyFilter({ type: 'all', value: '' }); }); - applyFilter(currentFilter); + const urlState = readUrlState(); + const storedState = readStoredState(); + const initialState = rawValueToFilterState(urlState || storedState); + + applyControlState(initialState); + applyFilter(initialState); + + window.addEventListener('popstate', () => { + const stateFromUrl = rawValueToFilterState(readUrlState()); + applyControlState(stateFromUrl); + applyFilter(stateFromUrl); + });