Skip to content

Commit

Permalink
add hx-disabled-elts functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
1cg committed Sep 14, 2023
1 parent 77bdc2c commit af0dc9c
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 45 deletions.
31 changes: 26 additions & 5 deletions src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -2343,14 +2343,34 @@ 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;
if (internalData.requestCount === 0) {
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');
}
});
}

//====================================================================
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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) {
Expand Down
94 changes: 94 additions & 0 deletions test/attributes/hx-disabled-elts.js
Original file line number Diff line number Diff line change
@@ -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);

});


})
1 change: 1 addition & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ <h2>Mocha Test Suite</h2>
<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>
Expand Down
4 changes: 4 additions & 0 deletions test/scratch/scratch.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ <h2>Server Options</h2>

<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){
Expand Down
26 changes: 26 additions & 0 deletions www/content/attributes/hx-disabled-elts.md
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions www/content/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit af0dc9c

Please sign in to comment.