import './linq.js' import { Template } from './template.js'; export function formToObject(form) { const data = new FormData(form); const object = {}; data.forEach((value, key) => { setNestedValue(object, key, value); }); return object; } function setNestedValue(obj, path, value) { path.split('.').asEnumerable() .isLast() .forEach((key, isLast) => { if (isLast) { obj[key] = value; } else { if (!obj[key]) { obj[key] = {}; } obj = obj[key]; } }); } export async function sendFormAsync(form, url, method) { url = url || form.action; method = method || form.method || 'post'; const data = formToObject(form); const response = await sendJsonAsync(url, data, method); if (response.ok && response.redirected) { window.location.href = response.url; return null; } const responseText = await response.text(); if (response.ok == false && handleValidationError(response, responseText, form)) { return null; } if (response.ok == false) { handleGenericFormError(response, responseText, form); return null; } else { return responseText.length == 0 ? null : JSON.parse(responseText); } } export async function sendJsonAsync(url, data, method = 'post') { const response = await fetch(url, { method: method.toUpperCase(), headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return response; } export async function postAndRenderAsync(url, data, template, targetElement) { const response = await sendJsonAsync(url, data); if (response.ok) { const responseText = await response.text(); targetElement.innerHTML = template.render(responseText.length == 0 ? undefined : JSON.parse(responseText)); } } export async function postFormAndRenderAsync(url, form, template, targetElement) { const object = formToObject(form); const data = await postFormAsync(url, object, template, targetElement); if (data) { targetElement.innerHTML = template.render(data); } } const genericFormErrorTemplate = new Template(`
An error occurred while submitting the form. Please try again later. {{ $this }}
`); function handleGenericFormError(response, responseText, form) { if (!response.ok) { const html = genericFormErrorTemplate.render(responseText); form.insertAdjacentHTML('beforeend', html); } } const validationErrorTemplate = new Template(`
`); const unknownInputErrorTemplate = new Template(`

An error occurred with the following fields:

@for(field, errors of Object.entries($this)) { }
`); function toCamelCase(str) { str = str.replace(/([-_][a-z])/gi, (match) => { return match.toUpperCase() .replace('-', '') .replace('_', ''); }); str = str[0].toLowerCase() + str.substring(1); return str; } function handleValidationError(response, responseText, form) { if (response.status !== 400) return false; const responseObject = JSON.parse(responseText); const unknownInputErrors = {}; if (responseObject.type === 'https://tools.ietf.org/html/rfc9110#section-15.5.1' && responseObject.errors) { for (const [field, messages] of Object.entries(responseObject.errors)) { const input = form.querySelector(`[name="${toCamelCase(field)}"]`); if (input) { const parent = input.parentElement; const errorHtml = validationErrorTemplate.render(messages); let errorContainer = parent.querySelector('.form-error'); // Check if an error container already exists if (errorContainer) { errorContainer.outerHTML = errorHtml; // Replace existing error container } else { parent.insertAdjacentHTML('beforeend', errorHtml); } } else { unknownInputErrors[field] = messages; } } if (Object.keys(unknownInputErrors).length > 0) { const html = unknownInputErrorTemplate.render(unknownInputErrors); form.insertAdjacentHTML('beforeend', html); } return true; } return false; }