Files

230 lines
7.8 KiB
JavaScript
Raw Permalink Normal View History

2026-06-12 11:25:57 +02:00
export const RsvInlineFormBuilder = {
2026-06-11 19:03:29 +02:00
match_p(name, value) {
return (form) => String(form[name]) === String(value);
},
create(datasource) {
const fields = [];
const builder = {
datasource: datasource,
fieldset(legend, width = null) {
fields.push({ type: 'fieldset', legend, width });
return this;
},
input_text(name, label, value = '') {
fields.push({ type: 'text', name, label, value });
return this;
},
input_number(name, label, value = '') {
fields.push({ type: 'number', name, label, value });
return this;
},
input_date(name, label, value = '') {
fields.push({ type: 'date', name, label, value });
return this;
},
input_time(name, label, value = '') {
fields.push({ type: 'time', name, label, value });
return this;
},
input_textarea(name, label, value = '') {
fields.push({ type: 'textarea', name, label, value });
return this;
},
input_checkbox(name, label, checked = false) {
fields.push({ type: 'checkbox', name, label, checked });
return this;
},
input_hidden(name, value) {
fields.push({ type: 'hidden', name, value });
return this;
},
input_select(name, label, options, value = '') {
fields.push({ type: 'select', name, label, options, value });
return this;
},
show_if(predicate) {
const last = fields[fields.length - 1];
if (last) last.show_if = predicate;
return this;
},
build({ id, colspan = 1, save_label = 'Update', on_success, on_cancel } = {}) {
const td = document.createElement('td');
td.setAttribute('colspan', colspan);
const form = document.createElement('form');
const wrapper = document.createElement('div');
wrapper.classList.add('inline-edit-wrapper');
const hidden_inputs = [];
let current_fieldset = null;
let current_col = null;
const fieldsets = [];
const conditionals = [];
function ensure_fieldset() {
if (current_fieldset === null) {
current_fieldset = document.createElement('fieldset');
current_col = document.createElement('div');
current_col.classList.add('inline-edit-col');
fieldsets.push(current_fieldset);
}
}
for (const field of fields) {
if (field.type === 'hidden') {
hidden_inputs.push(field);
continue;
}
if (field.type === 'fieldset') {
if (current_fieldset !== null) {
current_fieldset.appendChild(current_col);
}
current_fieldset = document.createElement('fieldset');
if (field.width) current_fieldset.style.width = field.width;
const legend_el = document.createElement('legend');
legend_el.classList.add('inline-edit-legend');
legend_el.innerText = field.legend;
current_fieldset.appendChild(legend_el);
current_col = document.createElement('div');
current_col.classList.add('inline-edit-col');
fieldsets.push(current_fieldset);
continue;
}
ensure_fieldset();
const label_el = document.createElement('label');
const title = document.createElement('span');
title.classList.add('title');
title.innerText = field.label;
const wrap = document.createElement('span');
wrap.classList.add('input-text-wrap');
let input;
if (field.type === 'select') {
input = document.createElement('select');
input.name = field.name;
for (const opt of field.options) {
const option = document.createElement('option');
if (typeof opt === 'object' && opt !== null) {
option.value = opt.value;
option.textContent = opt.label;
option.selected = String(opt.value) === String(field.value);
} else {
option.value = opt;
option.textContent = opt;
option.selected = opt === field.value;
}
input.appendChild(option);
}
} else if (field.type === 'textarea') {
input = document.createElement('textarea');
input.name = field.name;
input.rows = 5;
input.style.width = '100%';
input.value = field.value ?? '';
} else {
input = document.createElement('input');
input.type = field.type;
input.name = field.name;
if (field.type === 'checkbox') {
input.checked = field.checked;
} else {
input.value = field.value ?? '';
}
}
wrap.appendChild(input);
label_el.replaceChildren(title, wrap);
current_col.appendChild(label_el);
if (field.show_if) conditionals.push({ label_el, predicate: field.show_if });
}
if (current_fieldset !== null) {
current_fieldset.appendChild(current_col);
}
const save_row = document.createElement('div');
save_row.classList.add('inline-edit-save', 'submit');
const error = document.createElement('div');
error.classList.add('notice', 'notice-error', 'notice-alt', 'inline', 'hidden');
const error_p = document.createElement('p');
error_p.classList.add('error');
error.appendChild(error_p);
const spinner = document.createElement('span');
spinner.classList.add('spinner');
const save_btn = document.createElement('button');
save_btn.type = 'button';
save_btn.classList.add('save', 'button', 'button-primary');
save_btn.innerText = save_label;
save_btn.onclick = () => {
const form_data = Object.fromEntries(new FormData(form));
for (const field of fields) {
if (field.type === 'checkbox') {
form_data[field.name] = field.name in form_data;
}
}
spinner.classList.add('is-active');
error.classList.add('hidden');
builder.datasource.put(id, form_data)
.then(() => { if (on_success) on_success(); })
.catch(err => {
error_p.innerText = err.message;
error.classList.remove('hidden');
})
.finally(() => spinner.classList.remove('is-active'));
};
const cancel_btn = document.createElement('button');
cancel_btn.type = 'button';
cancel_btn.classList.add('cancel', 'button');
cancel_btn.innerText = 'Cancel';
if (on_cancel) cancel_btn.onclick = on_cancel;
save_row.replaceChildren(save_btn, cancel_btn, spinner, error);
for (const h of hidden_inputs) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = h.name;
input.value = h.value ?? '';
form.appendChild(input);
}
wrapper.replaceChildren(...fieldsets, save_row);
form.appendChild(wrapper);
td.appendChild(form);
if (conditionals.length) {
const snapshot = () => {
const f = Object.fromEntries(new FormData(form));
for (const field of fields) if (field.type === 'checkbox') f[field.name] = field.name in f;
return f;
};
const sync_all = () => {
const f = snapshot();
for (const c of conditionals) c.label_el.classList.toggle('hidden', !c.predicate(f));
};
form.addEventListener('change', sync_all);
form.addEventListener('input', sync_all);
sync_all();
}
return td;
},
};
return builder;
},
};