initial
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
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;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user