mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-10-02 07:21:05 +00:00
Fix confirmed
being ignored in htmx:confirm event (#1610)
* Current behavior testing Testing current library behavior * Test should remove correct handler * Add question in htmx:confirm event detail * Allow skipping window.confirm * Additional test without hx-confirm value * Wrap htmx.off in finally * More correct assertion in case of no calls to confirm * Remove erroneously added formatting * Remove erroneously added formatting * Documentation, fix loop --------- Co-authored-by: mat <matt@techspace.asia>
This commit is contained in:
parent
1040ace093
commit
712ee759f1
12
src/htmx.js
12
src/htmx.js
@ -2975,12 +2975,13 @@ return (function () {
|
||||
}
|
||||
}
|
||||
|
||||
var confirmQuestion = getClosestAttributeValue(elt, "hx-confirm");
|
||||
// allow event-based confirmation w/ a callback
|
||||
if (!confirmed) {
|
||||
var issueRequest = function() {
|
||||
return issueAjaxRequest(verb, path, elt, event, etc, true);
|
||||
if (confirmed === undefined) {
|
||||
var issueRequest = function(skipConfirmation) {
|
||||
return issueAjaxRequest(verb, path, elt, event, etc, !!skipConfirmation);
|
||||
}
|
||||
var confirmDetails = {target: target, elt: elt, path: path, verb: verb, triggeringEvent: event, etc: etc, issueRequest: issueRequest};
|
||||
var confirmDetails = {target: target, elt: elt, path: path, verb: verb, triggeringEvent: event, etc: etc, issueRequest: issueRequest, question: confirmQuestion};
|
||||
if (triggerEvent(elt, 'htmx:confirm', confirmDetails) === false) {
|
||||
maybeCall(resolve);
|
||||
return promise;
|
||||
@ -3081,8 +3082,7 @@ return (function () {
|
||||
}
|
||||
}
|
||||
|
||||
var confirmQuestion = getClosestAttributeValue(elt, "hx-confirm");
|
||||
if (confirmQuestion) {
|
||||
if (confirmQuestion && !confirmed) {
|
||||
if(!confirm(confirmQuestion)) {
|
||||
maybeCall(resolve);
|
||||
endRequestLock()
|
||||
|
126
test/attributes/hx-confirm.js
Normal file
126
test/attributes/hx-confirm.js
Normal file
@ -0,0 +1,126 @@
|
||||
describe("hx-confirm attribute", function () {
|
||||
var confirm
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
confirm = sinon.stub(window, "confirm");
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
confirm.restore()
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('prompts using window.confirm when hx-confirm is set', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
confirm.returns(true);
|
||||
var btn = make('<button hx-get="/test" hx-confirm="Sure?">Click Me!</button>')
|
||||
btn.click();
|
||||
confirm.calledOnce.should.equal(true);
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
})
|
||||
|
||||
it('stops the request if confirm is cancelled', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
confirm.returns(false);
|
||||
var btn = make('<button hx-get="/test" hx-confirm="Sure?">Click Me!</button>')
|
||||
btn.click();
|
||||
confirm.calledOnce.should.equal(true);
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Click Me!");
|
||||
})
|
||||
|
||||
it('uses the value of hx-confirm as the prompt', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
confirm.returns(false);
|
||||
var btn = make('<button hx-get="/test" hx-confirm="Sure?">Click Me!</button>')
|
||||
btn.click();
|
||||
confirm.firstCall.args[0].should.equal("Sure?");
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Click Me!");
|
||||
})
|
||||
|
||||
it('should prompt when htmx:confirm handler calls issueRequest', function () {
|
||||
try {
|
||||
var btn = make('<button hx-get="/test" hx-confirm="Surely?">Click Me!</button>')
|
||||
var handler = htmx.on("htmx:confirm", function (evt) {
|
||||
evt.preventDefault();
|
||||
evt.detail.issueRequest();
|
||||
});
|
||||
btn.click();
|
||||
confirm.calledOnce.should.equal(true);
|
||||
} finally {
|
||||
htmx.off("htmx:confirm", handler);
|
||||
}
|
||||
})
|
||||
|
||||
it('should include the question in htmx:confirm event', function () {
|
||||
var stub = sinon.stub();
|
||||
try {
|
||||
var btn = make('<button hx-get="/test" hx-confirm="Surely?">Click Me!</button>')
|
||||
var handler = htmx.on("htmx:confirm", stub);
|
||||
btn.click();
|
||||
stub.calledOnce.should.equal(true);
|
||||
stub.firstCall.args[0].detail.should.have.property("question", "Surely?");
|
||||
} finally {
|
||||
htmx.off("htmx:confirm", handler);
|
||||
}
|
||||
})
|
||||
|
||||
it('should allow skipping built-in window.confirm when using issueRequest', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
try {
|
||||
var btn = make('<button hx-get="/test" hx-confirm="Sure?">Click Me!</button>')
|
||||
var handler = htmx.on("htmx:confirm", function (evt) {
|
||||
evt.detail.question.should.equal("Sure?");
|
||||
evt.preventDefault();
|
||||
evt.detail.issueRequest(true);
|
||||
});
|
||||
btn.click();
|
||||
confirm.called.should.equal(false);
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
} finally {
|
||||
htmx.off("htmx:confirm", handler);
|
||||
}
|
||||
})
|
||||
it('should allow skipping built-in window.confirm when using issueRequest', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
try {
|
||||
var btn = make('<button hx-get="/test" hx-confirm="Sure?">Click Me!</button>')
|
||||
var handler = htmx.on("htmx:confirm", function (evt) {
|
||||
evt.detail.question.should.equal("Sure?");
|
||||
evt.preventDefault();
|
||||
evt.detail.issueRequest(true);
|
||||
});
|
||||
btn.click();
|
||||
confirm.called.should.equal(false);
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
} finally {
|
||||
htmx.off("htmx:confirm", handler);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
it('should allow htmx:confirm even when no hx-confirm is set', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
try {
|
||||
var btn = make('<button hx-get="/test">Click Me!</button>')
|
||||
var handler = htmx.on("htmx:confirm", function (evt) {
|
||||
evt.detail.should.have.property("question", null);
|
||||
evt.preventDefault();
|
||||
evt.detail.issueRequest();
|
||||
});
|
||||
btn.click();
|
||||
confirm.called.should.equal(false); // no hx-confirm means no window.confirm
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
} finally {
|
||||
htmx.off("htmx:confirm", handler);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
});
|
@ -659,7 +659,7 @@ describe("Core htmx Events", function() {
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("updated");
|
||||
} finally {
|
||||
htmx.off("htmx:load", handler);
|
||||
htmx.off("htmx:confirm", handler);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -70,6 +70,7 @@
|
||||
|
||||
<!-- attribute tests -->
|
||||
<script src="attributes/hx-boost.js"></script>
|
||||
<script src="attributes/hx-confirm.js"></script>
|
||||
<script src="attributes/hx-delete.js"></script>
|
||||
<script src="attributes/hx-ext.js"></script>
|
||||
<script src="attributes/hx-get.js"></script>
|
||||
|
@ -13,6 +13,16 @@ Here is an example:
|
||||
</button>
|
||||
```
|
||||
|
||||
## Event details
|
||||
|
||||
The event triggered by `hx-confirm` contains additional properties in its `detail`:
|
||||
|
||||
* triggeringEvent: the event that triggered the original request
|
||||
* issueRequest(skipConfirmation=false): a callback which can be used to confirm the AJAX request
|
||||
* question: the value of the `hx-confirm` attribute on the HTML element
|
||||
|
||||
## Notes
|
||||
|
||||
* `hx-confirm` is inherited and can be placed on a parent element
|
||||
* `hx-confirm` uses the browser's `window.confirm` by default. You can customize this behavior as shown [in this example](@/examples/confirm.md).
|
||||
* a boolean `skipConfirmation` can be passed to the `issueRequest` callback; if true (defaults to false), the `window.confirm` will not be called and the AJAX request is issued directly
|
||||
|
@ -8,32 +8,78 @@ action. This uses the default `confirm()` function in javascript which, while t
|
||||
applications UX.
|
||||
|
||||
In this example we will see how to use [sweetalert2](https://sweetalert2.github.io) and the [`htmx:confirm`](@/events.md#htmx:confirm)
|
||||
event to implement a custom confirmation dialog.
|
||||
event to implement a custom confirmation dialog. Below are two examples, one with `hyperscript` using a click+custom event method, and one in vanilla JS and the built-in `hx-confirm` attribute.
|
||||
|
||||
## Hyperscript, on click+custom event
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<button hx-get="/confirmed"
|
||||
_="on htmx:confirm(issueRequest)
|
||||
halt the event
|
||||
call Swal.fire({title: 'Confirm', text:'Do you want to continue?'})
|
||||
if result.isConfirmed issueRequest()">
|
||||
<button hx-get="/confirmed"
|
||||
hx-trigger='confirmed'
|
||||
_="on click
|
||||
call Swal.fire({title: 'Confirm', text:'Do you want to continue?'})
|
||||
if result.isConfirmed trigger confirmed">
|
||||
Click Me
|
||||
</button>
|
||||
```
|
||||
|
||||
We add some hyperscript to invoke Sweet Alert 2 on a click, asking for confirmation. If the user confirms
|
||||
the dialog, we trigger the request by invoking the `issueRequest()` function, which was destructured from the event
|
||||
detail object.
|
||||
the dialog, we trigger the request by triggering the custom "confirmed" event
|
||||
which is then picked up by `hx-trigger`.
|
||||
|
||||
Note that we are taking advantage of the fact that hyperscript is [async-transparent](https://hyperscript.org/docs/#async)
|
||||
and automatically resolves the Promise returned by `Swal.fire()`.
|
||||
|
||||
A VanillaJS implementation is left as an exercise for the reader. :)
|
||||
|
||||
## Vanilla JS, hx-confirm
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script>
|
||||
document.addEventListener("htmx:confirm", function(e) {
|
||||
e.preventDefault()
|
||||
Swal.fire({
|
||||
title: "Proceed?",
|
||||
text: `I ask you... ${e.detail.question}`
|
||||
}).then(function(result) {
|
||||
if(result.isConfirmed) e.detail.issueRequest(true) // use true to skip window.confirm
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<button hx-get="/confirmed" hx-confirm="Some confirm text here">
|
||||
Click Me
|
||||
</button>
|
||||
```
|
||||
|
||||
We add some javascript to invoke Sweet Alert 2 on a click, asking for confirmation. If the user confirms
|
||||
the dialog, we trigger the request by calling the `issueRequest` method. We pass `skipConfirmation=true` as argument to skip `window.confirm`.
|
||||
|
||||
This allows to use `hx-confirm`'s value in the prompt which is convenient
|
||||
when the question depends on the element e.g. a django list:
|
||||
|
||||
```html
|
||||
{% for row in clients %}
|
||||
<button hx-post="/delete/{{client.pk}}" hx-confirm="Delete {{client.name}}??">Delete</button>
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
{{ demoenv() }}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("htmx:confirm", function(e) {
|
||||
e.preventDefault()
|
||||
Swal.fire({
|
||||
title: "Proceed?",
|
||||
text: `I ask you... ${e.detail.question}`,
|
||||
showCancelButton: true
|
||||
}).then(function(result) {
|
||||
if(result.isConfirmed) e.detail.issueRequest(true)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<script>
|
||||
|
||||
//=========================================================================
|
||||
@ -51,13 +97,17 @@ A VanillaJS implementation is left as an exercise for the reader. :)
|
||||
|
||||
// templates
|
||||
function initialUI() {
|
||||
return `<button hx-trigger='confirmed'
|
||||
hx-get="/confirmed"
|
||||
_="on click
|
||||
call Swal.fire({title: 'Confirm', text:'Do you want to continue?'})
|
||||
if result.isConfirmed trigger confirmed">
|
||||
Click Me
|
||||
</button>`;
|
||||
return `<button hx-get="/confirmed"
|
||||
_="on htmx:confirm(issueRequest)
|
||||
halt the event
|
||||
call Swal.fire({title: 'Confirm', text:'Do you want to continue?'})
|
||||
if result.isConfirmed issueRequest()">
|
||||
Click me (hyperscript click & custom event)
|
||||
</button><br><br>
|
||||
<button id="confirmButton" hx-get="/confirmed" hx-confirm="Some confirm text here">
|
||||
Click Me (vanilla JS, hx-confirm)
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user