Setting up view css and utilities
This commit is contained in:
163
view-webapp/src/main/webapp/js/formproxy.js
Normal file
163
view-webapp/src/main/webapp/js/formproxy.js
Normal file
@@ -0,0 +1,163 @@
|
||||
// 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));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user