diff --git a/src/htmx.js b/src/htmx.js index 7efb762b..0d9060ca 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -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) { diff --git a/test/attributes/hx-disabled-elts.js b/test/attributes/hx-disabled-elts.js new file mode 100644 index 00000000..f66fd5e8 --- /dev/null +++ b/test/attributes/hx-disabled-elts.js @@ -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('') + 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('') + 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('
') + 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('') + var b2 = make('') + var b3 = make('') + 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('') + var b2 = make('') + var b3 = make('') + + 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); + + }); + + +}) diff --git a/test/index.html b/test/index.html index 26a353db..6da68a49 100644 --- a/test/index.html +++ b/test/index.html @@ -72,6 +72,7 @@ + diff --git a/test/scratch/scratch.html b/test/scratch/scratch.html index 6cacd3d2..b73ff46d 100644 --- a/test/scratch/scratch.html +++ b/test/scratch/scratch.html @@ -68,6 +68,10 @@ Autorespond: Log It... + +