* It is now possible to pass an element as the dialog message to have it inserted as the body.
295 lines
6.4 KiB
JavaScript
295 lines
6.4 KiB
JavaScript
|
|
import { dom, el } from './dom.js';
|
|
import { jsonQuery, jsonPatch } from './json.js';
|
|
|
|
/**
|
|
* This form encapsulates a form with a model and a layout.
|
|
*
|
|
*/
|
|
class JsonForm {
|
|
|
|
#dom = null;
|
|
#model = null;
|
|
#layout = null;
|
|
#editable = false;
|
|
|
|
set model(model) {
|
|
this.#model = model;
|
|
this.#layout.modelUpdated(model);
|
|
}
|
|
get model() {
|
|
return this.#model;
|
|
}
|
|
|
|
set editable(state) {
|
|
this.#editable = !!state;
|
|
this.#layout.setEditable(this.#editable);
|
|
if (this.#editable === false) {
|
|
this.refreshModel();
|
|
}
|
|
}
|
|
get editable() {
|
|
return this.#editable;
|
|
}
|
|
|
|
get layout() {
|
|
return this.#layout;
|
|
}
|
|
|
|
constructor() {
|
|
this.#layout = new FormLayout();
|
|
}
|
|
|
|
dom() {
|
|
if (!this.#dom) {
|
|
this.#dom = el.div({ class: 'jsl-form' });
|
|
}
|
|
this.#layout.buildDom(this.#dom);
|
|
return this.#dom;
|
|
}
|
|
|
|
refreshModel() {
|
|
// TODO go over the fields and update the model with any modified data
|
|
this.#model = this.#layout.updateModel(this.#model);
|
|
}
|
|
|
|
}
|
|
|
|
class FormLayout {
|
|
|
|
#rows = [];
|
|
|
|
addRow() {
|
|
const row = new FormRow();
|
|
this.#rows.push(row);
|
|
return row;
|
|
}
|
|
|
|
buildDom(element) {
|
|
element.innerHTML = '';
|
|
this.#rows.forEach(row => {
|
|
const rowEl = row.dom();
|
|
|
|
element.appendChild(rowEl);
|
|
})
|
|
}
|
|
|
|
modelUpdated(model) {
|
|
this.#rows.forEach(row => row.modelUpdated(model));
|
|
}
|
|
|
|
updateModel(model) {
|
|
this.#rows.forEach(row => model = row.updateModel(model));
|
|
return model;
|
|
}
|
|
|
|
setEditable(state) {
|
|
this.#rows.forEach(row => row.setEditable(state));
|
|
}
|
|
|
|
}
|
|
|
|
class FormRow {
|
|
el = null;
|
|
#fields = [];
|
|
|
|
append(field) {
|
|
this.#fields.push(field);
|
|
return this;
|
|
}
|
|
|
|
dom() {
|
|
if (!this.el) {
|
|
this.el = el.div({ class: '-row' });
|
|
}
|
|
this.el.innerHTML = '';
|
|
this.#fields.forEach(field => {
|
|
this.el.appendChild(field.dom());
|
|
})
|
|
return this.el;
|
|
}
|
|
|
|
modelUpdated(model) {
|
|
this.#fields.forEach(field => {
|
|
if (!field.options.path) return;
|
|
field.value = jsonQuery(model, field.options.path);
|
|
});
|
|
}
|
|
|
|
updateModel(model) {
|
|
this.#fields.forEach(field => {
|
|
if (!field.options.path) return;
|
|
field.value = field.getValue();
|
|
model = jsonPatch(model, field.options.path, field.value);
|
|
});
|
|
return model;
|
|
}
|
|
|
|
setEditable(state) {
|
|
this.#fields.forEach(field => {
|
|
if (field.options.locked === true) return;
|
|
field.setEditable(state)
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
class FormField {
|
|
#el = null;
|
|
#label = null;
|
|
#options = {};
|
|
#value = null;
|
|
|
|
get value() {
|
|
return this.#value;
|
|
}
|
|
set value(val) {
|
|
this.#value = val;
|
|
this.valueUpdated();
|
|
}
|
|
|
|
get el() {
|
|
return this.#el;
|
|
}
|
|
|
|
get options() {
|
|
return this.#options;
|
|
}
|
|
|
|
constructor(options = {}) {
|
|
this.#options = options || {};
|
|
}
|
|
|
|
dom() {
|
|
if (!this.#el) {
|
|
let style = null;
|
|
if (this.#options.width) {
|
|
style = 'width: ' + this.#options.width + '%;';
|
|
} else {
|
|
style = 'flex-grow: 1;';
|
|
}
|
|
this.#el = el.div({ class: '-form-field', style: style ?? null });
|
|
|
|
this.#label = el.label({ class: '-label' });
|
|
this.#label.innerHTML = this.options.label ?? "Unlabeled";
|
|
this.#el.appendChild(this.#label);
|
|
}
|
|
this.buildField();
|
|
return this.#el;
|
|
}
|
|
|
|
/**
|
|
* Update the value without triggering any effects
|
|
*
|
|
* @param {*} value The new value
|
|
*/
|
|
updateValue(value) {
|
|
this.#value = value;
|
|
}
|
|
|
|
buildField() {
|
|
}
|
|
|
|
valueUpdated() {
|
|
}
|
|
|
|
setEditable(state) {
|
|
}
|
|
}
|
|
|
|
class TextField extends FormField {
|
|
#input = null;
|
|
buildField() {
|
|
if (!this.#input) {
|
|
this.#input = el.div({ class: '-field -text-field' })
|
|
this.el.appendChild(this.#input);
|
|
}
|
|
this.#input.innerText = this.value;
|
|
}
|
|
valueUpdated() {
|
|
if (!this.#input) return;
|
|
this.#input.innerText = this.value;
|
|
}
|
|
getValue() {
|
|
if (this.#input.innerText === '') {
|
|
if (this.options.nullable !== false)
|
|
return null;
|
|
}
|
|
return this.#input.innerText;
|
|
}
|
|
setEditable(state) {
|
|
if (state) {
|
|
dom.apply(this.#input, { 'contenteditable':'plaintext-only' });
|
|
} else {
|
|
this.updateValue(this.#input.innerText);
|
|
dom.apply(this.#input, { 'contenteditable':null });
|
|
}
|
|
}
|
|
}
|
|
|
|
class NumericField extends FormField {
|
|
#input = null;
|
|
buildField() {
|
|
if (!this.#input) {
|
|
this.#input = el.div({ class: '-field -number-field' })
|
|
this.el.appendChild(this.#input);
|
|
}
|
|
this.#input.innerText = this.value;
|
|
}
|
|
valueUpdated() {
|
|
if (!this.#input) return;
|
|
this.#input.innerText = this.value;
|
|
}
|
|
getValue() {
|
|
if (this.#input.innerText === '') {
|
|
if (this.options.nullable !== false)
|
|
return null;
|
|
}
|
|
if (this.options.fixed && typeof this.options.fixed == 'number') {
|
|
return parseFloat(this.#input.innerText).toFixed(this.options.fixed);
|
|
}
|
|
if (this.options.float) {
|
|
return parseFloat(this.#input.innerText);
|
|
}
|
|
return parseInt(this.#input.innerText);
|
|
}
|
|
setEditable(state) {
|
|
if (state) {
|
|
dom.apply(this.#input, { 'contenteditable':'plaintext-only' });
|
|
} else {
|
|
this.updateValue(this.#input.innerText);
|
|
dom.apply(this.#input, { 'contenteditable':null });
|
|
}
|
|
}
|
|
}
|
|
|
|
class DateField extends FormField {
|
|
#input = null;
|
|
buildField() {
|
|
if (!this.#input) {
|
|
this.#input = el.div({ class: '-field -date-field' })
|
|
this.el.appendChild(this.#input);
|
|
}
|
|
this.#input.innerHTML = "I'm a date!";
|
|
}
|
|
}
|
|
|
|
class SelectField extends FormField {
|
|
|
|
}
|
|
|
|
class CheckField extends FormField {
|
|
|
|
}
|
|
|
|
export {
|
|
JsonForm,
|
|
FormLayout,
|
|
FormRow,
|
|
FormField,
|
|
TextField,
|
|
NumericField,
|
|
DateField,
|
|
SelectField,
|
|
CheckField,
|
|
} |