summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authormatiboy <mathieu.chauvinc@gmail.com>2023-10-27 04:43:41 +0800
committerGitHub <noreply@github.com>2023-10-26 14:43:41 -0600
commit712ee759f17ceac51df6c4c8542401a83c35abee (patch)
tree74bb392d1c22914dcf5329a4e081972c02e7cce5
parent1040ace09377abb453ceff56cafa9931f4e097a4 (diff)
downloadhtmx-712ee759f17ceac51df6c4c8542401a83c35abee.tar.gz
htmx-712ee759f17ceac51df6c4c8542401a83c35abee.zip
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>
-rw-r--r--src/htmx.js12
-rw-r--r--test/attributes/hx-confirm.js126
-rw-r--r--test/core/events.js2
-rw-r--r--test/index.html1
-rw-r--r--www/content/attributes/hx-confirm.md10
-rw-r--r--www/content/examples/confirm.md82
6 files changed, 210 insertions, 23 deletions
diff --git a/src/htmx.js b/src/htmx.js
index 74bf5a79..62afd334 100644
--- a/src/htmx.js
+++ b/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()
diff --git a/test/attributes/hx-confirm.js b/test/attributes/hx-confirm.js
new file mode 100644
index 00000000..7f8516d7
--- /dev/null
+++ b/test/attributes/hx-confirm.js
@@ -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);
+ }
+ })
+
+
+}); \ No newline at end of file
diff --git a/test/core/events.js b/test/core/events.js
index 261a407f..7c1c1596 100644
--- a/test/core/events.js
+++ b/test/core/events.js
@@ -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);
}
});
diff --git a/test/index.html b/test/index.html
index 50892993..b0841d25 100644
--- a/test/index.html
+++ b/test/index.html
@@ -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>
diff --git a/www/content/attributes/hx-confirm.md b/www/content/attributes/hx-confirm.md
index 5a8ceaa5..9cb3ced4 100644
--- a/www/content/attributes/hx-confirm.md
+++ b/www/content/attributes/hx-confirm.md
@@ -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
diff --git a/www/content/examples/confirm.md b/www/content/examples/confirm.md
index fde399f0..d1f00c49 100644
--- a/www/content/examples/confirm.md
+++ b/www/content/examples/confirm.md
@@ -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. :)
-{{ demoenv() }}
+## 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>