htmx.org 1.7.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,49 @@
1
1
  # Changelog
2
2
 
3
- ## [1.7.0] - 2022-02-2
3
+ ## [1.8.1] - 2022-10-11
4
+
5
+ * We now keep a count of outstanding requests for an indicator, so more than one overlapping request can share the same
6
+ indicator without issues
7
+ * We now track the attribute state of an element and re-initialize it if `htmx.process()` is called on the element and
8
+ the attributes have changed
9
+ * [Idiomorph](https://github.com/bigskysoftware/idiomorph) is now available for all your morph-swapping needs
10
+ * The `unset` directive now works properly for `hx-vals` and `hx-vars`
11
+ * The title of the page is now properly set on a history cache miss
12
+ * The new (`hx-validate`)[/attributes/hx-validate] attribute will force elements to validate before a request, even if
13
+ they are not within a form being submitted
14
+ * Many smaller bug and docs fixes
15
+
16
+ ## [1.8.0] - 2022-7-12
17
+
18
+ * **NOTE**: This release involved some changes to touchy code (e.g. history support) so please test thoroughly and let
19
+ us know if you see any issues
20
+ * Boosted forms now will automatically push URLs into history as with links. The [response URL](https://caniuse.com/mdn-api_xmlhttprequest_responseurl)
21
+ detection API support is good enough that we feel comfortable making this the default now.
22
+ * If you do not want this behavior you can add `hx-push-url='false'` to your boosted forms
23
+ * The [`hx-replace-url`](https://htmx.org/attributes/hx-replace-url) attribute was introduced, allowing you to replace
24
+ the current URL in history (to complement `hx-push-url`)
25
+ * Bug fix - if htmx is included in a page more than once, we do not process elements multiple times
26
+ * Bug fix - When localStorage is not available we do not attempt to save history in it
27
+ * [Bug fix](https://github.com/bigskysoftware/htmx/issues/908) - `hx-boost` respects the `enctype` attribute
28
+ * `m` is now a valid timing modifier (e.g. `hx-trigger="every 2m"`)
29
+ * `next` and `previous` are now valid extended query selector modifiers, e.g. `hx-target="next div"` will target the
30
+ next div from the current element
31
+ * Bug fix - `hx-boost` will boost anchor tags with a `_self` target
32
+ * The `load` event now properly supports event filters
33
+ * The websocket extension has had many improvements: (A huge thank you to Denis Palashevskii, our newest committer on the project!)
34
+ * Implement proper `hx-trigger` support
35
+ * Expose trigger handling API to extensions
36
+ * Implement safe message sending with sending queue
37
+ * Fix `ws-send` attributes connecting in new elements
38
+ * Fix OOB swapping of multiple elements in response
39
+ * The `HX-Location` response header now implements client-side redirects entirely within htmx
40
+ * The `HX-Reswap` response header allows you to change the swap behavior of htmx
41
+ * The new [`hx-select-oob`](/attributes/hx-select-oob) attribute selects one or more elements from a server response to swap in via an out of band swap
42
+ * The new [`hx-replace-url`](/attributes/hx-replace-url) attribute can be used to replace the current URL in the location
43
+ bar (very similar to `hx-push-url` but no new history entry is created). The corresponding `HX-Replace-Url` response header can be used as well.
44
+ * htmx now properly handles anchors in both boosted links, as well as in `hx-get`, etc. attributes
45
+
46
+ ## [1.7.0] - 2022-02-22
4
47
 
5
48
  * The new [`hx-sync`](/attributes/hx-sync) attribute allows you to synchronize multiple element requests on a single
6
49
  element using various strategies (e.g. replace)
package/README.md CHANGED
@@ -34,8 +34,7 @@ By removing these arbitrary constraints htmx completes HTML as a
34
34
  ## quick start
35
35
 
36
36
  ```html
37
- <!-- Load from unpkg -->
38
- <script src="https://unpkg.com/htmx.org@1.7.0" ></script>
37
+ <script src="https://unpkg.com/htmx.org@1.8.1"></script>
39
38
  <!-- have a button POST a click via AJAX -->
40
39
  <button hx-post="/clicked" hx-swap="outerHTML">
41
40
  Click Me
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ // Disable Submit Button
4
+ htmx.defineExtension('disable-element', {
5
+ onEvent: function (name, evt) {
6
+ let elt = evt.detail.elt;
7
+ let target = elt.getAttribute("hx-disable-element");
8
+ let targetElement = (target == "self") ? elt : document.querySelector(target);
9
+
10
+ if (name === "htmx:beforeRequest" && targetElement) {
11
+ targetElement.disabled = true;
12
+ } else if (name == "htmx:afterRequest" && targetElement) {
13
+ targetElement.disabled = false;
14
+ }
15
+ }
16
+ });
@@ -69,6 +69,7 @@
69
69
  'data-loading-class',
70
70
  'data-loading-class-remove',
71
71
  'data-loading-disable',
72
+ 'data-loading-aria-busy',
72
73
  ]
73
74
 
74
75
  let loadingStateEltsByType = {}
@@ -77,7 +78,7 @@
77
78
  loadingStateEltsByType[type] = getLoadingStateElts(
78
79
  container,
79
80
  type,
80
- evt.detail.pathInfo.path
81
+ evt.detail.pathInfo.requestPath
81
82
  )
82
83
  })
83
84
 
@@ -153,6 +154,19 @@
153
154
  })
154
155
  }
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
+ )
156
170
  }
157
171
 
158
172
  if (name === 'htmx:afterOnLoad') {
package/dist/ext/ws.js CHANGED
@@ -13,7 +13,7 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
13
13
 
14
14
  /**
15
15
  * init is called once, when this extension is first registered.
16
- * @param {import("../htmx").HtmxInternalApi} apiRef
16
+ * @param {import("../htmx").HtmxInternalApi} apiRef
17
17
  */
18
18
  init: function(apiRef) {
19
19
 
@@ -33,9 +33,9 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
33
33
 
34
34
  /**
35
35
  * onEvent handles all events passed to this extension.
36
- *
37
- * @param {string} name
38
- * @param {Event} evt
36
+ *
37
+ * @param {string} name
38
+ * @param {Event} evt
39
39
  */
40
40
  onEvent: function(name, evt) {
41
41
 
@@ -50,7 +50,7 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
50
50
  internalData.webSocket.close();
51
51
  }
52
52
  return;
53
-
53
+
54
54
  // Try to create EventSources when elements are processed
55
55
  case "htmx:afterProcessNode":
56
56
  var parent = evt.target;
@@ -58,6 +58,9 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
58
58
  forEach(queryAttributeOnThisOrChildren(parent, "ws-connect"), function(child) {
59
59
  ensureWebSocket(child)
60
60
  });
61
+ forEach(queryAttributeOnThisOrChildren(parent, "ws-send"), function (child) {
62
+ ensureWebSocketSend(child)
63
+ });
61
64
  }
62
65
  }
63
66
  });
@@ -81,14 +84,14 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
81
84
 
82
85
  /**
83
86
  * ensureWebSocket creates a new WebSocket on the designated element, using
84
- * the element's "ws-connect" attribute.
85
- * @param {HTMLElement} elt
86
- * @param {number=} retryCount
87
- * @returns
87
+ * the element's "ws-connect" attribute.
88
+ * @param {HTMLElement} elt
89
+ * @param {number=} retryCount
90
+ * @returns
88
91
  */
89
92
  function ensureWebSocket(elt, retryCount) {
90
93
 
91
- // If the element containing the WebSocket connection no longer exists, then
94
+ // If the element containing the WebSocket connection no longer exists, then
92
95
  // do not connect/reconnect the WebSocket.
93
96
  if (!api.bodyContains(elt)) {
94
97
  return;
@@ -125,16 +128,19 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
125
128
  /** @type {WebSocket} */
126
129
  var socket = htmx.createWebSocket(wssSource);
127
130
 
131
+ var messageQueue = [];
132
+
128
133
  socket.onopen = function (e) {
129
134
  retryCount = 0;
135
+ handleQueuedMessages(messageQueue, socket);
130
136
  }
131
137
 
132
138
  socket.onclose = function (e) {
133
139
  // If Abnormal Closure/Service Restart/Try Again Later, then set a timer to reconnect after a pause.
134
- if ([1006, 1012, 1013].indexOf(e.code) >= 0) {
140
+ if ([1006, 1012, 1013].indexOf(e.code) >= 0) {
135
141
  var delay = getWebSocketReconnectDelay(retryCount);
136
142
  setTimeout(function() {
137
- ensureWebSocket(elt, retryCount+1);
143
+ ensureWebSocket(elt, retryCount+1);
138
144
  }, delay);
139
145
  }
140
146
  };
@@ -158,25 +164,42 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
158
164
  var fragment = api.makeFragment(response);
159
165
 
160
166
  if (fragment.children.length) {
161
- for (var i = 0; i < fragment.children.length; i++) {
162
- api.oobSwap(api.getAttributeValue(fragment.children[i], "hx-swap-oob") || "true", fragment.children[i], settleInfo);
167
+ var children = Array.from(fragment.children);
168
+ for (var i = 0; i < children.length; i++) {
169
+ api.oobSwap(api.getAttributeValue(children[i], "hx-swap-oob") || "true", children[i], settleInfo);
163
170
  }
164
171
  }
165
172
 
166
173
  api.settleImmediately(settleInfo.tasks);
167
174
  });
168
175
 
169
- // Re-connect any ws-send commands as well.
170
- forEach(queryAttributeOnThisOrChildren(elt, "ws-send"), function(child) {
171
- var legacyAttribute = api.getAttributeValue(child, "hx-ws");
172
- if (legacyAttribute && legacyAttribute !== 'send') {
173
- return;
174
- }
175
- processWebSocketSend(elt, child);
176
- });
177
-
178
176
  // Put the WebSocket into the HTML Element's custom data.
179
177
  api.getInternalData(elt).webSocket = socket;
178
+ api.getInternalData(elt).webSocketMessageQueue = messageQueue;
179
+ }
180
+
181
+ /**
182
+ * ensureWebSocketSend attaches trigger handles to elements with
183
+ * "ws-send" attribute
184
+ * @param {HTMLElement} elt
185
+ */
186
+ function ensureWebSocketSend(elt) {
187
+ var legacyAttribute = api.getAttributeValue(elt, "hx-ws");
188
+ if (legacyAttribute && legacyAttribute !== 'send') {
189
+ return;
190
+ }
191
+
192
+ var webSocketParent = api.getClosestMatch(elt, hasWebSocket)
193
+ processWebSocketSend(webSocketParent, elt);
194
+ }
195
+
196
+ /**
197
+ * hasWebSocket function checks if a node has webSocket instance attached
198
+ * @param {HTMLElement} node
199
+ * @returns {boolean}
200
+ */
201
+ function hasWebSocket(node) {
202
+ return api.getInternalData(node).webSocket != null;
180
203
  }
181
204
 
182
205
  /**
@@ -184,29 +207,65 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
184
207
  * messages can be sent to the WebSocket server when the form is submitted.
185
208
  * @param {HTMLElement} parent
186
209
  * @param {HTMLElement} child
187
- */
210
+ */
188
211
  function processWebSocketSend(parent, child) {
189
- child.addEventListener(api.getTriggerSpecs(child)[0].trigger, function (evt) {
190
- var webSocket = api.getInternalData(parent).webSocket;
191
- var headers = api.getHeaders(child, parent);
192
- var results = api.getInputValues(child, 'post');
193
- var errors = results.errors;
194
- var rawParameters = results.values;
195
- var expressionVars = api.getExpressionVars(child);
196
- var allParameters = api.mergeObjects(rawParameters, expressionVars);
197
- var filteredParameters = api.filterValues(allParameters, child);
198
- filteredParameters['HEADERS'] = headers;
199
- if (errors && errors.length > 0) {
200
- api.triggerEvent(child, 'htmx:validation:halted', errors);
201
- return;
202
- }
203
- webSocket.send(JSON.stringify(filteredParameters));
204
- if(api.shouldCancel(child)){
205
- evt.preventDefault();
206
- }
212
+ var nodeData = api.getInternalData(child);
213
+ let triggerSpecs = api.getTriggerSpecs(child);
214
+ triggerSpecs.forEach(function(ts) {
215
+ api.addTriggerHandler(child, ts, nodeData, function (evt) {
216
+ var webSocket = api.getInternalData(parent).webSocket;
217
+ var messageQueue = api.getInternalData(parent).webSocketMessageQueue;
218
+ var headers = api.getHeaders(child, parent);
219
+ var results = api.getInputValues(child, 'post');
220
+ var errors = results.errors;
221
+ var rawParameters = results.values;
222
+ var expressionVars = api.getExpressionVars(child);
223
+ var allParameters = api.mergeObjects(rawParameters, expressionVars);
224
+ var filteredParameters = api.filterValues(allParameters, child);
225
+ filteredParameters['HEADERS'] = headers;
226
+ if (errors && errors.length > 0) {
227
+ api.triggerEvent(child, 'htmx:validation:halted', errors);
228
+ return;
229
+ }
230
+ webSocketSend(webSocket, JSON.stringify(filteredParameters), messageQueue);
231
+ if(api.shouldCancel(evt, child)){
232
+ evt.preventDefault();
233
+ }
234
+ });
207
235
  });
208
236
  }
209
-
237
+
238
+ /**
239
+ * webSocketSend provides a safe way to send messages through a WebSocket.
240
+ * It checks that the socket is in OPEN state and, otherwise, awaits for it.
241
+ * @param {WebSocket} socket
242
+ * @param {string} message
243
+ * @param {string[]} messageQueue
244
+ * @return {boolean}
245
+ */
246
+ function webSocketSend(socket, message, messageQueue) {
247
+ if (socket.readyState != socket.OPEN) {
248
+ messageQueue.push(message);
249
+ } else {
250
+ socket.send(message);
251
+ }
252
+ }
253
+
254
+ /**
255
+ * handleQueuedMessages sends messages awaiting in the message queue
256
+ */
257
+ function handleQueuedMessages(messageQueue, socket) {
258
+ while (messageQueue.length > 0) {
259
+ var message = messageQueue[0]
260
+ if (socket.readyState == socket.OPEN) {
261
+ socket.send(message);
262
+ messageQueue.shift()
263
+ } else {
264
+ break;
265
+ }
266
+ }
267
+ }
268
+
210
269
  /**
211
270
  * getWebSocketReconnectDelay is the default easing function for WebSocket reconnects.
212
271
  * @param {number} retryCount // The number of retries that have already taken place
@@ -230,12 +289,12 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
230
289
 
231
290
  /**
232
291
  * maybeCloseWebSocketSource checks to the if the element that created the WebSocket
233
- * still exists in the DOM. If NOT, then the WebSocket is closed and this function
292
+ * still exists in the DOM. If NOT, then the WebSocket is closed and this function
234
293
  * returns TRUE. If the element DOES EXIST, then no action is taken, and this function
235
294
  * returns FALSE.
236
- *
237
- * @param {*} elt
238
- * @returns
295
+ *
296
+ * @param {*} elt
297
+ * @returns
239
298
  */
240
299
  function maybeCloseWebSocketSource(elt) {
241
300
  if (!api.bodyContains(elt)) {
@@ -248,8 +307,8 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
248
307
  /**
249
308
  * createWebSocket is the default method for creating new WebSocket objects.
250
309
  * it is hoisted into htmx.createWebSocket to be overridden by the user, if needed.
251
- *
252
- * @param {string} url
310
+ *
311
+ * @param {string} url
253
312
  * @returns WebSocket
254
313
  */
255
314
  function createWebSocket(url){
@@ -258,9 +317,9 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
258
317
 
259
318
  /**
260
319
  * queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
261
- *
262
- * @param {HTMLElement} elt
263
- * @param {string} attributeName
320
+ *
321
+ * @param {HTMLElement} elt
322
+ * @param {string} attributeName
264
323
  */
265
324
  function queryAttributeOnThisOrChildren(elt, attributeName) {
266
325
 
@@ -281,8 +340,8 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
281
340
 
282
341
  /**
283
342
  * @template T
284
- * @param {T[]} arr
285
- * @param {(T) => void} func
343
+ * @param {T[]} arr
344
+ * @param {(T) => void} func
286
345
  */
287
346
  function forEach(arr, func) {
288
347
  if (arr) {
@@ -292,4 +351,5 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
292
351
  }
293
352
  }
294
353
 
295
- })();
354
+ })();
355
+