Web view still in progress

This commit is contained in:
Claude Brisson
2023-11-05 13:51:01 +01:00
parent 5fdf3e8944
commit 5f068476dc
111 changed files with 8905 additions and 215 deletions

View File

@@ -23,6 +23,30 @@ let headers = function() {
return ret;
};
function clearFeedback() {
$('#error')[0].innerText = '';
$('#error, #success').addClass('hidden');
}
function success() {
$('#error')[0].innerText = '';
$('#error').addClass('hidden');
}
function error(response) {
const contentType = response.headers.get("content-type");
let promise =
(contentType && contentType.indexOf("application/json") !== -1)
? response.json().then(json => json.error || "unknown error")
: Promise.resolve(response.statusText);
promise.then(message => {
message = message.replaceAll(/([a-z])([A-Z])/g,"$1 $2").toLowerCase()
console.error(message);
$('#error')[0].innerText = message;
$('#error').removeClass('hidden');
});
}
let api = {
get: (path) => fetch(base + path, {
credentials: "same-origin",
@@ -49,22 +73,61 @@ let api = {
/* then, some helpers */
getJson: (path) => api.get(path)
.then(resp => {
if (resp.ok) return resp.json();
else throw resp.statusText;
}),
getJson: (path) => {
clearFeedback();
return api.get(path)
.then(resp => {
if (resp.ok) {
return resp.json();
}
else throw resp;
})
.catch(err => {
error(err);
return 'error'
});
},
postJson: (path, body) => api.post(path, body)
.then(resp => {
if (resp.ok) return resp.json();
else throw resp.statusText;
}),
postJson: (path, body) => {
clearFeedback();
spinner(true);
return api.post(path, body)
.then(resp => {
if (resp.ok) {
success();
return resp.json();
}
else {
throw resp;
}
})
.catch(err => {
error(err);
return 'error';
})
.finally(() => {
spinner(false);
});
},
putJson: (path, body) => api.put(path, body)
.then(resp => {
if (resp.ok) return resp.json();
else throw resp.statusText;
})
putJson: (path, body) => {
clearFeedback();
spinner(true);
return api.put(path, body)
.then(resp => {
if (resp.ok) {
success();
return resp.json();
}
else throw resp;
})
.catch(err => {
error(err);
return 'error';
})
.finally(() => {
spinner(false);
});
}
};

View File

@@ -78,27 +78,27 @@ Element.prototype.data = function (key) {
NodeList.prototype.data = function(key) {
this.item(0).data(key);
}
NodeList.prototype.show = function(key) {
this.item(0).show(key);
NodeList.prototype.show = function() {
this.item(0).show();
return this;
}
Element.prototype.show = function (key) {
Element.prototype.show = function() {
this.style.display = 'block';
}
NodeList.prototype.hide = function(key) {
this.item(0).hide(key);
NodeList.prototype.hide = function() {
this.item(0).hide();
return this;
}
Element.prototype.hide = function (key) {
Element.prototype.hide = function() {
this.style.display = 'none';
}
let initFunctions = [];
function onLoad(fct) {
if (typeof(fct) == "function") initFunctions.push(fct);
NodeList.prototype.text = function(txt) {
this.item(0).text(txt);
}
Element.prototype.text = function(txt) {
if (typeof(txt) === 'undefined') {
return this.textContent;
} else {
this.textContent = txt;
}
}
document.on("DOMContentLoaded", () => {
initFunctions.forEach(fct => {
fct();
});
});

View File

@@ -1,163 +0,0 @@
// 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));
}
}

View File

@@ -102,6 +102,35 @@ Element.prototype.modal = function(show) {
return this;
}
/* DOM helpers */
function formValue(name) {
let ctl = $(`[name="${name}"]`)[0];
let type = ctl.tagName;
if (
(type === 'INPUT' && ['text', 'number'].includes(ctl.attr('type'))) ||
type === 'SELECT'
) {
return ctl.value;
} else if (type === 'INPUT' && ctl.attr('type') === 'radio') {
ctl = $(`input[name="${name}"]:checked`)[0];
if (ctl) return ctl.value;
} else if (type === 'INPUT' && ctl.attr('type') === 'radio') {
return ctl.checked;
}
console.error(`unknown input name: ${name}`);
return null;
}
function msg(id) {
let ctl = $(`#${id}`)[0];
return ctl.textContent;
}
function spinner(show) {
if (show) $('#backdrop').addClass('active');
else $('#backdrop').removeClass('active');
}
onLoad(() => {
/*

View File

@@ -1,5 +0,0 @@
/*! store2 - v2.14.2 - 2022-07-18
* Copyright (c) 2022 Nathan Bubna; Licensed (MIT OR GPL-3.0) */
!function(a,b){var c={version:"2.14.2",areas:{},apis:{},nsdelim:".",inherit:function(a,b){for(var c in a)b.hasOwnProperty(c)||Object.defineProperty(b,c,Object.getOwnPropertyDescriptor(a,c));return b},stringify:function(a,b){return void 0===a||"function"==typeof a?a+"":JSON.stringify(a,b||c.replace)},parse:function(a,b){try{return JSON.parse(a,b||c.revive)}catch(b){return a}},fn:function(a,b){c.storeAPI[a]=b;for(var d in c.apis)c.apis[d][a]=b},get:function(a,b){return a.getItem(b)},set:function(a,b,c){a.setItem(b,c)},remove:function(a,b){a.removeItem(b)},key:function(a,b){return a.key(b)},length:function(a){return a.length},clear:function(a){a.clear()},Store:function(a,b,d){var e=c.inherit(c.storeAPI,function(a,b,c){return 0===arguments.length?e.getAll():"function"==typeof b?e.transact(a,b,c):void 0!==b?e.set(a,b,c):"string"==typeof a||"number"==typeof a?e.get(a):"function"==typeof a?e.each(a):a?e.setAll(a,b):e.clear()});e._id=a;try{b.setItem("__store2_test","ok"),e._area=b,b.removeItem("__store2_test")}catch(a){e._area=c.storage("fake")}return e._ns=d||"",c.areas[a]||(c.areas[a]=e._area),c.apis[e._ns+e._id]||(c.apis[e._ns+e._id]=e),e},storeAPI:{area:function(a,b){var d=this[a];return d&&d.area||(d=c.Store(a,b,this._ns),this[a]||(this[a]=d)),d},namespace:function(a,b,d){if(d=d||this._delim||c.nsdelim,!a)return this._ns?this._ns.substring(0,this._ns.length-d.length):"";var e=a,f=this[e];if(!(f&&f.namespace||(f=c.Store(this._id,this._area,this._ns+e+d),f._delim=d,this[e]||(this[e]=f),b)))for(var g in c.areas)f.area(g,c.areas[g]);return f},isFake:function(a){return a?(this._real=this._area,this._area=c.storage("fake")):!1===a&&(this._area=this._real||this._area),"fake"===this._area.name},toString:function(){return"store"+(this._ns?"."+this.namespace():"")+"["+this._id+"]"},has:function(a){return this._area.has?this._area.has(this._in(a)):!!(this._in(a)in this._area)},size:function(){return this.keys().length},each:function(a,b){for(var d=0,e=c.length(this._area);d<e;d++){var f=this._out(c.key(this._area,d));if(void 0!==f&&!1===a.call(this,f,this.get(f),b))break;e>c.length(this._area)&&(e--,d--)}return b||this},keys:function(a){return this.each(function(a,b,c){c.push(a)},a||[])},get:function(a,b){var d,e=c.get(this._area,this._in(a));return"function"==typeof b&&(d=b,b=null),null!==e?c.parse(e,d):null!=b?b:e},getAll:function(a){return this.each(function(a,b,c){c[a]=b},a||{})},transact:function(a,b,c){var d=this.get(a,c),e=b(d);return this.set(a,void 0===e?d:e),this},set:function(a,b,d){var e,f=this.get(a);return null!=f&&!1===d?b:("function"==typeof d&&(e=d,d=void 0),c.set(this._area,this._in(a),c.stringify(b,e),d)||f)},setAll:function(a,b){var c,d;for(var e in a)d=a[e],this.set(e,d,b)!==d&&(c=!0);return c},add:function(a,b,d){var e=this.get(a);if(e instanceof Array)b=e.concat(b);else if(null!==e){var f=typeof e;if(f===typeof b&&"object"===f){for(var g in b)e[g]=b[g];b=e}else b=e+b}return c.set(this._area,this._in(a),c.stringify(b,d)),b},remove:function(a,b){var d=this.get(a,b);return c.remove(this._area,this._in(a)),d},clear:function(){return this._ns?this.each(function(a){c.remove(this._area,this._in(a))},1):c.clear(this._area),this},clearAll:function(){var a=this._area;for(var b in c.areas)c.areas.hasOwnProperty(b)&&(this._area=c.areas[b],this.clear());return this._area=a,this},_in:function(a){return"string"!=typeof a&&(a=c.stringify(a)),this._ns?this._ns+a:a},_out:function(a){return this._ns?a&&0===a.indexOf(this._ns)?a.substring(this._ns.length):void 0:a}},storage:function(a){return c.inherit(c.storageAPI,{items:{},name:a})},storageAPI:{length:0,has:function(a){return this.items.hasOwnProperty(a)},key:function(a){var b=0;for(var c in this.items)if(this.has(c)&&a===b++)return c},setItem:function(a,b){this.has(a)||this.length++,this.items[a]=b},removeItem:function(a){this.has(a)&&(delete this.items[a],this.length--)},getItem:function(a){return this.has(a)?this.items[a]:null},clear:function(){for(var a in this.items)this.removeItem(a)}}},d=c.Store("local",function(){try{return localStorage}catch(a){}}());d.local=d,d._=c,d.area("session",function(){try{return sessionStorage}catch(a){}}()),d.area("page",c.storage("page")),"function"==typeof b&&void 0!==b.amd?b("store2",[],function(){return d}):"undefined"!=typeof module&&module.exports?module.exports=d:(a.store&&(c.conflict=a.store),a.store=d)}(this,this&&this.define);
//# sourceMappingURL=store2.min.js.map

View File

@@ -1,6 +0,0 @@
/*!
* tablesort v5.4.0 (2022-04-27)
* http://tristen.ca/tablesort/demo/
* Copyright (c) 2022 ; Licensed MIT
*/
!function(){function r(t,e){if(!(this instanceof r))return new r(t,e);if(!t||"TABLE"!==t.tagName)throw new Error("Element must be a table");this.init(t,e||{})}function m(t){var e;return window.CustomEvent&&"function"==typeof window.CustomEvent?e=new CustomEvent(t):(e=document.createEvent("CustomEvent")).initCustomEvent(t,!1,!1,void 0),e}function p(t,e){return t.getAttribute(e.sortAttribute||"data-sort")||t.textContent||t.innerText||""}function v(t,e){return(t=t.trim().toLowerCase())===(e=e.trim().toLowerCase())?0:t<e?1:-1}function A(t,e){return[].slice.call(t).find(function(t){return t.getAttribute("data-sort-column-key")===e})}function E(n,o){return function(t,e){var r=n(t.td,e.td);return 0===r?o?e.index-t.index:t.index-e.index:r}}var x=[];r.extend=function(t,e,r){if("function"!=typeof e||"function"!=typeof r)throw new Error("Pattern and sort must be a function");x.push({name:t,pattern:e,sort:r})},r.prototype={init:function(t,e){var r,n,o,i=this;if(i.table=t,i.thead=!1,i.options=e,t.rows&&0<t.rows.length)if(t.tHead&&0<t.tHead.rows.length){for(a=0;a<t.tHead.rows.length;a++)if("thead"===t.tHead.rows[a].getAttribute("data-sort-method")){r=t.tHead.rows[a];break}r=r||t.tHead.rows[t.tHead.rows.length-1],i.thead=!0}else r=t.rows[0];if(r){function s(){i.current&&i.current!==this&&i.current.removeAttribute("aria-sort"),i.current=this,i.sortTable(this)}for(var a=0;a<r.cells.length;a++)(o=r.cells[a]).setAttribute("role","columnheader"),"none"!==o.getAttribute("data-sort-method")&&(o.tabindex=0,o.addEventListener("click",s,!1),null!==o.getAttribute("data-sort-default")&&(n=o));n&&(i.current=n,i.sortTable(n))}},sortTable:function(t,e){var r=this,n=t.getAttribute("data-sort-column-key"),o=t.cellIndex,i=v,s="",a=[],d=r.thead?0:1,u=t.getAttribute("data-sort-method"),l=t.getAttribute("aria-sort");if(r.table.dispatchEvent(m("beforeSort")),e||(l="ascending"===l||"descending"!==l&&r.options.descending?"descending":"ascending",t.setAttribute("aria-sort",l)),!(r.table.rows.length<2)){if(!u){for(;a.length<3&&d<r.table.tBodies[0].rows.length;)0<(s=(s=(f=n?A(r.table.tBodies[0].rows[d].cells,n):r.table.tBodies[0].rows[d].cells[o])?p(f,r.options):"").trim()).length&&a.push(s),d++;if(!a)return}for(d=0;d<x.length;d++)if(s=x[d],u){if(s.name===u){i=s.sort;break}}else if(a.every(s.pattern)){i=s.sort;break}for(r.col=o,d=0;d<r.table.tBodies.length;d++){var c,f,h=[],b={},w=0,g=0;if(!(r.table.tBodies[d].rows.length<2)){for(c=0;c<r.table.tBodies[d].rows.length;c++)"none"===(s=r.table.tBodies[d].rows[c]).getAttribute("data-sort-method")?b[w]=s:(f=n?A(s.cells,n):s.cells[r.col],h.push({tr:s,td:f?p(f,r.options):"",index:w})),w++;for("descending"===l?h.sort(E(i,!0)):(h.sort(E(i,!1)),h.reverse()),c=0;c<w;c++)b[c]?(s=b[c],g++):s=h[c-g].tr,r.table.tBodies[d].appendChild(s)}}r.table.dispatchEvent(m("afterSort"))}},refresh:function(){void 0!==this.current&&this.sortTable(this.current,!0)}},"undefined"!=typeof module&&module.exports?module.exports=r:window.Tablesort=r}();