UNPKG

4.51 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 'data-loading-aria-busy',
73 ]
74
75 let loadingStateEltsByType = {}
76
77 loadingStateTypes.forEach((type) => {
78 loadingStateEltsByType[type] = getLoadingStateElts(
79 container,
80 type,
81 evt.detail.pathInfo.requestPath
82 )
83 })
84
85 loadingStateEltsByType['data-loading'].forEach((sourceElt) => {
86 getLoadingTarget(sourceElt).forEach((targetElt) => {
87 queueLoadingState(
88 sourceElt,
89 targetElt,
90 () =>
91 (targetElt.style.display =
92 sourceElt.getAttribute('data-loading') ||
93 'inline-block'),
94 () => (targetElt.style.display = 'none')
95 )
96 })
97 })
98
99 loadingStateEltsByType['data-loading-class'].forEach(
100 (sourceElt) => {
101 const classNames = sourceElt
102 .getAttribute('data-loading-class')
103 .split(' ')
104
105 getLoadingTarget(sourceElt).forEach((targetElt) => {
106 queueLoadingState(
107 sourceElt,
108 targetElt,
109 () =>
110 classNames.forEach((className) =>
111 targetElt.classList.add(className)
112 ),
113 () =>
114 classNames.forEach((className) =>
115 targetElt.classList.remove(className)
116 )
117 )
118 })
119 }
120 )
121
122 loadingStateEltsByType['data-loading-class-remove'].forEach(
123 (sourceElt) => {
124 const classNames = sourceElt
125 .getAttribute('data-loading-class-remove')
126 .split(' ')
127
128 getLoadingTarget(sourceElt).forEach((targetElt) => {
129 queueLoadingState(
130 sourceElt,
131 targetElt,
132 () =>
133 classNames.forEach((className) =>
134 targetElt.classList.remove(className)
135 ),
136 () =>
137 classNames.forEach((className) =>
138 targetElt.classList.add(className)
139 )
140 )
141 })
142 }
143 )
144
145 loadingStateEltsByType['data-loading-disable'].forEach(
146 (sourceElt) => {
147 getLoadingTarget(sourceElt).forEach((targetElt) => {
148 queueLoadingState(
149 sourceElt,
150 targetElt,
151 () => (targetElt.disabled = true),
152 () => (targetElt.disabled = false)
153 )
154 })
155 }
156 )
157
158 loadingStateEltsByType['data-loading-aria-busy'].forEach(
159 (sourceElt) => {
160 getLoadingTarget(sourceElt).forEach((targetElt) => {
161 queueLoadingState(
162 sourceElt,
163 targetElt,
164 () => (targetElt.setAttribute("aria-busy", "true")),
165 () => (targetElt.removeAttribute("aria-busy"))
166 )
167 })
168 }
169 )
170 }
171
172 if (name === 'htmx:beforeOnLoad') {
173 while (loadingStatesUndoQueue.length > 0) {
174 loadingStatesUndoQueue.shift()()
175 }
176 }
177 },
178 })
179})()