117 lines
3.3 KiB
JavaScript
117 lines
3.3 KiB
JavaScript
const INDICATOR_ID = 'loading-indicator'
|
|
const HIDDEN_CLASS = 'invisible'
|
|
const GRACE_PERIOD_MS = 500
|
|
const DEBOUNCE_MS = 200
|
|
|
|
let debounceTimeout = null
|
|
let historyRestoreGracePeriod = false
|
|
let savedScrollPosition = null
|
|
let activeRequests = 0
|
|
|
|
function getIndicator() {
|
|
return document.getElementById(INDICATOR_ID)
|
|
}
|
|
|
|
function show() {
|
|
const indicator = getIndicator()
|
|
if (indicator) {
|
|
indicator.classList.remove(HIDDEN_CLASS)
|
|
}
|
|
}
|
|
|
|
function hide() {
|
|
if (debounceTimeout) {
|
|
clearTimeout(debounceTimeout)
|
|
debounceTimeout = null
|
|
}
|
|
const indicator = getIndicator()
|
|
if (indicator) {
|
|
indicator.classList.add(HIDDEN_CLASS)
|
|
}
|
|
}
|
|
|
|
function handleBeforeRequest(event) {
|
|
// Ignore requests triggered during history restore grace period
|
|
if (true === historyRestoreGracePeriod) {
|
|
return
|
|
}
|
|
|
|
activeRequests++
|
|
|
|
// Save scroll position of the target container before HTMX request
|
|
const target = event.detail?.target || document.getElementById('main-content')
|
|
if (target && typeof target.scrollTop === 'number') {
|
|
savedScrollPosition = { element: target, top: target.scrollTop }
|
|
}
|
|
|
|
// Don't start a new debounce if already pending
|
|
if (debounceTimeout) {
|
|
return
|
|
}
|
|
|
|
// Show indicator after delay
|
|
debounceTimeout = setTimeout(() => {
|
|
show()
|
|
}, DEBOUNCE_MS)
|
|
}
|
|
|
|
function handleRequestEnd() {
|
|
// Counter may already be 0 if a force-reset (history save, page show) fired
|
|
// before this afterRequest — ignore the stale completion in that case
|
|
if (0 === activeRequests) {
|
|
return
|
|
}
|
|
|
|
activeRequests--
|
|
if (0 === activeRequests) {
|
|
hide()
|
|
}
|
|
}
|
|
|
|
function handleAfterSwap() {
|
|
// Restore scroll position after HTMX swaps content
|
|
if (savedScrollPosition !== null && savedScrollPosition.element) {
|
|
savedScrollPosition.element.scrollTop = savedScrollPosition.top
|
|
savedScrollPosition = null
|
|
}
|
|
}
|
|
|
|
function handleHistoryRestore() {
|
|
activeRequests = 0
|
|
hide()
|
|
|
|
// Set grace period to ignore change events triggered by history restore
|
|
historyRestoreGracePeriod = true
|
|
setTimeout(() => {
|
|
historyRestoreGracePeriod = false
|
|
}, GRACE_PERIOD_MS)
|
|
}
|
|
|
|
function handleTimeout() {
|
|
// Don't decrement here — htmx:afterRequest always fires alongside htmx:timeout
|
|
// and handles the counter. This handler only shows the alert.
|
|
alert('Die Anfrage hat zu lange gedauert. Bitte versuchen Sie es erneut. Falls das Problem weiterhin besteht, kontaktieren Sie bitte unseren Support.')
|
|
}
|
|
|
|
function handleBeforeHistorySave() {
|
|
// Hide indicator before HTMX saves the page to history cache
|
|
activeRequests = 0
|
|
hide()
|
|
}
|
|
|
|
function handlePageShow() {
|
|
activeRequests = 0
|
|
hide()
|
|
}
|
|
|
|
// Initialize event listeners
|
|
document.body.addEventListener('htmx:beforeRequest', handleBeforeRequest)
|
|
document.body.addEventListener('htmx:afterRequest', handleRequestEnd)
|
|
document.body.addEventListener('htmx:afterSwap', handleAfterSwap)
|
|
document.body.addEventListener('htmx:timeout', handleTimeout)
|
|
document.body.addEventListener('htmx:historyRestore', handleHistoryRestore)
|
|
document.body.addEventListener('htmx:beforeHistorySave', handleBeforeHistorySave)
|
|
|
|
window.addEventListener('pageshow', handlePageShow)
|
|
window.addEventListener('popstate', handleHistoryRestore)
|