unclean SPARC

This commit is contained in:
STEINNI
2025-08-27 07:03:09 +00:00
commit f308460931
430 changed files with 54426 additions and 0 deletions
+173
View File
@@ -0,0 +1,173 @@
/**
* @category Core
* @subcategory Application
*/
class Controller {
static _template = null;
static _contents = [];
static _currentContent = null;
/**
*
*/
constructor() { }
/**
*
*/
destructor() {}
/**
* @async
* @param {*} name
* @param {*} args
* @param {*} data
* @returns {View}
*/
loadView(name, args, data) {
args = args || {};
args.name = name+'.html';
args.className = name.split('/').pop();
args.onContentLoaded = args.onContentLoaded || this.appendHTML;
args.path = app.config.router.viewsPath;
let request = app.Assets.loadHtml(args);
return request.then(args.onContentLoaded.bind(this, args, data));
}
/**
*
* @param {*} args
* @param {*} data
* @param {*} html
* @returns {View}
*/
appendHTML(args,data, html) {
if(!args.target) {
args.target = document.body;
} else {
if(typeof args.target == 'string') {
let selector = null;
let content = document.body;
let crumbs = args.target.split('@')
if(crumbs.length > 1) {
let targetId = crumbs.pop()
content = Controller._contents.find(o => o.view._sparcId == targetId).view.el;
}
selector = crumbs.join('@');
args.target = content.querySelector(selector);
}
}
let container = document.createElement('div');
container.innerHTML = Controller.processTemplate(args.name, html, data);
let parent = args.target;
parent.innerHTML = '';
while(container.children.length > 0) {
parent.appendChild(container.children[0]);
}
let view = new app.LoadedClasses[args.className]();
view._className = args.name.replace('.html', '');
Controller._currentContent = view;
Controller._contents.push({view: view, dom: parent, type: 'nested'});
view.el = parent;
view.el.setAttribute('sparc-id', view._sparcId);
view._controller = this;
//view.DOMContentLoaded(data);
return view;
}
/**
*
* @param {*} content
*/
unloadView(content) {
app.events.clear(content.view._sparcId);
Controller._contents = Controller._contents.filter(o => o.view._sparcId != content.view._sparcId);
content.view.DOMContentRemoved();
content.view.el.remove();
}
/**
*
* @param {*} name
* @param {*} args
*/
loadModel(name, args) { }
/**
*
* @param {*} id
* @returns {View}
*/
static getContentById(id) { return Controller._contents.find(o => o.view._sparcId == id); }
/**
*
* @param {*} name
* @param {*} html
* @param {*} templateData
* @returns {string}
*/
static processTemplate(name, html, templateData) {
// Define a function inside of which templateData keys will become local variables
// AND where templateData values will become thos variable values ;-)
// Drawback is that complex epression
function evalaluateTemplate(tpl, params) {
var interpretor = Function(...Object.keys(params), 'return('+tpl+');' );
return interpretor(...Object.values(params));
}
// Define a function inside of which a string of a variable name becomes that variable
// and where we check tht the corresponding (evaluable) expression in templateData is defined
function checkExpression(varname, params) {
var code = ' try{var t=typeof('+varname+');} catch(e){ t="badexpr"; } return(t)';
var interpretor = Function(...Object.keys(params), code );
return interpretor(...Object.values(params));
}
// Preflight check to avoid template crashes on undefined expressions
const rgxp = /\$\{(.*?)\}/gm;
let m = null;
templateData = templateData || {};
while ((m = rgxp.exec(html)) !== null) {
let safeType = checkExpression(m[1],templateData);
if(safeType == 'undefined') {
// variable undefined in templateData: add an empty one
console.warn(`In template ${name}, ${m[1]} is not defined !`);
templateData[m[1]]='';
} else if(safeType == 'badexpr') {
// unevaluable expression : remove it from template
console.warn(`In template ${name}, Bad expression: ${m[1]}`);
var escsearch = m[1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
var replrexp = new RegExp('\\$\\{'+escsearch+'\\}','gm')
html = html.replace(replrexp,'');
}
}
// Now that all remaining expressions are OK, evaluate the template via backtits !
return(evalaluateTemplate('`' + html + '`', templateData));
}
}
app.registerClass("Controller", Controller);
+83
View File
@@ -0,0 +1,83 @@
/**
* @category Core
* @subcategory Application
* @extends Controller
*/
class MasterController extends Controller {
content = null;
/**
*
*/
constructor() {
super();
if(app.MessageBus) this.onBusEnabled()
}
/**
* Called on init, if bus enabled (probably not yet connected)
*/
onBusEnabled() { app.MessageBus.whenConnected(this.onBusConnected.bind(this)) }
/** Called on bus connection
* Might be called again upon drop+reconnect
* Good place to put general subscriptions, to ensure
* you'll resubscribe after drop+reconnect
*/
onBusConnected(){ }
/**
*
* @param {*} name
* @returns {View}
*/
useTemplate(name) {
let className = name.split('/').pop(0);
let args = {};
if(name) {
if(!Controller._template || (Controller._template.name != className)) {
args.onContentLoaded = this.onTemplateLoaded;
return(this.loadView(name, args));
}
}
return(new Promise((resolve) => { resolve(null); }));
}
/**
*
* @param {*} args
* @param {*} data
* @param {*} html
*/
onTemplateLoaded(args,data, html) {
let container = document.createElement('div');
container.innerHTML = html;
this.el = document.body;
this.el.innerHTML = '';
while(container.children.length > 0) {
this.el.appendChild(container.children[0]);
}
let view = new app.LoadedClasses[args.className]();
Controller._template = {name: args.className, view: view, dom: this.el};
view.el = this.el;
view.DOMContentLoaded();
this.content = Controller._template.view;
this.ControllerReady();
}
/**
*
*/
ControllerReady() {}
}
app.registerClass('MasterController', MasterController);
+128
View File
@@ -0,0 +1,128 @@
/**
* Base model class
*
* @author Nicolas Stein
* @author Michael Fallise
* @version 1.2
* @category Core
* @subcategory Application
*/
class Model {
/**
*
* @param {object} response
* @returns {object}
*/
_processResponse(response, resolve, reject){
if(response.ok){ // All ok
if( (response.headers.get('Content-Type')) && (response.headers.get('Content-Type').toLocaleLowerCase().indexOf('application/json')>-1) ) {
resolve(response.json());
} else {
resolve(
response.text().then(rawResponse => {
if(rawResponse != '') {
console.warn('Server did not declare response as Json...trying to parse it anyway', rawResponse)
return(JSON.parse(rawResponse));
} else if(response.status==204) return(true); // Normal to have no content, so make sure other layers see it as success
else return('');
})
)
}
} else { // server error
// server error with json error details
if( (response.headers.get('Content-Type')) && (response.headers.get('Content-Type').toLocaleLowerCase().indexOf('application/json')>-1) ){
response.json().then(
cleanJson => { // json error details is clean
let error = {
'code': response.status,
'displayMessage': (cleanJson.error.displayMessage || ' !!No debugMessage from server!!'),
'debugMessage': response.statusText+'\n'+(cleanJson.error.debugMessage || ' !!No debugMessage from server!!'),
}
this.onRequestError(error);
reject(error);
},
text => {
let error = { // json error details is garbage
'code': response.status,
'displayMessage': 'Could not request to server!?',
'debugMessage': 'Additionally, bad Json returned :', text,
}
this.onRequestError(error);
reject(error);
}
)
} else { // server error with text return
response.text().then(
text => {
let error = {
'code': response.status,
'displayMessage': 'Could not request to server!?',
'debugMessage': `Server responded: ${text}`,
}
this.onRequestError(error);
reject(error);
},
)
}
}
}
/**
* Generic API service call
* @param {string} uri
* @param {string} method
* @param {object} payload
* @returns {object}
*/
request(uri, method, payload) {
// Fetch chooses its own resolve/reject (see https://developer.mozilla.org/en-US/docs/Web/API/fetch)
// Errors like 404 are not rejected. and we can't change the -already done- resolve/reject choice.
// therefore we return our own promise.
if((!method) || (!uri)) {
return(
new Promise((resolve, reject) => reject(
{ sucess :false,
payload: null,
error: { displayError: 'Server Error (no endpoint)',
debugError: 'Missing endpoint' },
})
))
}
return (
new Promise((resolve, reject) => {
let body = null;
if(method.toLowerCase()!='get') body = payload ? JSON.stringify(payload): null;
else if(payload) uri += '?'+Object.entries(payload).map(kv => kv.map(encodeURIComponent).join("=")).join("&");
fetch(uri, {
method: method,
headers: { 'Accept': 'application/json', 'Content-Type':'application/json'},
body: body,
credentials: 'include', //By nike: mandatory for MT to get session !!!
})
.then( response => {
this._processResponse(response, resolve, reject);
})
.catch(debugMessage => { // Other errors like metwork, protocol, CORs errors, or 200 but bad JSON
let error = {
'displayMessage': 'Could not request to server!?',
'debugMessage': debugMessage,
'code': '4XX' // non http but >299
}
this.onRequestError(error);
reject(error);
})
})
);
}
/**
* Request error handler
* @param {object} error
*/
onRequestError(error) { console.error('Ajax request error:', error); }
}
app.registerClass('Model', Model)
+53
View File
@@ -0,0 +1,53 @@
'use strict'
if(typeof(app)=='undefined') var app = {};
/**
__ __
( )( ) ___ ____ ____
)( )( / __)( ___)( _ \
)(__)( \__ \ )__) ) /
(______)(___/(____)(_)\_)
By Nike
This file is part of Widgets by Nike from Nicsys (info@nicsys.eu).
Widgets is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
Widgets is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
Get your copy of the GNU General Public License at <https://www.gnu.org/licenses/>.
* @category Core
* @subcategory Application
*/
class User {
authenticationDone = false;
isAuthenticated = false;
identity = {};
roles = [];
constructor(){ }
/**
*
* @param {*} callBack
*/
checkAuthenticated(callBack){
this.authenticationDone = true
this.isAuthenticated = true
this.userInfo = {
'identity':{},
'roles': [],
};
callBack();
}
/**
* do nothing
*/
gotoLogin() { }
/**
* do nothing
*/
getMessageBusUserInfo() { }
}
app.LoadedClasses.User = User
+112
View File
@@ -0,0 +1,112 @@
/**
* @category Core
* @subcategory Application
*/
class View {
_sparcId = null;
_className = null;
_controller = null;
title = "";
description = "";
/**
*
* @param {*} parms
*/
constructor(parms) {
parms = parms || {};
this._sparcId = crypto.randomUUID();
this.title = parms.title || '';
this.description = parms.description || '';
}
/**
* base method called when content is appended to the document.
*/
DOMContentLoaded() {}
/**
* base method called when content is removed from the document.
*/
DOMContentFocused() {}
/**
* base method called when content is removed from the document.
*/
DOMContentBlured() {}
/**
* base method called when content is removed from the document.
*/
DOMContentRemoved() {}
/**
* acts as a shortcut for vanilla querySelector Element method.
* @param {*} selector
* @returns {Element|null}
*/
find(selector) { return this.el ? this.el.querySelector(selector): null; }
/**
* acts as a shortcut for vanilla querySelectorAll Element method.
* @param {*} selector
* @returns {Array<Element>}
*/
findAll(selector) { return this.el ? this.el.querySelectorAll(selector): null; }
/**
* wrapper for vanilla addEventListener Element method.
* @param {*} type
* @param {*} callback
*/
addEvent(type, callback) { app.addEvent(type, callback, this._sparcId); }
/**
* AddEvent to all found selector.
* @param {*} selector
* @param {*} type
* @param {*} callback
*/
addEventTo(selector, type, callback) {
for(let el of this.findAll(selector)) {
// Cannot use the wrapper because not attached to el, but to window
// app.addEvent(type, callback, this._sparcId);
//TODO: see with Mike if addEvent could include target element .
el.addEventListener(type, callback);
}
}
/**
* Add an action to all found selector.
* @param {*} selector
* @param {*} type
* @todo what is this used for ? deprecate ?
*/
addActions(selector, type) {
this.addEventTo(selector+'[action]', type, e => {
let ax = this['action_'+e.target.getAttribute('action')];
if(typeof(ax)== 'function') ax.bind(this)(e);
else console.warn('Bad action: '+e.target.getAttribute('action'));
});
}
/**
* aggregates the content inner key to the targeted selector, avoiding targeting other content.
* @param {string} selector
* @returns {string}
*/
scope(selector) { return `${selector}@${this._sparcId}`; }
/**
* Wrapper for Controller.loadView
* @param {*} name
* @param {*} args
* @async
* @returns {View}
*/
loadView(name, args) { return this._controller.loadView(name, args); }
}
app.registerClass('View', View)