aboutsummaryrefslogblamecommitdiff
path: root/static/js/ext/loading-states.js
blob: 17743047fa2900eb75e7165c6283f7ac2bd3d011 (plain) (tree)




























































































































































































                                                                                                                                   
;(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()()
				}
			}
		},
	})
})()