164 lines
6.4 KiB
JavaScript
164 lines
6.4 KiB
JavaScript
// hack to replace .34 into 0.34 (CB TODO - upstream patch to inputmask)
|
|
const fixNumber = (value) => isNaN(value) || !`${value}`.startsWith('.') ? value : `0.${value}`;
|
|
|
|
class FormProxy {
|
|
constructor(formSelector, config) {
|
|
this.formSelector = formSelector;
|
|
this.config = config;
|
|
this.config.properties = () => Object.keys(this.config).filter(k => typeof(this.config[k]) !== 'function');
|
|
this.promises = [];
|
|
this.dirty = false;
|
|
|
|
this.config.import = function(obj) {
|
|
for (let key in obj) {
|
|
if (key in this.config) {
|
|
this.proxy[key] = obj[key];
|
|
} else {
|
|
console.warn(`ignoring property ${key}`)
|
|
}
|
|
}
|
|
this.config.dirty(false);
|
|
}.bind(this);
|
|
|
|
this.config.export = function() {
|
|
let ret = {}
|
|
this.config.properties().forEach(prop => {
|
|
ret[prop] = this.proxy[prop];
|
|
});
|
|
return ret;
|
|
}.bind(this);
|
|
|
|
this.config.dirty = function(value) {
|
|
if (typeof(value) === 'undefined') return thisProxy.dirty;
|
|
else thisProxy.dirty = Boolean(value);
|
|
}.bind(this);
|
|
|
|
this.config.valid = function() {
|
|
return $(`${thisProxy.formSelector} [required]:invalid`).length === 0
|
|
}.bind(this);
|
|
|
|
this.config.reset = function() {
|
|
this.initialize();
|
|
}.bind(this);
|
|
|
|
// CB TODO - needs a function to wait for promises coming from dependencies
|
|
|
|
this.setState('loading');
|
|
$(() => this.configure.bind(this)());
|
|
|
|
let thisProxy = this;
|
|
return this.proxy = new Proxy(config, {
|
|
get(target, prop) {
|
|
if (typeof(target[prop]) === 'function') {
|
|
return target[prop];
|
|
}
|
|
else {
|
|
let elem = config[prop];
|
|
if (typeof(elem) === 'undefined') throw `invalid property: ${prop}`
|
|
return elem.getter();
|
|
}
|
|
},
|
|
set(target, prop, value) {
|
|
let def = config[prop];
|
|
if (typeof(def) === 'undefined') throw `invalid property: ${prop}`
|
|
let depends = [].concat(def.depends ? def.depends : []);
|
|
let proms = depends.flatMap(field => config[field].promise).filter(prom => prom);
|
|
let operation = () => {
|
|
def.setter(value);
|
|
if (typeof(def.change) === 'function') {
|
|
let rst = def.change(value, def.elem);
|
|
if (typeof(rst?.then) === 'function') {
|
|
def.promise = rst;
|
|
}
|
|
}
|
|
};
|
|
if (proms.length) Promise.all(proms).then(() => operation());
|
|
else operation();
|
|
config.dirty(true);
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
configure() {
|
|
this.form = $(this.formSelector);
|
|
if (!this.form.length) throw `Form not found: ${this.formSelector}`;
|
|
this.form.on('submit', e => { e.preventDefault(); return false; });
|
|
let controls = this.form.find('input[name],select[name],textarea[name]');
|
|
controls.on('input change keyup', e => {
|
|
this.setState('editing');
|
|
this.config.dirty(true);
|
|
});
|
|
controls.each((i,e) => {
|
|
let name = $(e).attr('name');
|
|
if (!(name in this.config)) this.config[name] = {};
|
|
});
|
|
this.config.properties().forEach(key => {
|
|
let def = this.config[key];
|
|
if (!def) def = this.config[key] = {};
|
|
else if (typeof(def) === 'function') return true; // continue foreach
|
|
if (!def.getter) {
|
|
let elem = def.elem;
|
|
if (!elem || !elem.length) elem = $(`${this.formSelector} [name="${key}"]`);
|
|
if (!elem || !elem.length) elem = $(`#${key}`);
|
|
if (!elem || !elem.length) throw `element not found: ${key}`;
|
|
def.elem = elem;
|
|
def.getter = elem.is('input,select,textarea')
|
|
? elem.attr('type') === 'radio'
|
|
? (() => elem.filter(':checked').val())
|
|
: (() => elem.data('default')
|
|
? elem.val()
|
|
? elem.is('.number')
|
|
? elem.val().replace(/ /g, '')
|
|
: elem.val()
|
|
: elem.data('default')
|
|
: elem.is('.number')
|
|
? elem.val() ? elem.val().replace(/ /g, '') : elem.val()
|
|
: elem.val())
|
|
: (() => elem.text());
|
|
def.setter = elem.is('input,select,textarea')
|
|
? elem.attr('type') === 'radio'
|
|
? (value => elem.filter(`[value="${value}"]`).prop('checked', true))
|
|
: elem.is('input.number') ? (value => elem.val(fixNumber(value))) : (value => elem.val(value))
|
|
: (value => elem.text(value));
|
|
if (typeof(def.change) === 'function') {
|
|
elem.on('change', () => def.change(def.getter(), elem));
|
|
}
|
|
}
|
|
let loading = def?.loading;
|
|
switch (typeof(loading)) {
|
|
case 'function':
|
|
let rst = loading(def.elem);
|
|
if (typeof(rst?.then) === 'function') {
|
|
this.promises.push(rst);
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
setTimeout(() => {
|
|
Promise.all(this.promises).then(() => { this.promises = []; this.initialize(); });
|
|
}, 10);
|
|
}
|
|
|
|
initialize() {
|
|
this.config.properties().forEach(key => {
|
|
let def = this.config[key];
|
|
if (typeof(def.initial) === 'undefined') {
|
|
this.proxy[key] = '';
|
|
} else {
|
|
if (typeof(def.initial) === 'function') {
|
|
def.initial(def.elem)
|
|
} else if (def.initial != null) {
|
|
this.proxy[key] = def.initial;
|
|
}
|
|
}
|
|
});
|
|
this.config.dirty(false);
|
|
this.setState('initial');
|
|
}
|
|
|
|
setState(state) {
|
|
if (this.form && this.form.length) this.form[0].dispatchEvent(new Event(state));
|
|
}
|
|
}
|