fix: avoid loading indicator issues when using browser history for navigation
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import './bootstrap.js'
|
||||
import './loading.js'
|
||||
|
||||
import './styles/app.css'
|
||||
|
||||
import htmx from 'htmx.org'
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
import {Controller} from '@hotwired/stimulus'
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = ['indicator']
|
||||
static classes = ['hidden']
|
||||
static values = {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.debounceTimeout = null
|
||||
|
||||
// Bind event handlers to preserve context
|
||||
this.boundHandleBeforeRequest = this.handleBeforeRequest.bind(this)
|
||||
this.boundHandleAfterRequest = this.handleAfterRequest.bind(this)
|
||||
this.boundHandleTimeout = this.handleTimeout.bind(this)
|
||||
this.boundHandlePageShow = this.handlePageShow.bind(this)
|
||||
this.boundHandleHistoryRestore = this.handleHistoryRestore.bind(this)
|
||||
|
||||
// Listen to HTMX events
|
||||
document.body.addEventListener('htmx:beforeRequest', this.boundHandleBeforeRequest)
|
||||
document.body.addEventListener('htmx:afterRequest', this.boundHandleAfterRequest)
|
||||
document.body.addEventListener('htmx:timeout', this.boundHandleTimeout)
|
||||
document.body.addEventListener('htmx:historyRestore', this.boundHandleHistoryRestore)
|
||||
|
||||
// Listen for browser back/forward navigation
|
||||
window.addEventListener('pageshow', this.boundHandlePageShow)
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
// Clean up event listeners
|
||||
document.body.removeEventListener('htmx:beforeRequest', this.boundHandleBeforeRequest)
|
||||
document.body.removeEventListener('htmx:afterRequest', this.boundHandleAfterRequest)
|
||||
document.body.removeEventListener('htmx:timeout', this.boundHandleTimeout)
|
||||
document.body.removeEventListener('htmx:historyRestore', this.boundHandleHistoryRestore)
|
||||
window.removeEventListener('pageshow', this.boundHandlePageShow)
|
||||
|
||||
// Clear any pending timeout
|
||||
if (this.debounceTimeout) {
|
||||
clearTimeout(this.debounceTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
handleBeforeRequest() {
|
||||
// Don't start a new debounce if already visible
|
||||
if (true === this.visibleValue) {
|
||||
return
|
||||
}
|
||||
|
||||
// Show indicator after 200ms delay for field refreshes
|
||||
this.debounceTimeout = setTimeout(() => {
|
||||
this.show()
|
||||
}, 200)
|
||||
}
|
||||
|
||||
handleAfterRequest() {
|
||||
// Clear debounce timer if request completes before 200ms
|
||||
if (this.debounceTimeout) {
|
||||
clearTimeout(this.debounceTimeout)
|
||||
this.debounceTimeout = null
|
||||
}
|
||||
|
||||
this.hide()
|
||||
}
|
||||
|
||||
handleTimeout() {
|
||||
// Hide loading indicator
|
||||
this.hide()
|
||||
|
||||
// Show user-friendly error message
|
||||
alert('Die Anfrage hat zu lange gedauert. Bitte versuchen Sie es erneut. Falls das Problem weiterhin besteht, kontaktieren Sie bitte unseren Support.')
|
||||
}
|
||||
|
||||
handlePageShow(event) {
|
||||
// event.persisted is true when page is restored from bfcache (back/forward navigation)
|
||||
if (true === event.persisted) {
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
handleHistoryRestore() {
|
||||
// HTMX history restore - hide loading indicator
|
||||
this.hide()
|
||||
}
|
||||
|
||||
show() {
|
||||
this.visibleValue = true
|
||||
this.indicatorTarget.classList.remove(this.hiddenClass)
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.visibleValue = false
|
||||
this.indicatorTarget.classList.add(this.hiddenClass)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
const INDICATOR_ID = 'loading-indicator'
|
||||
const HIDDEN_CLASS = 'invisible'
|
||||
const GRACE_PERIOD_MS = 500
|
||||
const DEBOUNCE_MS = 200
|
||||
|
||||
let debounceTimeout = null
|
||||
let historyRestoreGracePeriod = false
|
||||
|
||||
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() {
|
||||
// Ignore requests triggered during history restore grace period
|
||||
if (true === historyRestoreGracePeriod) {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't start a new debounce if already pending
|
||||
if (debounceTimeout) {
|
||||
return
|
||||
}
|
||||
|
||||
// Show indicator after delay
|
||||
debounceTimeout = setTimeout(() => {
|
||||
show()
|
||||
}, DEBOUNCE_MS)
|
||||
}
|
||||
|
||||
function handleHistoryRestore() {
|
||||
hide()
|
||||
|
||||
// Set grace period to ignore change events triggered by history restore
|
||||
historyRestoreGracePeriod = true
|
||||
setTimeout(() => {
|
||||
historyRestoreGracePeriod = false
|
||||
}, GRACE_PERIOD_MS)
|
||||
}
|
||||
|
||||
function handleTimeout() {
|
||||
hide()
|
||||
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
|
||||
hide()
|
||||
}
|
||||
|
||||
// Initialize event listeners
|
||||
document.body.addEventListener('htmx:beforeRequest', handleBeforeRequest)
|
||||
document.body.addEventListener('htmx:afterRequest', hide)
|
||||
document.body.addEventListener('htmx:timeout', handleTimeout)
|
||||
document.body.addEventListener('htmx:historyRestore', handleHistoryRestore)
|
||||
document.body.addEventListener('htmx:sendError', hide)
|
||||
document.body.addEventListener('htmx:responseError', hide)
|
||||
document.body.addEventListener('htmx:beforeHistorySave', handleBeforeHistorySave)
|
||||
|
||||
window.addEventListener('pageshow', hide)
|
||||
window.addEventListener('popstate', handleHistoryRestore)
|
||||
Reference in New Issue
Block a user