diff --git a/README.md b/README.md index e45e8d7..2853987 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ Note that in the following example the CSRF element is marked with the `data-csr - `csrf` is the [CSRF][] token for the posted form. It's available in the request body as a `authenticity_token` form parameter. - You can also supply the CSRF token via a child element. See [usage](#Usage) example. - `required` is a boolean attribute that requires the validation to succeed before the surrounding form may be submitted. +- `http-method` defaults to `POST` where data is submitted as a POST with form data. You can set `GET` and the HTTP method used will be a get with url encoded params instead. ## Events diff --git a/custom-elements.json b/custom-elements.json index 3a59cb4..026d746 100644 --- a/custom-elements.json +++ b/custom-elements.json @@ -56,16 +56,8 @@ }, "members": [ { - "kind": "method", + "kind": "field", "name": "setValidity", - "parameters": [ - { - "name": "message", - "type": { - "text": "string" - } - } - ], "inheritedFrom": { "name": "AutoCheckValidationEvent", "module": "src/auto-check-element.ts" @@ -92,16 +84,8 @@ }, "members": [ { - "kind": "method", + "kind": "field", "name": "setValidity", - "parameters": [ - { - "name": "message", - "type": { - "text": "string" - } - } - ], "inheritedFrom": { "name": "AutoCheckValidationEvent", "module": "src/auto-check-element.ts" @@ -206,6 +190,14 @@ "type": { "text": "string" } + }, + { + "kind": "field", + "name": "httpMethod", + "type": { + "text": "string" + }, + "readonly": true } ], "attributes": [ diff --git a/src/auto-check-element.ts b/src/auto-check-element.ts index a61dbe6..ce94cc3 100644 --- a/src/auto-check-element.ts +++ b/src/auto-check-element.ts @@ -12,6 +12,11 @@ type State = { controller: Controller | null } +enum AllowedHttpMethods { + GET = 'GET', + POST = 'POST', +} + const states = new WeakMap() class AutoCheckEvent extends Event { @@ -176,6 +181,10 @@ export class AutoCheckElement extends HTMLElement { set csrfField(value: string) { this.setAttribute('csrf-field', value) } + + get httpMethod(): string { + return AllowedHttpMethods[this.getAttribute('http-method') as keyof typeof AllowedHttpMethods] || 'POST' + } } function setLoadingState(event: Event) { @@ -187,10 +196,11 @@ function setLoadingState(event: Event) { const src = autoCheckElement.src const csrf = autoCheckElement.csrf + const httpMethod = autoCheckElement.httpMethod const state = states.get(autoCheckElement) // If some attributes are missing we want to exit early and make sure that the element is valid. - if (!src || !csrf || !state) { + if (!src || (httpMethod === 'POST' && !csrf) || !state) { return } @@ -214,6 +224,9 @@ function makeAbortController() { } async function fetchWithNetworkEvents(el: Element, url: string, options: RequestInit): Promise { + if (options.method === 'GET') { + delete options.body + } try { const response = await fetch(url, options) el.dispatchEvent(new Event('load')) @@ -238,9 +251,10 @@ async function check(autoCheckElement: AutoCheckElement) { const src = autoCheckElement.src const csrf = autoCheckElement.csrf const state = states.get(autoCheckElement) + const httpMethod = autoCheckElement.httpMethod // If some attributes are missing we want to exit early and make sure that the element is valid. - if (!src || !csrf || !state) { + if (!src || (httpMethod === 'POST' && !csrf) || !state) { if (autoCheckElement.required) { input.setCustomValidity('') } @@ -255,8 +269,13 @@ async function check(autoCheckElement: AutoCheckElement) { } const body = new FormData() - body.append(csrfField, csrf) - body.append('value', input.value) + const url = new URL(src, window.location.origin) + if (httpMethod === 'POST') { + body.append(csrfField, csrf) + body.append('value', input.value) + } else { + url.search = new URLSearchParams({value: input.value}).toString() + } input.dispatchEvent(new AutoCheckSendEvent(body)) @@ -269,10 +288,10 @@ async function check(autoCheckElement: AutoCheckElement) { state.controller = makeAbortController() try { - const response = await fetchWithNetworkEvents(autoCheckElement, src, { + const response = await fetchWithNetworkEvents(autoCheckElement, url.toString(), { credentials: 'same-origin', signal: state.controller.signal, - method: 'POST', + method: httpMethod, body, }) if (response.ok) { diff --git a/test/auto-check.js b/test/auto-check.js index 67c12be..611a674 100644 --- a/test/auto-check.js +++ b/test/auto-check.js @@ -114,6 +114,35 @@ describe('auto-check element', function () { }) }) + describe('using HTTP GET', function () { + let checker + let input + + beforeEach(function () { + const container = document.createElement('div') + container.innerHTML = ` + + + ` + document.body.append(container) + + checker = document.querySelector('auto-check') + input = checker.querySelector('input') + }) + + afterEach(function () { + document.body.innerHTML = '' + checker = null + input = null + }) + + it('validates input with successful server response with GET', async function () { + triggerInput(input, 'hub') + await once(input, 'auto-check-complete') + assert.isTrue(input.checkValidity()) + }) + }) + describe('network lifecycle events', function () { let checker let input diff --git a/web-test-runner.config.js b/web-test-runner.config.js index fa0fabd..a68f5cd 100644 --- a/web-test-runner.config.js +++ b/web-test-runner.config.js @@ -19,7 +19,7 @@ export default { middleware: [ async ({request, response}, next) => { const {method, path} = request - if (method === 'POST') { + if (method === 'POST' || method === 'GET') { if (path.startsWith('/fail')) { response.status = 422 // eslint-disable-next-line i18n-text/no-en