htmx integration
This commit is contained in:
189
static/packages/htmx/ext/loading-states.js
Normal file
189
static/packages/htmx/ext/loading-states.js
Normal file
@@ -0,0 +1,189 @@
|
||||
;(function () {
|
||||
|
||||
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||
}
|
||||
|
||||
let loadingStatesUndoQueue = []
|
||||
|
||||
function loadingStateContainer(target) {
|
||||
return htmx.closest(target, '[data-loading-states]') || document.body
|
||||
}
|
||||
|
||||
function mayProcessUndoCallback(target, callback) {
|
||||
if (document.body.contains(target)) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
function mayProcessLoadingStateByPath(elt, requestPath) {
|
||||
const pathElt = htmx.closest(elt, '[data-loading-path]')
|
||||
if (!pathElt) {
|
||||
return true
|
||||
}
|
||||
|
||||
return pathElt.getAttribute('data-loading-path') === requestPath
|
||||
}
|
||||
|
||||
function queueLoadingState(sourceElt, targetElt, doCallback, undoCallback) {
|
||||
const delayElt = htmx.closest(sourceElt, '[data-loading-delay]')
|
||||
if (delayElt) {
|
||||
const delayInMilliseconds =
|
||||
delayElt.getAttribute('data-loading-delay') || 200
|
||||
const timeout = setTimeout(function () {
|
||||
doCallback()
|
||||
|
||||
loadingStatesUndoQueue.push(function () {
|
||||
mayProcessUndoCallback(targetElt, undoCallback)
|
||||
})
|
||||
}, delayInMilliseconds)
|
||||
|
||||
loadingStatesUndoQueue.push(function () {
|
||||
mayProcessUndoCallback(targetElt, function () { clearTimeout(timeout) })
|
||||
})
|
||||
} else {
|
||||
doCallback()
|
||||
loadingStatesUndoQueue.push(function () {
|
||||
mayProcessUndoCallback(targetElt, undoCallback)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getLoadingStateElts(loadingScope, type, path) {
|
||||
return Array.from(htmx.findAll(loadingScope, "[" + type + "]")).filter(
|
||||
function (elt) { return mayProcessLoadingStateByPath(elt, path) }
|
||||
)
|
||||
}
|
||||
|
||||
function getLoadingTarget(elt) {
|
||||
if (elt.getAttribute('data-loading-target')) {
|
||||
return Array.from(
|
||||
htmx.findAll(elt.getAttribute('data-loading-target'))
|
||||
)
|
||||
}
|
||||
return [elt]
|
||||
}
|
||||
|
||||
htmx.defineExtension('loading-states', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === 'htmx:beforeRequest') {
|
||||
const container = loadingStateContainer(evt.target)
|
||||
|
||||
const loadingStateTypes = [
|
||||
'data-loading',
|
||||
'data-loading-class',
|
||||
'data-loading-class-remove',
|
||||
'data-loading-disable',
|
||||
'data-loading-aria-busy',
|
||||
]
|
||||
|
||||
let loadingStateEltsByType = {}
|
||||
|
||||
loadingStateTypes.forEach(function (type) {
|
||||
loadingStateEltsByType[type] = getLoadingStateElts(
|
||||
container,
|
||||
type,
|
||||
evt.detail.pathInfo.requestPath
|
||||
)
|
||||
})
|
||||
|
||||
loadingStateEltsByType['data-loading'].forEach(function (sourceElt) {
|
||||
getLoadingTarget(sourceElt).forEach(function (targetElt) {
|
||||
queueLoadingState(
|
||||
sourceElt,
|
||||
targetElt,
|
||||
function () {
|
||||
targetElt.style.display =
|
||||
sourceElt.getAttribute('data-loading') ||
|
||||
'inline-block' },
|
||||
function () { targetElt.style.display = 'none' }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
loadingStateEltsByType['data-loading-class'].forEach(
|
||||
function (sourceElt) {
|
||||
const classNames = sourceElt
|
||||
.getAttribute('data-loading-class')
|
||||
.split(' ')
|
||||
|
||||
getLoadingTarget(sourceElt).forEach(function (targetElt) {
|
||||
queueLoadingState(
|
||||
sourceElt,
|
||||
targetElt,
|
||||
function () {
|
||||
classNames.forEach(function (className) {
|
||||
targetElt.classList.add(className)
|
||||
})
|
||||
},
|
||||
function() {
|
||||
classNames.forEach(function (className) {
|
||||
targetElt.classList.remove(className)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
loadingStateEltsByType['data-loading-class-remove'].forEach(
|
||||
function (sourceElt) {
|
||||
const classNames = sourceElt
|
||||
.getAttribute('data-loading-class-remove')
|
||||
.split(' ')
|
||||
|
||||
getLoadingTarget(sourceElt).forEach(function (targetElt) {
|
||||
queueLoadingState(
|
||||
sourceElt,
|
||||
targetElt,
|
||||
function () {
|
||||
classNames.forEach(function (className) {
|
||||
targetElt.classList.remove(className)
|
||||
})
|
||||
},
|
||||
function() {
|
||||
classNames.forEach(function (className) {
|
||||
targetElt.classList.add(className)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
loadingStateEltsByType['data-loading-disable'].forEach(
|
||||
function (sourceElt) {
|
||||
getLoadingTarget(sourceElt).forEach(function (targetElt) {
|
||||
queueLoadingState(
|
||||
sourceElt,
|
||||
targetElt,
|
||||
function() { targetElt.disabled = true },
|
||||
function() { targetElt.disabled = false }
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
loadingStateEltsByType['data-loading-aria-busy'].forEach(
|
||||
function (sourceElt) {
|
||||
getLoadingTarget(sourceElt).forEach(function (targetElt) {
|
||||
queueLoadingState(
|
||||
sourceElt,
|
||||
targetElt,
|
||||
function () { targetElt.setAttribute("aria-busy", "true") },
|
||||
function () { targetElt.removeAttribute("aria-busy") }
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (name === 'htmx:beforeOnLoad') {
|
||||
while (loadingStatesUndoQueue.length > 0) {
|
||||
loadingStatesUndoQueue.shift()()
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
})()
|
Reference in New Issue
Block a user