add hx-disabled-elts functionality

This commit is contained in:
Carson Gross 2023-09-14 13:20:46 -06:00
parent 77bdc2c1da
commit af0dc9c352
7 changed files with 194 additions and 45 deletions

View File

@ -2343,7 +2343,20 @@ return (function () {
return indicators;
}
function removeRequestIndicatorClasses(indicators) {
function disableElements(elt) {
var disabledElts = findAttributeTargets(elt, 'hx-disabled-elts');
if (disabledElts == null) {
disabledElts = [];
}
forEach(disabledElts, function (disabledElement) {
var internalData = getInternalData(disabledElement);
internalData.requestCount = (internalData.requestCount || 0) + 1;
disabledElement.setAttribute("disabled", "");
});
return disabledElts;
}
function removeRequestIndicators(indicators, disabled) {
forEach(indicators, function (ic) {
var internalData = getInternalData(ic);
internalData.requestCount = (internalData.requestCount || 0) - 1;
@ -2351,6 +2364,13 @@ return (function () {
ic.classList["remove"].call(ic.classList, htmx.config.requestClass);
}
});
forEach(disabled, function (disabledElement) {
var internalData = getInternalData(disabledElement);
internalData.requestCount = (internalData.requestCount || 0) - 1;
if (internalData.requestCount === 0) {
disabledElement.removeAttribute('disabled');
}
});
}
//====================================================================
@ -3186,7 +3206,7 @@ return (function () {
var hierarchy = hierarchyForElt(elt);
responseInfo.pathInfo.responsePath = getPathFromResponse(xhr);
responseHandler(elt, responseInfo);
removeRequestIndicatorClasses(indicators);
removeRequestIndicators(indicators, disableElts);
triggerEvent(elt, 'htmx:afterRequest', responseInfo);
triggerEvent(elt, 'htmx:afterOnLoad', responseInfo);
// if the body no longer contains the element, trigger the event on the closest parent
@ -3212,21 +3232,21 @@ return (function () {
}
}
xhr.onerror = function () {
removeRequestIndicatorClasses(indicators);
removeRequestIndicators(indicators, disableElts);
triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
triggerErrorEvent(elt, 'htmx:sendError', responseInfo);
maybeCall(reject);
endRequestLock();
}
xhr.onabort = function() {
removeRequestIndicatorClasses(indicators);
removeRequestIndicators(indicators, disableElts);
triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
triggerErrorEvent(elt, 'htmx:sendAbort', responseInfo);
maybeCall(reject);
endRequestLock();
}
xhr.ontimeout = function() {
removeRequestIndicatorClasses(indicators);
removeRequestIndicators(indicators, disableElts);
triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
triggerErrorEvent(elt, 'htmx:timeout', responseInfo);
maybeCall(reject);
@ -3238,6 +3258,7 @@ return (function () {
return promise
}
var indicators = addRequestIndicatorClasses(elt);
var disableElts = disableElements(elt);
forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
forEach([xhr, xhr.upload], function (target) {

View File

@ -0,0 +1,94 @@
describe("hx-disabled-elts attribute", function(){
beforeEach(function() {
this.server = sinon.fakeServer.create();
clearWorkArea();
});
afterEach(function() {
this.server.restore();
clearWorkArea();
});
it('single element can be disabled w/ hx-disabled elts', function()
{
this.server.respondWith("GET", "/test", "Clicked!");
var btn = make('<button hx-get="/test" hx-disabled-elts="this">Click Me!</button>')
btn.hasAttribute('disabled').should.equal(false);
btn.click();
btn.hasAttribute('disabled').should.equal(true);
this.server.respond();
btn.hasAttribute('disabled').should.equal(false);
});
it('single element can be disabled w/ data-hx-disabled elts', function()
{
this.server.respondWith("GET", "/test", "Clicked!");
var btn = make('<button hx-get="/test" data-hx-disabled-elts="this">Click Me!</button>')
btn.hasAttribute('disabled').should.equal(false);
btn.click();
btn.hasAttribute('disabled').should.equal(true);
this.server.respond();
btn.hasAttribute('disabled').should.equal(false);
});
it('single element can be disabled w/ closest syntax', function()
{
this.server.respondWith("GET", "/test", "Clicked!");
var fieldset = make('<fieldset><button id="b1" hx-get="/test" hx-disabled-elts="closest fieldset">Click Me!</button></fieldset>')
var btn = byId('b1');
fieldset.hasAttribute('disabled').should.equal(false);
btn.click();
fieldset.hasAttribute('disabled').should.equal(true);
this.server.respond();
fieldset.hasAttribute('disabled').should.equal(false);
});
it('multiple requests with same disabled elt are handled properly', function()
{
this.server.respondWith("GET", "/test", "Clicked!");
var b1 = make('<button hx-get="/test" hx-disabled-elts="#b3">Click Me!</button>')
var b2 = make('<button hx-get="/test" hx-disabled-elts="#b3">Click Me!</button>')
var b3 = make('<button id="b3">Demo</button>')
b3.hasAttribute('disabled').should.equal(false);
b1.click();
b3.hasAttribute('disabled').should.equal(true);
b2.click();
b3.hasAttribute('disabled').should.equal(true);
// hack to make sinon process only one response
this.server.processRequest(this.server.queue.shift());
b3.hasAttribute('disabled').should.equal(true);
this.server.respond();
b3.hasAttribute('disabled').should.equal(false);
});
it('multiple elts can be disabled', function()
{
this.server.respondWith("GET", "/test", "Clicked!");
var b1 = make('<button hx-get="/test" hx-disabled-elts="#b2, #b3">Click Me!</button>')
var b2 = make('<button id="b2">Click Me!</button>')
var b3 = make('<button id="b3">Demo</button>')
b2.hasAttribute('disabled').should.equal(false);
b3.hasAttribute('disabled').should.equal(false);
b1.click();
b2.hasAttribute('disabled').should.equal(true);
b3.hasAttribute('disabled').should.equal(true);
this.server.respond();
b2.hasAttribute('disabled').should.equal(false);
b3.hasAttribute('disabled').should.equal(false);
});
})

View File

@ -72,6 +72,7 @@
<script src="attributes/hx-history.js"></script>
<script src="attributes/hx-include.js"></script>
<script src="attributes/hx-indicator.js"></script>
<script src="attributes/hx-disabled-elts.js"></script>
<script src="attributes/hx-disinherit.js"></script>
<script src="attributes/hx-on.js"></script>
<script src="attributes/hx-on-wildcard.js"></script>

View File

@ -68,6 +68,10 @@ Autorespond: <input id="autorespond" type="checkbox" onclick="toggleAutoRespond(
<button onclick="console.log(this, event)">Log It...</button>
<button hx-get="/whatever">
</button>
<script>
let requestCount = 0;
this.server.respondWith("GET", "/demo", function(xhr){

View File

@ -0,0 +1,26 @@
+++
title = "hx-disabled-elts"
+++
The `hx-disabled-elts` attribute allows you to specify elements that will have the `disabled` attribute
added to them for the duration of the request.
The value of this attribute is a CSS query selector of the element or elements to apply the class to,
or the keyword [`closest`](https://developer.mozilla.org/docs/Web/API/Element/closest), followed by a CSS selector,
which will find the closest ancestor element or itself, that matches the given CSS selector (e.g. `closest tr`), or
the keyword `this`
Here is an example with a button that will disable itself during a request:
```html
<button hx-post="/example" hx-disabled-elts="this">
Post It!
</button>
```
When a request is in flight, this will cause the button to be marked with [the `disabled` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled),
which will prevent further clicks from occurring.
## Notes
* `hx-disable-elts` is inherited and can be placed on a parent element

View File

@ -368,6 +368,9 @@ attribute with a CSS selector to do so:
Here we call out the indicator explicitly by id. Note that we could have placed the class on the parent `div` as well
and had the same effect.
You can also add the [the `disabled` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled) to
elements for the duration of a request by using the [hx-disabled-elts](@/attributes/hx-indicator.md) attribute.
### Targets
If you want the response to be loaded into a different element other than the one that made the request, you can

View File

@ -19,20 +19,20 @@ The following are the most common attributes when using htmx.
<div class="info-table">
| Attribute | Description |
|----------------------------------------------------|-------------|
| [`hx-boost`](@/attributes/hx-boost.md) | add or remove [progressive enhancement](https://en.wikipedia.org/wiki/Progressive_enhancement) for links and forms
| [`hx-get`](@/attributes/hx-get.md) | issues a `GET` to the specified URL
| [`hx-post`](@/attributes/hx-post.md) | issues a `POST` to the specified URL
| [`hx-on`](@/attributes/hx-on.md) | handle any event with a script inline
| [`hx-push-url`](@/attributes/hx-push-url.md) | pushes the URL into the browser location bar, creating a new history entry
| [`hx-select`](@/attributes/hx-select.md) | select content to swap in from a response
| [`hx-select-oob`](@/attributes/hx-select-oob.md) | select content to swap in from a response, out of band (somewhere other than the target)
| [`hx-swap`](@/attributes/hx-swap.md) | controls how content is swapped in (`outerHTML`, `beforeend`, `afterend`, ...)
| [`hx-swap-oob`](@/attributes/hx-swap-oob.md) | marks content in a response to be out of band (should swap in somewhere other than the target)
| [`hx-target`](@/attributes/hx-target.md) | specifies the target element to be swapped
| [`hx-trigger`](@/attributes/hx-trigger.md) | specifies the event that triggers the request
| [`hx-vals`](@/attributes/hx-vals.md) | adds values to the parameters to submit with the request (JSON-formatted)
| Attribute | Description |
|--------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|
| [`hx-boost`](@/attributes/hx-boost.md) | add or remove [progressive enhancement](https://en.wikipedia.org/wiki/Progressive_enhancement) for links and forms |
| [`hx-get`](@/attributes/hx-get.md) | issues a `GET` to the specified URL |
| [`hx-post`](@/attributes/hx-post.md) | issues a `POST` to the specified URL |
| [`hx-on`](@/attributes/hx-on.md) | handle any event with a script inline |
| [`hx-push-url`](@/attributes/hx-push-url.md) | pushes the URL into the browser location bar, creating a new history entry |
| [`hx-select`](@/attributes/hx-select.md) | select content to swap in from a response |
| [`hx-select-oob`](@/attributes/hx-select-oob.md) | select content to swap in from a response, out of band (somewhere other than the target) |
| [`hx-swap`](@/attributes/hx-swap.md) | controls how content is swapped in (`outerHTML`, `beforeend`, `afterend`, ...) |
| [`hx-swap-oob`](@/attributes/hx-swap-oob.md) | marks content in a response to be out of band (should swap in somewhere other than the target) |
| [`hx-target`](@/attributes/hx-target.md) | specifies the target element to be swapped |
| [`hx-trigger`](@/attributes/hx-trigger.md) | specifies the event that triggers the request |
| [`hx-vals`](@/attributes/hx-vals.md) | adds values to the parameters to submit with the request (JSON-formatted) |
</div>
@ -42,32 +42,32 @@ The table below lists all other attributes available in htmx.
<div class="info-table">
| Attribute | Description |
|----------------------------------------------------|-------------|
| [`hx-confirm`](@/attributes/hx-confirm.md) | shows a `confirm()` dialog before issuing a request
| [`hx-delete`](@/attributes/hx-delete.md) | issues a `DELETE` to the specified URL
| [`hx-disable`](@/attributes/hx-disable.md) | disables htmx processing for the given node and any children nodes
| [`hx-disinherit`](@/attributes/hx-disinherit.md) | control and disable automatic attribute inheritance for child nodes
| [`hx-encoding`](@/attributes/hx-encoding.md) | changes the request encoding type
| [`hx-ext`](@/attributes/hx-ext.md) | extensions to use for this element
| [`hx-headers`](@/attributes/hx-headers.md) | adds to the headers that will be submitted with the request
| [`hx-history`](@/attributes/hx-history.md) | prevent sensitive data being saved to the history cache
| [`hx-history-elt`](@/attributes/hx-history-elt.md) | the element to snapshot and restore during history navigation
| [`hx-include`](@/attributes/hx-include.md) | include additional data in requests
| [`hx-indicator`](@/attributes/hx-indicator.md) | the element to put the `htmx-request` class on during the request
| [`hx-params`](@/attributes/hx-params.md) | filters the parameters that will be submitted with a request
| [`hx-patch`](@/attributes/hx-patch.md) | issues a `PATCH` to the specified URL
| [`hx-preserve`](@/attributes/hx-preserve.md) | specifies elements to keep unchanged between requests
| [`hx-prompt`](@/attributes/hx-prompt.md) | shows a `prompt()` before submitting a request
| [`hx-put`](@/attributes/hx-put.md) | issues a `PUT` to the specified URL
| [`hx-replace-url`](@/attributes/hx-replace-url.md) | replace the URL in the browser location bar
| [`hx-request`](@/attributes/hx-request.md) | configures various aspects of the request
| [`hx-sse`](@/extensions/server-sent-events.md) | has been moved to an extension. [Documentation for older versions](@/attributes/hx-sse.md)
| [`hx-sync`](@/attributes/hx-sync.md) | control how requests made by different elements are synchronized
| [`hx-validate`](@/attributes/hx-validate.md) | force elements to validate themselves before a request
| [`hx-vars`](@/attributes/hx-vars.md) | adds values dynamically to the parameters to submit with the request (deprecated, please use [`hx-vals`](@/attributes/hx-vals.md))
| [`hx-ws`](@/extensions/web-sockets.md) | has been moved to an extension. [Documentation for older versions](@/attributes/hx-ws.md)
| Attribute | Description |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|
| [`hx-confirm`](@/attributes/hx-confirm.md) | shows a `confirm()` dialog before issuing a request |
| [`hx-delete`](@/attributes/hx-delete.md) | issues a `DELETE` to the specified URL |
| [`hx-disable`](@/attributes/hx-disable.md) | disables htmx processing for the given node and any children nodes |
| [`hx-disabled-elts`](@/attributes/hx-disabled-elts.md) | adds the `disabled` attribute to the specified elements while a request is in flight |
| [`hx-disinherit`](@/attributes/hx-disinherit.md) | control and disable automatic attribute inheritance for child nodes |
| [`hx-encoding`](@/attributes/hx-encoding.md) | changes the request encoding type |
| [`hx-ext`](@/attributes/hx-ext.md) | extensions to use for this element |
| [`hx-headers`](@/attributes/hx-headers.md) | adds to the headers that will be submitted with the request |
| [`hx-history`](@/attributes/hx-history.md) | prevent sensitive data being saved to the history cache |
| [`hx-history-elt`](@/attributes/hx-history-elt.md) | the element to snapshot and restore during history navigation |
| [`hx-include`](@/attributes/hx-include.md) | include additional data in requests |
| [`hx-indicator`](@/attributes/hx-indicator.md) | the element to put the `htmx-request` class on during the request |
| [`hx-params`](@/attributes/hx-params.md) | filters the parameters that will be submitted with a request |
| [`hx-patch`](@/attributes/hx-patch.md) | issues a `PATCH` to the specified URL |
| [`hx-preserve`](@/attributes/hx-preserve.md) | specifies elements to keep unchanged between requests |
| [`hx-prompt`](@/attributes/hx-prompt.md) | shows a `prompt()` before submitting a request |
| [`hx-put`](@/attributes/hx-put.md) | issues a `PUT` to the specified URL |
| [`hx-replace-url`](@/attributes/hx-replace-url.md) | replace the URL in the browser location bar |
| [`hx-request`](@/attributes/hx-request.md) | configures various aspects of the request |
| [`hx-sse`](@/extensions/server-sent-events.md) | has been moved to an extension. [Documentation for older versions](@/attributes/hx-sse.md) |
| [`hx-sync`](@/attributes/hx-sync.md) | control how requests made by different elements are synchronized |
| [`hx-validate`](@/attributes/hx-validate.md) | force elements to validate themselves before a request |
| [`hx-vars`](@/attributes/hx-vars.md) | adds values dynamically to the parameters to submit with the request (deprecated, please use [`hx-vals`](@/attributes/hx-vals.md)) |
| [`hx-ws`](@/extensions/web-sockets.md) | has been moved to an extension. [Documentation for older versions](@/attributes/hx-ws.md) |
</div>