Skip to content

Commit a51baeb

Browse files
authored
Merge pull request #1190 from Patternslib/fix-autosubmit
Fix auto submit on non-form elements
2 parents b0b57b1 + a2530f3 commit a51baeb

File tree

6 files changed

+165
-21
lines changed

6 files changed

+165
-21
lines changed

src/core/dom.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,25 @@ const element_uuid = (el) => {
557557
return get_data(el, "uuid");
558558
};
559559

560+
/**
561+
* Find a related form element.
562+
*
563+
* @param {Node} el - The DOM node to start the search from.
564+
* @returns {Node} - The closest form element.
565+
*
566+
* @example
567+
* find_form(document.querySelector("input"));
568+
*/
569+
const find_form = (el) => {
570+
// Prefer input.form which allows for input outside form elements and fall
571+
// back to search for a parent form.
572+
return (
573+
el.form ||
574+
el.querySelector("input, select, textarea, button")?.form ||
575+
el.closest("form")
576+
);
577+
};
578+
560579
const dom = {
561580
toNodeArray: toNodeArray,
562581
querySelectorAllAndMe: querySelectorAllAndMe,
@@ -585,6 +604,7 @@ const dom = {
585604
get_visible_ratio: get_visible_ratio,
586605
escape_css_id: escape_css_id,
587606
element_uuid: element_uuid,
607+
find_form: find_form,
588608
add_event_listener: events.add_event_listener, // BBB export. TODO: Remove in an upcoming version.
589609
remove_event_listener: events.remove_event_listener, // BBB export. TODO: Remove in an upcoming version.
590610
};

src/core/dom.test.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,3 +903,103 @@ describe("element_uuid", function () {
903903
window.crypto.randomUUID = orig_randomUUID;
904904
});
905905
});
906+
907+
describe("find_form", function () {
908+
it("example 1", function () {
909+
document.body.innerHTML = `
910+
<form>
911+
<div id="start"></div>
912+
</form>
913+
`;
914+
const el = document.querySelector("#start");
915+
const form = document.querySelector("form");
916+
expect(dom.find_form(el)).toBe(form);
917+
});
918+
919+
it("example 2", function () {
920+
document.body.innerHTML = `
921+
<form>
922+
<input>
923+
</form>
924+
`;
925+
const el = document.querySelector("input");
926+
const form = document.querySelector("form");
927+
expect(dom.find_form(el)).toBe(form);
928+
});
929+
930+
it("example 3", function () {
931+
document.body.innerHTML = `
932+
<form id="the-form">
933+
</form>
934+
<input form="the-form">
935+
`;
936+
const el = document.querySelector("input");
937+
const form = document.querySelector("#the-form");
938+
expect(dom.find_form(el)).toBe(form);
939+
});
940+
941+
it("example 4", function () {
942+
document.body.innerHTML = `
943+
<form id="the-form">
944+
</form>
945+
<form id="other-form">
946+
<input form="the-form">
947+
</form>
948+
`;
949+
const el = document.querySelector("input");
950+
const form = document.querySelector("#the-form");
951+
expect(dom.find_form(el)).toBe(form);
952+
});
953+
954+
it("example 5", function () {
955+
document.body.innerHTML = `
956+
<form id="the-form">
957+
</form>
958+
<form id="other-form">
959+
<input form="the-form">
960+
</form>
961+
`;
962+
const el = document.querySelector("#other-form");
963+
const form = document.querySelector("#the-form");
964+
expect(dom.find_form(el)).toBe(form);
965+
});
966+
967+
it("example 6", function () {
968+
document.body.innerHTML = `
969+
<form id="the-form">
970+
</form>
971+
<form id="other-form">
972+
<select form="the-form"></select>
973+
</form>
974+
`;
975+
const el = document.querySelector("#other-form");
976+
const form = document.querySelector("#the-form");
977+
expect(dom.find_form(el)).toBe(form);
978+
});
979+
980+
it("example 7", function () {
981+
document.body.innerHTML = `
982+
<form id="the-form">
983+
</form>
984+
<form id="other-form">
985+
<textarea form="the-form"></textarea>
986+
</form>
987+
`;
988+
const el = document.querySelector("#other-form");
989+
const form = document.querySelector("#the-form");
990+
expect(dom.find_form(el)).toBe(form);
991+
});
992+
993+
it("example 8", function () {
994+
document.body.innerHTML = `
995+
<form id="the-form">
996+
</form>
997+
<form id="other-form">
998+
<button form="the-form"></button>
999+
</form>
1000+
`;
1001+
const el = document.querySelector("#other-form");
1002+
const form = document.querySelector("#the-form");
1003+
expect(dom.find_form(el)).toBe(form);
1004+
});
1005+
});

src/pat/auto-submit/auto-submit.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import "../../core/jquery-ext";
22
import $ from "jquery";
33
import Base from "../../core/base";
4+
import dom from "../../core/dom";
45
import events from "../../core/events";
56
import input_change_events from "../../lib/input-change-events";
67
import logging from "../../core/logging";
@@ -46,7 +47,7 @@ export default Base.extend({
4647
data?.pattern === "sortable"
4748
) {
4849
// Directly submit when removing a clone or changing the sorting.
49-
this.el.dispatchEvent(events.submit_event());
50+
dom.find_form(this.el).dispatchEvent(events.submit_event());
5051
log.debug(
5152
`triggered by pat-update, pattern: ${data.pattern}, action: ${data.action}`
5253
);
@@ -128,7 +129,13 @@ export default Base.extend({
128129

129130
onInputChange(e) {
130131
e.stopPropagation();
131-
this.el.dispatchEvent(events.submit_event({ submitter: e.target }));
132-
log.debug("triggered by " + e.type);
132+
dom.find_form(this.el).dispatchEvent(
133+
events.submit_event({ submitter: e.target })
134+
);
135+
log.debug(
136+
`submit event triggered by event ${e.type} by submitter (1) on (2)`,
137+
e.target,
138+
this.el
139+
);
133140
},
134141
});

src/pat/auto-submit/auto-submit.test.js

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,12 @@ describe("pat-autosubmit", function () {
8282
`;
8383
const input = document.querySelector(".pat-autosubmit");
8484
new Pattern(input);
85-
let submit_input_dispatched = false;
8685
let submit_form_dispatched = false;
87-
input.addEventListener("submit", () => {
88-
submit_input_dispatched = true;
89-
});
9086
document.querySelector("form").addEventListener("submit", () => {
9187
submit_form_dispatched = true;
9288
});
9389
input.dispatchEvent(events.input_event());
9490
await utils.timeout(1);
95-
expect(submit_input_dispatched).toBe(true);
9691
expect(submit_form_dispatched).toBe(true);
9792
});
9893

@@ -179,23 +174,41 @@ describe("pat-autosubmit", function () {
179174
`;
180175
const input = document.querySelector(".pat-autosubmit");
181176
new Pattern(input);
182-
let submit_input_dispatched = false;
183177
let submit_form_dispatched = false;
184-
input.addEventListener("submit", () => {
185-
submit_input_dispatched = true;
186-
});
187178
document.querySelector("form").addEventListener("submit", () => {
188179
submit_form_dispatched = true;
189180
});
190181
input.dispatchEvent(events.input_event());
191182
await utils.timeout(1);
192-
expect(submit_input_dispatched).toBe(false);
193183
expect(submit_form_dispatched).toBe(false);
194184
await utils.timeout(9);
195-
expect(submit_input_dispatched).toBe(false);
196185
expect(submit_form_dispatched).toBe(false);
197186
await utils.timeout(10);
198-
expect(submit_input_dispatched).toBe(true);
187+
expect(submit_form_dispatched).toBe(true);
188+
});
189+
190+
it("2.6 - when pat-autosubmit is defined not on aform element", async function () {
191+
document.body.innerHTML = `
192+
<form>
193+
<div
194+
class="pat-autosubmit"
195+
data-pat-autosubmit="delay: 0"
196+
>
197+
<input name="q">
198+
</div>
199+
</form>
200+
`;
201+
const input = document.querySelector("input");
202+
const autosubmit = document.querySelector(".pat-autosubmit");
203+
new Pattern(autosubmit);
204+
205+
let submit_form_dispatched = false;
206+
document.querySelector("form").addEventListener("submit", () => {
207+
submit_form_dispatched = true;
208+
});
209+
210+
input.dispatchEvent(events.input_event());
211+
await utils.timeout(1);
199212
expect(submit_form_dispatched).toBe(true);
200213
});
201214
});

src/pat/auto-suggest/auto-suggest.test.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -428,17 +428,20 @@ describe("pat-autosuggest", function () {
428428
describe("4 - Integration...", function () {
429429
it("4.1 - Works with pat-auto-submit", async function () {
430430
document.body.innerHTML = `
431-
<input
432-
type="text"
433-
class="pat-autosuggest pat-autosubmit"
434-
data-pat-autosuggest="words: apple, orange, pear"
435-
data-pat-autosubmit="delay:0" />
431+
<form>
432+
<input
433+
type="text"
434+
class="pat-autosuggest pat-autosubmit"
435+
data-pat-autosuggest="words: apple, orange, pear"
436+
data-pat-autosubmit="delay:0" />
437+
</form>
436438
`;
437439

438440
const pattern_autosubmit = (await import("../auto-submit/auto-submit")).default; // prettier-ignore
439441
const input = document.querySelector("input");
442+
440443
let submit_dispatched = false;
441-
input.addEventListener("submit", () => {
444+
document.querySelector("form").addEventListener("submit", () => {
442445
submit_dispatched = true;
443446
});
444447

src/pat/inject/inject.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ const inject = {
105105
});
106106
// setup event handlers
107107
if ($el[0]?.tagName === "FORM") {
108+
log.debug("Initializing form with injection on", $el[0]);
108109
events.add_event_listener(
109110
$el[0],
110111
"submit",

0 commit comments

Comments
 (0)