nomilo/assets/scripts/configure-services.js
2026-06-24 11:45:51 +02:00

153 lines
5 KiB
JavaScript

function serviceName(formData) {
const serviceType = formData.get('service_type');
if (serviceType == 'other') {
return formData.get('service_name') + '/' + formData.get('service_protocol');
} else {
return document.querySelector(`option[value="${serviceType}"]`).innerText;
}
}
async function insertValidationErrors(form, errorList) {
const errors = new Map()
for (const error of errorList) {
const errorsForKey = errors.get(error.path) ?? [];
errorsForKey.push(error)
errors.set(error.path, errorsForKey)
}
for (const [key, errorsForKey] of errors) {
// TODO: support for multiple error keys in dataset
const input = form.querySelector(`[data-error-keys="${key}"]`);
// TODO: support for unmanaged errors
const otherErrorIds = input.getAttribute('aria-describedby').trim().split(' ');
for (const errorId of otherErrorIds) {
if (errorId.length) {
document.getElementById(errorId)?.remove();
}
}
const errorIds = [];
for (const errorIndex in errorsForKey) {
const error = errorsForKey[errorIndex];
const errorContainer = document.createElement('p');
errorContainer.classList.add('error');
errorContainer.id = `${input.id}-error-${errorIndex}`;
errorIds.push(errorContainer.id)
errorContainer.innerText = await l10n(input.dataset.errorMessageId + '.error-' + error.code.replaceAll(':', '-'));
input.insertAdjacentElement('afterend', errorContainer);
}
input.setAttribute('aria-describedby', errorIds.join(' '));
input.setAttribute('aria-invalid', 'true');
}
form.querySelector('[aria-invalid]').focus();
}
function resetErrors(form) {
for (const errorEl of form.querySelectorAll('p.error')) {
errorEl.remove();
}
for (const input of form.querySelectorAll('[aria-invalid]')) {
input.removeAttribute("aria-invalid");
}
}
async function isFormValid(form) {
const formData = new FormData(form);
const errors = await fetch(`${window.location}&validate_only=true`, {
method: 'POST',
body: new URLSearchParams(formData.entries()),
})
.then(response => response.json())
.then(errors => errors.filter(error => error.path !== '/services/0/data/service_targets'));
if (errors.length > 0) {
insertValidationErrors(form, errors);
return false
}
resetErrors(form)
return true
}
async function addServiceCard(e) {
e.preventDefault();
const form = e.submitter.form;
const idTemplate = form.dataset.newCard;
const formData = new FormData(form);
if (!(await isFormValid(form))) {
return
}
const template = document.querySelector(`template[data-new-card-template="${idTemplate}"]`);
const newCard = document.importNode(template.content, true).firstElementChild ;
let html = newCard.innerHTML;
const allCards = document.querySelectorAll('[data-card]');
const formVars = new Map([
['service_type', formData.get('services[0][data][service_type][service_type]')],
['service_protocol', formData.get('services[0][data][service_type][protocol]')],
['service_name', formData.get('services[0][data][service_type][name]')],
]);
console.log(formVars);
const vars = [
['service_index', allCards.length],
['service_fullname', serviceName(formVars)],
...formVars.entries(),
]
for (const [key, value] of vars ) {
html = html.replaceAll(`{${key}}`, value);
}
newCard.innerHTML = html;
setupForm(newCard);
setupDeleteServiceCardButton(form, newCard);
template.insertAdjacentElement('beforebegin', newCard);
focusInput(newCard);
form.reset();
}
function deleteServiceCardButton(addCardForm, button) {
let card = button.parentElement;
while (!('card' in card.dataset)) {
card = card.parentElement;
}
const allCards = document.querySelectorAll('[data-card]');
if (allCards.length === 1) {
focusInput(addCardForm);
} else if (card.nextElementSibling && 'card' in card.nextElementSibling.dataset) {
card.nextElementSibling.querySelector('[data-delete-card]').focus();
} else {
card.previousElementSibling.querySelector('[data-delete-card]').focus();
}
card.remove();
}
function setupDeleteServiceCardButton(addCardForm, card) {
const button = card.querySelector('button[data-delete-card]');
button.addEventListener('click', () => deleteServiceCardButton(addCardForm, button));
}
function setupServiceForm() {
const addCardForm = document.querySelector('form[data-new-card="service"]');
if (addCardForm) {
addCardForm.addEventListener('submit', addServiceCard);
const cards = document.querySelectorAll('[data-card]');
for (const card of cards) {
setupDeleteServiceCardButton(addCardForm, card);
}
}
}
document.addEventListener('DOMContentLoaded', setupServiceForm);