From 2f108db6ab5825a6076aca59473e4e412b2bf026 Mon Sep 17 00:00:00 2001 From: Filip Halama Date: Fri, 17 May 2024 13:30:05 +0200 Subject: [PATCH] SubmitEvent.submitter & FormData(form, submitter) Solves #401 --- .github/workflows/test.yml | 2 +- src/core/UIHandler.ts | 80 +++++++++++--------------------------- tests/Naja.UIHandler.js | 26 ++++++------- 3 files changed, 36 insertions(+), 72 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dcfcf79..8df4708 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,7 @@ name: Test on: push: - branches: [main] + branches: [main, naja-formdata-submitter] pull_request: branches: [main] diff --git a/src/core/UIHandler.ts b/src/core/UIHandler.ts index 037685e..10b9ab3 100644 --- a/src/core/UIHandler.ts +++ b/src/core/UIHandler.ts @@ -1,5 +1,5 @@ import {Naja, Options, Payload} from '../Naja'; -import {assert, onDomReady, TypedEventListener} from '../utils'; +import {onDomReady, TypedEventListener} from '../utils'; export class UIHandler extends EventTarget { public selector: string = '.ajax'; @@ -20,29 +20,18 @@ export class UIHandler extends EventTarget { } public bindUI(element: Element): void { - const selectors = [ - `a${this.selector}`, - `input[type="submit"]${this.selector}`, - `input[type="image"]${this.selector}`, - `button[type="submit"]${this.selector}`, - `button[form]:not([type])${this.selector}`, - `form button:not([type])${this.selector}`, - `form${this.selector} input[type="submit"]`, - `form${this.selector} input[type="image"]`, - `form${this.selector} button[type="submit"]`, - `form${this.selector} button:not([type])`, - ].join(', '); - - const bindElement = (element: Element) => { + const selector = `a${this.selector}`; + + const bindElement = (element: HTMLAnchorElement) => { element.removeEventListener('click', this.handler); element.addEventListener('click', this.handler); }; - const elements = element.querySelectorAll(selectors); - elements.forEach((element) => bindElement(element)); + const elements = element.querySelectorAll(selector); + elements.forEach((element) => bindElement(element as HTMLAnchorElement)); - if (element.matches(selectors)) { - bindElement(element); + if (element.matches(selector)) { + bindElement(element as HTMLAnchorElement); } const bindForm = (form: HTMLFormElement) => { @@ -50,11 +39,11 @@ export class UIHandler extends EventTarget { form.addEventListener('submit', this.handler); }; - if (element.matches(`form${this.selector}`)) { + if (element.tagName === 'FORM') { bindForm(element as HTMLFormElement); } - const forms = element.querySelectorAll(`form${this.selector}`); + const forms = element.querySelectorAll('form'); forms.forEach((form) => bindForm(form as HTMLFormElement)); } @@ -73,50 +62,25 @@ export class UIHandler extends EventTarget { }; if (event.type === 'submit') { - this.submitForm(element as HTMLFormElement, options, event).catch(ignoreErrors); + const {submitter} = (event as SubmitEvent); + if ((element as HTMLFormElement).matches(this.selector) || submitter?.matches(this.selector)) { + this.submitForm(element as HTMLFormElement, options, event as SubmitEvent).catch(ignoreErrors); + } } else if (event.type === 'click') { - this.clickElement(element as HTMLElement, options, mouseEvent).catch(ignoreErrors); + this.clickElement(element as HTMLAnchorElement, options, mouseEvent).catch(ignoreErrors); } } - public async clickElement(element: HTMLElement, options: Options = {}, event?: MouseEvent): Promise { - let method: string = 'GET', url: string = '', data: any; - - if (element.tagName === 'A') { - assert(element instanceof HTMLAnchorElement); - - method = 'GET'; - url = element.href; - data = null; - - } else if (element.tagName === 'INPUT' || element.tagName === 'BUTTON') { - assert(element instanceof HTMLInputElement || element instanceof HTMLButtonElement); - - const {form} = element; - // eslint-disable-next-line no-nested-ternary,no-extra-parens - method = element.getAttribute('formmethod')?.toUpperCase() ?? form?.getAttribute('method')?.toUpperCase() ?? 'GET'; - url = element.getAttribute('formaction') ?? form?.getAttribute('action') ?? window.location.pathname + window.location.search; - data = new FormData(form ?? undefined); - - if (element.type === 'submit' && element.name !== '') { - data.append(element.name, element.value || ''); - - } else if (element.type === 'image') { - const coords = element.getBoundingClientRect(); - const prefix = element.name !== '' ? `${element.name}.` : ''; - data.append(`${prefix}x`, Math.max(0, Math.floor(event !== undefined ? event.pageX - coords.left : 0))); - data.append(`${prefix}y`, Math.max(0, Math.floor(event !== undefined ? event.pageY - coords.top : 0))); - } - } - - return this.processInteraction(element, method, url, data, options, event); + public async clickElement(element: HTMLAnchorElement, options: Options = {}, event?: MouseEvent): Promise { + return this.processInteraction(element, 'GET', element.href, null, options, event); } - public async submitForm(form: HTMLFormElement, options: Options = {}, event?: Event): Promise { - const method = form.getAttribute('method')?.toUpperCase() ?? 'GET'; - const url = form.getAttribute('action') ?? window.location.pathname + window.location.search; - const data = new FormData(form); + public async submitForm(form: HTMLFormElement, options: Options = {}, event?: SubmitEvent): Promise { + const submitter = event?.submitter; + const method = (submitter?.getAttribute('formmethod') || form.getAttribute('method') || 'GET').toUpperCase(); + const url = submitter?.getAttribute('formaction') ?? form.getAttribute('action') ?? window.location.pathname + window.location.search; + const data = new FormData(form, submitter); return this.processInteraction(form, method, url, data, options, event); } diff --git a/tests/Naja.UIHandler.js b/tests/Naja.UIHandler.js index d01b461..15ca9a1 100644 --- a/tests/Naja.UIHandler.js +++ b/tests/Naja.UIHandler.js @@ -110,12 +110,8 @@ describe('UIHandler', function () { this.a.dispatchEvent(createEvent('click')); this.form.dispatchEvent(createEvent('submit')); - this.input.dispatchEvent(createEvent('click')); - this.image.dispatchEvent(createEvent('click')); - this.submitButton.dispatchEvent(createEvent('click')); - this.externalButton.dispatchEvent(createEvent('click')); - assert.equal(naja.uiHandler.handler.callCount, 6); + assert.equal(naja.uiHandler.handler.callCount, 2); }); it('binds to elements specified by custom selector', function () { @@ -488,8 +484,9 @@ describe('UIHandler', function () { const preventDefault = sinon.spy(); const evt = { - type: 'click', - currentTarget: this.input, + type: 'submit', + currentTarget: this.form2, + submitter: this.input, preventDefault, }; handler.handleUI(evt); @@ -511,8 +508,9 @@ describe('UIHandler', function () { const preventDefault = sinon.spy(); const evt = { - type: 'click', - currentTarget: this.image, + type: 'submit', + currentTarget: this.form3, + submitter: this.image, preventDefault, }; handler.handleUI(evt); @@ -534,8 +532,9 @@ describe('UIHandler', function () { const preventDefault = sinon.spy(); const evt = { - type: 'click', - currentTarget: this.submitButton, + type: 'submit', + currentTarget: this.form4, + submitter: this.submitButton, preventDefault, }; handler.handleUI(evt); @@ -557,8 +556,9 @@ describe('UIHandler', function () { const preventDefault = sinon.spy(); const evt = { - type: 'click', - currentTarget: this.externalButton, + type: 'submit', + currentTarget: this.form5, + submitter: this.externalButton, preventDefault, }; handler.handleUI(evt);