unclean SPARC
This commit is contained in:
Executable
+173
@@ -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);
|
||||
Executable
+83
@@ -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);
|
||||
Executable
+128
@@ -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)
|
||||
Executable
+53
@@ -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
|
||||
Executable
+112
@@ -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)
|
||||
Reference in New Issue
Block a user