mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-09-30 06:21:19 +00:00
Add data-loading-aria-busy directive for loading-states extension (#864)
* Add data-loading-aria-busy directive for loading-states extension; add tests for the extension * Update docs * Fix tests of scoped loading states * Commit test files * Improve delay tests by using sinon timer * Add reference to MDN about `aria-busy` attribute
This commit is contained in:
parent
e2944595a3
commit
70b4ad16f0
@ -37,6 +37,7 @@
|
||||
"@11ty/eleventy": "^0.10.0",
|
||||
"@11ty/eleventy-plugin-rss": "^1.1.2",
|
||||
"chai": "^4.3.6",
|
||||
"chai-dom": "^1.11.0",
|
||||
"eleventy-plugin-sass": "^1.2.0",
|
||||
"fs-extra": "^9.1.0",
|
||||
"mocha": "^7.2.0",
|
||||
|
@ -69,6 +69,7 @@
|
||||
'data-loading-class',
|
||||
'data-loading-class-remove',
|
||||
'data-loading-disable',
|
||||
'data-loading-aria-busy',
|
||||
]
|
||||
|
||||
let loadingStateEltsByType = {}
|
||||
@ -153,6 +154,19 @@
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
loadingStateEltsByType['data-loading-aria-busy'].forEach(
|
||||
(sourceElt) => {
|
||||
getLoadingTarget(sourceElt).forEach((targetElt) => {
|
||||
queueLoadingState(
|
||||
sourceElt,
|
||||
targetElt,
|
||||
() => (targetElt.setAttribute("aria-busy", "true")),
|
||||
() => (targetElt.removeAttribute("aria-busy"))
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (name === 'htmx:afterOnLoad') {
|
||||
|
143
test/ext/loading-states.js
Normal file
143
test/ext/loading-states.js
Normal file
@ -0,0 +1,143 @@
|
||||
describe("loading states extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
this.clock = sinon.useFakeTimers();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
this.clock.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('works on basic setup', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="loading-states">Click Me!</button>');
|
||||
var element = make('<div data-loading>');
|
||||
btn.click();
|
||||
element.style.display.should.be.equal("inline-block");
|
||||
this.server.respond();
|
||||
element.style.display.should.be.equal("none");
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with custom display', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="loading-states">Click Me!</button>');
|
||||
var element = make('<div data-loading="flex">');
|
||||
btn.click();
|
||||
element.style.display.should.be.equal("flex");
|
||||
this.server.respond();
|
||||
element.style.display.should.be.equal("none");
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with classes', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="loading-states">Click Me!</button>');
|
||||
var element = make('<div data-loading-class="test">');
|
||||
btn.click();
|
||||
element.should.have.class("test");
|
||||
this.server.respond();
|
||||
element.should.not.have.class("test");
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with classes removal', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="loading-states">Click Me!</button>');
|
||||
var element = make('<div data-loading-class-remove="test" class="test">');
|
||||
btn.click();
|
||||
element.should.not.have.class("test");
|
||||
this.server.respond();
|
||||
element.should.have.class("test");
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with disabling', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="loading-states">Click Me!</button>');
|
||||
var element = make('<button data-loading-disable>');
|
||||
btn.click();
|
||||
element.disabled.should.be.true;
|
||||
this.server.respond();
|
||||
element.disabled.should.be.false;
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with aria-busy', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="loading-states">Click Me!</button>');
|
||||
var element = make('<button data-loading-aria-busy>');
|
||||
btn.click();
|
||||
element.should.have.attribute("aria-busy", "true");
|
||||
this.server.respond();
|
||||
element.should.not.have.attribute("aria-busy");
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with multiple directives', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="loading-states">Click Me!</button>');
|
||||
var element = make('<button data-loading-aria-busy data-loading-class="loading" data-loading-class-remove="not-loading" class="not-loading">');
|
||||
btn.click();
|
||||
element.should.have.attribute("aria-busy", "true");
|
||||
element.should.have.class("loading")
|
||||
element.should.not.have.class("not-loading")
|
||||
this.server.respond();
|
||||
element.should.not.have.attribute("aria-busy");
|
||||
element.should.not.have.class("loading")
|
||||
element.should.have.class("not-loading")
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with delay', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="loading-states">Click Me!</button>');
|
||||
var element = make('<div data-loading-class-remove="test" data-loading-delay="1s" class="test">');
|
||||
btn.click();
|
||||
element.should.have.class("test");
|
||||
this.clock.tick(1000);
|
||||
element.should.not.have.class("test");
|
||||
this.server.respond();
|
||||
element.should.have.class("test");
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with custom targets', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="loading-states" data-loading-target="#loader" data-loading-class="test">Click Me!</button>');
|
||||
var element = make('<div id="loader">');
|
||||
btn.click();
|
||||
element.should.have.class("test");
|
||||
this.server.respond();
|
||||
element.should.not.have.class("test");
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with path filters', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="loading-states" >Click Me!</button>');
|
||||
var matchingRequestElement = make('<div data-loading-class="test" data-loading-path="/test">');
|
||||
var nonMatchingPathElement = make('<div data-loading-class="test" data-loading-path="/test1">');
|
||||
btn.click();
|
||||
matchingRequestElement.should.have.class("test");
|
||||
nonMatchingPathElement.should.not.have.class("test");
|
||||
this.server.respond();
|
||||
matchingRequestElement.should.not.have.class("test");
|
||||
nonMatchingPathElement.should.not.have.class("test");
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with scopes', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<div data-loading-states><button hx-get="/test" hx-ext="loading-states" >Click Me!</button></div>');
|
||||
var element = make('<div data-loading-class="test">');
|
||||
btn.getElementsByTagName("button")[0].click();
|
||||
element.should.not.have.class("test");
|
||||
this.server.respond();
|
||||
element.should.not.have.class("test");
|
||||
btn.getElementsByTagName("button")[0].innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
});
|
@ -29,6 +29,7 @@
|
||||
<a href="index.html">[ALL]</a>
|
||||
|
||||
<script src="../node_modules/chai/chai.js"></script>
|
||||
<script src="../node_modules/chai-dom/chai-dom.js"></script>
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/mocha-webdriver-runner/dist/mocha-webdriver-client.js"></script>
|
||||
<script src="../node_modules/sinon/pkg/sinon.js"></script>
|
||||
@ -112,6 +113,9 @@
|
||||
<script src="../src/ext/class-tools.js"></script>
|
||||
<script src="ext/class-tools.js"></script>
|
||||
|
||||
<script src="../src/ext/loading-states.js"></script>
|
||||
<script src="ext/loading-states.js"></script>
|
||||
|
||||
<script src="ext/bad-extension.js"></script>
|
||||
|
||||
<script src="../src/ext/remove-me.js"></script>
|
||||
|
@ -60,6 +60,14 @@ Add the following class to your stylesheet to make sure elements are hidden by d
|
||||
<button data-loading-disable>Submit</button>
|
||||
```
|
||||
|
||||
- `data-loading-aria-busy`
|
||||
|
||||
Add [`aria-busy="true"`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-busy) attribute to the element for the duration of the request
|
||||
|
||||
```html
|
||||
<button data-loading-aria-busy>Submit</button>
|
||||
```
|
||||
|
||||
- `data-loading-delay`
|
||||
|
||||
Some actions may update quickly and showing a loading state in these cases may be more of a distraction. This attribute ensures that the loading state changes are applied only after 200ms if the request is not finished. The default delay can be modified through the attribute value and expressed in milliseconds:
|
||||
|
Loading…
x
Reference in New Issue
Block a user