Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix auto submit on non-form elements #1190

Merged
merged 4 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/core/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,25 @@ const element_uuid = (el) => {
return get_data(el, "uuid");
};

/**
* Find a related form element.
*
* @param {Node} el - The DOM node to start the search from.
* @returns {Node} - The closest form element.
*
* @example
* find_form(document.querySelector("input"));
*/
const find_form = (el) => {
// Prefer input.form which allows for input outside form elements and fall
// back to search for a parent form.
return (
el.form ||
el.querySelector("input, select, textarea, button")?.form ||
el.closest("form")
);
};

const dom = {
toNodeArray: toNodeArray,
querySelectorAllAndMe: querySelectorAllAndMe,
Expand Down Expand Up @@ -585,6 +604,7 @@ const dom = {
get_visible_ratio: get_visible_ratio,
escape_css_id: escape_css_id,
element_uuid: element_uuid,
find_form: find_form,
add_event_listener: events.add_event_listener, // BBB export. TODO: Remove in an upcoming version.
remove_event_listener: events.remove_event_listener, // BBB export. TODO: Remove in an upcoming version.
};
Expand Down
100 changes: 100 additions & 0 deletions src/core/dom.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -903,3 +903,103 @@ describe("element_uuid", function () {
window.crypto.randomUUID = orig_randomUUID;
});
});

describe("find_form", function () {
it("example 1", function () {
document.body.innerHTML = `
<form>
<div id="start"></div>
</form>
`;
const el = document.querySelector("#start");
const form = document.querySelector("form");
expect(dom.find_form(el)).toBe(form);
});

it("example 2", function () {
document.body.innerHTML = `
<form>
<input>
</form>
`;
const el = document.querySelector("input");
const form = document.querySelector("form");
expect(dom.find_form(el)).toBe(form);
});

it("example 3", function () {
document.body.innerHTML = `
<form id="the-form">
</form>
<input form="the-form">
`;
const el = document.querySelector("input");
const form = document.querySelector("#the-form");
expect(dom.find_form(el)).toBe(form);
});

it("example 4", function () {
document.body.innerHTML = `
<form id="the-form">
</form>
<form id="other-form">
<input form="the-form">
</form>
`;
const el = document.querySelector("input");
const form = document.querySelector("#the-form");
expect(dom.find_form(el)).toBe(form);
});

it("example 5", function () {
document.body.innerHTML = `
<form id="the-form">
</form>
<form id="other-form">
<input form="the-form">
</form>
`;
const el = document.querySelector("#other-form");
const form = document.querySelector("#the-form");
expect(dom.find_form(el)).toBe(form);
});

it("example 6", function () {
document.body.innerHTML = `
<form id="the-form">
</form>
<form id="other-form">
<select form="the-form"></select>
</form>
`;
const el = document.querySelector("#other-form");
const form = document.querySelector("#the-form");
expect(dom.find_form(el)).toBe(form);
});

it("example 7", function () {
document.body.innerHTML = `
<form id="the-form">
</form>
<form id="other-form">
<textarea form="the-form"></textarea>
</form>
`;
const el = document.querySelector("#other-form");
const form = document.querySelector("#the-form");
expect(dom.find_form(el)).toBe(form);
});

it("example 8", function () {
document.body.innerHTML = `
<form id="the-form">
</form>
<form id="other-form">
<button form="the-form"></button>
</form>
`;
const el = document.querySelector("#other-form");
const form = document.querySelector("#the-form");
expect(dom.find_form(el)).toBe(form);
});
});
13 changes: 10 additions & 3 deletions src/pat/auto-submit/auto-submit.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "../../core/jquery-ext";
import $ from "jquery";
import Base from "../../core/base";
import dom from "../../core/dom";
import events from "../../core/events";
import input_change_events from "../../lib/input-change-events";
import logging from "../../core/logging";
Expand Down Expand Up @@ -46,7 +47,7 @@ export default Base.extend({
data?.pattern === "sortable"
) {
// Directly submit when removing a clone or changing the sorting.
this.el.dispatchEvent(events.submit_event());
dom.find_form(this.el).dispatchEvent(events.submit_event());
log.debug(
`triggered by pat-update, pattern: ${data.pattern}, action: ${data.action}`
);
Expand Down Expand Up @@ -128,7 +129,13 @@ export default Base.extend({

onInputChange(e) {
e.stopPropagation();
this.el.dispatchEvent(events.submit_event({ submitter: e.target }));
log.debug("triggered by " + e.type);
dom.find_form(this.el).dispatchEvent(
events.submit_event({ submitter: e.target })
);
log.debug(
`submit event triggered by event ${e.type} by submitter (1) on (2)`,
e.target,
this.el
);
},
});
37 changes: 25 additions & 12 deletions src/pat/auto-submit/auto-submit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,12 @@ describe("pat-autosubmit", function () {
`;
const input = document.querySelector(".pat-autosubmit");
new Pattern(input);
let submit_input_dispatched = false;
let submit_form_dispatched = false;
input.addEventListener("submit", () => {
submit_input_dispatched = true;
});
document.querySelector("form").addEventListener("submit", () => {
submit_form_dispatched = true;
});
input.dispatchEvent(events.input_event());
await utils.timeout(1);
expect(submit_input_dispatched).toBe(true);
expect(submit_form_dispatched).toBe(true);
});

Expand Down Expand Up @@ -179,23 +174,41 @@ describe("pat-autosubmit", function () {
`;
const input = document.querySelector(".pat-autosubmit");
new Pattern(input);
let submit_input_dispatched = false;
let submit_form_dispatched = false;
input.addEventListener("submit", () => {
submit_input_dispatched = true;
});
document.querySelector("form").addEventListener("submit", () => {
submit_form_dispatched = true;
});
input.dispatchEvent(events.input_event());
await utils.timeout(1);
expect(submit_input_dispatched).toBe(false);
expect(submit_form_dispatched).toBe(false);
await utils.timeout(9);
expect(submit_input_dispatched).toBe(false);
expect(submit_form_dispatched).toBe(false);
await utils.timeout(10);
expect(submit_input_dispatched).toBe(true);
expect(submit_form_dispatched).toBe(true);
});

it("2.6 - when pat-autosubmit is defined not on aform element", async function () {
document.body.innerHTML = `
<form>
<div
class="pat-autosubmit"
data-pat-autosubmit="delay: 0"
>
<input name="q">
</div>
</form>
`;
const input = document.querySelector("input");
const autosubmit = document.querySelector(".pat-autosubmit");
new Pattern(autosubmit);

let submit_form_dispatched = false;
document.querySelector("form").addEventListener("submit", () => {
submit_form_dispatched = true;
});

input.dispatchEvent(events.input_event());
await utils.timeout(1);
expect(submit_form_dispatched).toBe(true);
});
});
Expand Down
15 changes: 9 additions & 6 deletions src/pat/auto-suggest/auto-suggest.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -428,17 +428,20 @@ describe("pat-autosuggest", function () {
describe("4 - Integration...", function () {
it("4.1 - Works with pat-auto-submit", async function () {
document.body.innerHTML = `
<input
type="text"
class="pat-autosuggest pat-autosubmit"
data-pat-autosuggest="words: apple, orange, pear"
data-pat-autosubmit="delay:0" />
<form>
<input
type="text"
class="pat-autosuggest pat-autosubmit"
data-pat-autosuggest="words: apple, orange, pear"
data-pat-autosubmit="delay:0" />
</form>
`;

const pattern_autosubmit = (await import("../auto-submit/auto-submit")).default; // prettier-ignore
const input = document.querySelector("input");

let submit_dispatched = false;
input.addEventListener("submit", () => {
document.querySelector("form").addEventListener("submit", () => {
submit_dispatched = true;
});

Expand Down
1 change: 1 addition & 0 deletions src/pat/inject/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ const inject = {
});
// setup event handlers
if ($el[0]?.tagName === "FORM") {
log.debug("Initializing form with injection on", $el[0]);
events.add_event_listener(
$el[0],
"submit",
Expand Down