export const RsvInlineFormBuilder = { 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; }, };