UNPKG

4.12 kBJavaScriptView Raw
1;(function () {
2 let loadingStatesUndoQueue = []
3
4 function loadingStateContainer(target) {
5 return htmx.closest(target, '[data-loading-states]') || document.body
6 }
7
8 function mayProcessUndoCallback(target, callback) {
9 if (document.body.contains(target)) {
10 callback()
11 }
12 }
13
14 function mayProcessLoadingStateByPath(elt, requestPath) {
15 const pathElt = htmx.closest(elt, '[data-loading-path]')
16 if (!pathElt) {
17 return true
18 }
19
20 return pathElt.getAttribute('data-loading-path') === requestPath
21 }
22
23 function queueLoadingState(sourceElt, targetElt, doCallback, undoCallback) {
24 const delayElt = htmx.closest(sourceElt, '[data-loading-delay]')
25 if (delayElt) {
26 const delayInMilliseconds =
27 delayElt.getAttribute('data-loading-delay') || 200
28 const timeout = setTimeout(() => {
29 doCallback()
30
31 loadingStatesUndoQueue.push(() => {
32 mayProcessUndoCallback(targetElt, () => undoCallback())
33 })
34 }, delayInMilliseconds)
35
36 loadingStatesUndoQueue.push(() => {
37 mayProcessUndoCallback(targetElt, () => clearTimeout(timeout))
38 })
39 } else {
40 doCallback()
41 loadingStatesUndoQueue.push(() => {
42 mayProcessUndoCallback(targetElt, () => undoCallback())
43 })
44 }
45 }
46
47 function getLoadingStateElts(loadingScope, type, path) {
48 return Array.from(htmx.findAll(loadingScope, `[${type}]`)).filter(
49 (elt) => mayProcessLoadingStateByPath(elt, path)
50 )
51 }
52
53 function getLoadingTarget(elt) {
54 if (elt.getAttribute('data-loading-target')) {
55 return Array.from(
56 htmx.findAll(elt.getAttribute('data-loading-target'))
57 )
58 }
59 return [elt]
60 }
61
62 htmx.defineExtension('loading-states', {
63 onEvent: function (name, evt) {
64 if (name === 'htmx:beforeRequest') {
65 const container = loadingStateContainer(evt.target)
66
67 const loadingStateTypes = [
68 'data-loading',
69 'data-loading-class',
70 'data-loading-class-remove',
71 'data-loading-disable',
72 ]
73
74 let loadingStateEltsByType = {}
75
76 loadingStateTypes.forEach((type) => {
77 loadingStateEltsByType[type] = getLoadingStateElts(
78 container,
79 type,
80 evt.detail.pathInfo.path
81 )
82 })
83
84 loadingStateEltsByType['data-loading'].forEach((sourceElt) => {
85 getLoadingTarget(sourceElt).forEach((targetElt) => {
86 queueLoadingState(
87 sourceElt,
88 targetElt,
89 () =>
90 (targetElt.style.display =
91 sourceElt.getAttribute('data-loading') ||
92 'inline-block'),
93 () => (targetElt.style.display = 'none')
94 )
95 })
96 })
97
98 loadingStateEltsByType['data-loading-class'].forEach(
99 (sourceElt) => {
100 const classNames = sourceElt
101 .getAttribute('data-loading-class')
102 .split(' ')
103
104 getLoadingTarget(sourceElt).forEach((targetElt) => {
105 queueLoadingState(
106 sourceElt,
107 targetElt,
108 () =>
109 classNames.forEach((className) =>
110 targetElt.classList.add(className)
111 ),
112 () =>
113 classNames.forEach((className) =>
114 targetElt.classList.remove(className)
115 )
116 )
117 })
118 }
119 )
120
121 loadingStateEltsByType['data-loading-class-remove'].forEach(
122 (sourceElt) => {
123 const classNames = sourceElt
124 .getAttribute('data-loading-class-remove')
125 .split(' ')
126
127 getLoadingTarget(sourceElt).forEach((targetElt) => {
128 queueLoadingState(
129 sourceElt,
130 targetElt,
131 () =>
132 classNames.forEach((className) =>
133 targetElt.classList.remove(className)
134 ),
135 () =>
136 classNames.forEach((className) =>
137 targetElt.classList.add(className)
138 )
139 )
140 })
141 }
142 )
143
144 loadingStateEltsByType['data-loading-disable'].forEach(
145 (sourceElt) => {
146 getLoadingTarget(sourceElt).forEach((targetElt) => {
147 queueLoadingState(
148 sourceElt,
149 targetElt,
150 () => (targetElt.disabled = true),
151 () => (targetElt.disabled = false)
152 )
153 })
154 }
155 )
156 }
157
158 if (name === 'htmx:afterOnLoad') {
159 while (loadingStatesUndoQueue.length > 0) {
160 loadingStatesUndoQueue.shift()()
161 }
162 }
163 },
164 })
165})()