Skip to content

Commit

Permalink
prevent double click form submit (#757)
Browse files Browse the repository at this point in the history
fixes #755

quickfix...  
see #756 for the
root issue
  • Loading branch information
derTobsch authored Jul 5, 2024
2 parents 7c01a9e + 0544d6c commit 5553ff3
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/main/javascript/bundles/user-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import "../components/details-dropdown";
import "../components/feedback-form";
import "../components/navigation";
import "../components/time-clock";
import { initPreventDoubleClickSubmit } from "../components/form";
import { initFeedbackHeartView } from "../components/feedback-heart";

const showFeedbackKudo =
Expand All @@ -15,3 +16,5 @@ initFeedbackHeartView({
showFeedbackKudo: showFeedbackKudo,
},
});

initPreventDoubleClickSubmit();
81 changes: 81 additions & 0 deletions src/main/javascript/components/form/autosubmit.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {
afterEach,
beforeAll,
beforeEach,
describe,
expect,
test,
} from "vitest";
import { initAutosubmit } from "./autosubmit";

describe("autosubmit", () => {
beforeAll(() => {
initAutosubmit();
});

beforeEach(() => {
// prevent HTMLFormElement.prototype.requestSubmit is not implemented log.
//
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
window._virtualConsole.emit = () => {};
});

afterEach(() => {
while (document.body.firstChild) {
document.body.firstChild.remove();
}
});

test("auto-submits text input", async () => {
document.body.innerHTML = `
<form action="#">
<input type="text" data-auto-submit="submitter" />
<button type="submit" id="submitter">Submit</button>
</form>
`;

let submitter;

document.querySelector("form").addEventListener("submit", function (event) {
submitter = event.submitter;
});

const inputElement = document.querySelector("input");
inputElement.value = "awesome text";
inputElement.dispatchEvent(new InputEvent("input", { bubbles: true }));

await wait();

expect(submitter).toBe(document.querySelector("button"));
});

test("auto-submits text input with custom delay", async () => {
document.body.innerHTML = `
<form action="#">
<input type="text" data-auto-submit="submitter" data-auto-submit-delay="100" />
<button type="submit" id="submitter">Submit</button>
</form>
`;

let submitter;

document.querySelector("form").addEventListener("submit", function (event) {
submitter = event.submitter;
});

const inputElement = document.querySelector("input");
inputElement.value = "awesome text";
inputElement.dispatchEvent(new InputEvent("input", { bubbles: true }));

await wait();
expect(submitter).toBeUndefined();

await wait(100);
expect(submitter).toBe(document.querySelector("button"));
});
});

function wait(delay = 0) {
return new Promise((resolve) => setTimeout(resolve, delay));
}
9 changes: 9 additions & 0 deletions src/main/javascript/components/form/autosubmit.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/**
* Adds `input` and `change` event listeners and submits forms automatically.
*
* <p>Autosubmit can be configured with the `data-autosubmit` attribute on HTML elements.
* ```html
* <input type="text" data-autosubmit="submitter" data-auto-submit-delay="100" />
* <button type="submit" id="submitter">Submit</button>
* ```
*/
export function initAutosubmit() {
let keyupSubmit;

Expand Down
1 change: 1 addition & 0 deletions src/main/javascript/components/form/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./autosubmit";
export * from "./prevent-double-click-submit";
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
afterEach,
beforeAll,
beforeEach,
describe,
expect,
test,
} from "vitest";
import { initPreventDoubleClickSubmit } from "./prevent-double-click-submit";

describe("DoubleClickSubmitGuard", () => {
beforeAll(() => {
initPreventDoubleClickSubmit();
});

beforeEach(() => {
// prevent HTMLFormElement.prototype.requestSubmit is not implemented log.
//
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
window._virtualConsole.emit = () => {};
});

afterEach(() => {
while (document.body.firstChild) {
document.body.firstChild.remove();
}
});

test("disables submitter on form submit", () => {
document.body.innerHTML = `
<form action="#">
<button type="submit">Submit</button>
</form>
`;

const button = document.querySelector("button");

expect(button.getAttribute("disabled")).toBeNull();

button.click();

expect(button.getAttribute("disabled")).toBe("");
});

test("does not disable submitter on form submit when defaultPrevented", () => {
document.body.innerHTML = `
<form action="#">
<button type="submit">Submit</button>
</form>
`;

let called = false;

document.querySelector("form").addEventListener("submit", function (event) {
event.preventDefault();
called = true;
});

const button = document.querySelector("button");

button.click();

expect(button.getAttribute("disabled")).toBeNull();
expect(called).toBe(true);
});
});
13 changes: 13 additions & 0 deletions src/main/javascript/components/form/prevent-double-click-submit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Adds a global `submit` listener and disables the submitter.
*/
export function initPreventDoubleClickSubmit() {
window.addEventListener("submit", function (event) {
if (!event.defaultPrevented) {
event.submitter?.setAttribute("disabled", "");
// full page reload renders the form again with enabled submitter.
// maybe @hotwired/turbo is enabled somewhere. in this case, however, turbo
// handles the disabled attribute of the submitter.
}
});
}

0 comments on commit 5553ff3

Please sign in to comment.