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, }