unclean SPARC
This commit is contained in:
Executable
+557
@@ -0,0 +1,557 @@
|
||||
/**
|
||||
____ _____ __ __ ____ ____ ____
|
||||
( _ \ ( _ )( )( )(_ _)( ___)( _ \
|
||||
) _/ )(_)( )(__)( )( )__) ) /
|
||||
(_)\_) (_____)(______) (__) (____)(_)\_) for SPARC
|
||||
By Mike & Nike
|
||||
* This file is part of Sparc by Mike & Nike.
|
||||
* Sparc 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.
|
||||
* Sparc 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/>.
|
||||
* This class contains the SPA router mechanics.<br>
|
||||
* Once instanciated with a set of top-level routes, just launch instance.route().<br>
|
||||
*
|
||||
* @category Core
|
||||
* @subcategory Libraries
|
||||
*/
|
||||
|
||||
class Router {
|
||||
|
||||
/**
|
||||
* @typedef RouteNode
|
||||
* @property {string} url
|
||||
* @property {string} role
|
||||
* @property {string} controller
|
||||
* @property {string} [method]
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} args - object defining top-level routes
|
||||
* @param {object} args.routes - object defining top-level routes
|
||||
* @param {string|function} args.role - user role, used for selecting most apropriate route (if function: called at each re-routing)
|
||||
* @param {function} args.callback - Called when everything a route has been executed
|
||||
* @param {string} args.origin - [Experimental] - when router is not master of the domain root : process only URLs that begin with it.
|
||||
* @param {string} args.controllersPath - Location of application's scripts
|
||||
* @param {object} args.defaults - default values
|
||||
*/
|
||||
constructor(args) {
|
||||
this.defaults = args.defaults;
|
||||
|
||||
if(args.hasOwnProperty('controllersPath') && (args.controllersPath!='')) this.defaults.controllersPath = args.controllersPath;
|
||||
if(args.hasOwnProperty('modelsPath') && (args.modelsPath!='')) this.defaults.modelsPath = args.modelsPath;
|
||||
if(args.hasOwnProperty('viewsPath') && (args.viewsPath!='')) this.defaults.viewsPath = args.viewsPath;
|
||||
this.defaults.origin = args.hasOwnProperty('origin') && (args.origin!='') ? args.origin : (new URL(document.location)).origin;
|
||||
|
||||
this.ControllerInstances = {};
|
||||
this.ControllerConfigs = {};
|
||||
this.httpErrorRoute = null;
|
||||
this.routes = this.cleanupRoutes(args.routes);
|
||||
this.roles = args.roles;
|
||||
this.callback = args.callback;
|
||||
this.routeReady = false;
|
||||
this.merged_ctrl_routes = [];
|
||||
this.currentRoute = null;
|
||||
|
||||
document.addEventListener('click', this.onGlobalClick.bind(this));
|
||||
window.addEventListener('popstate', this.bckFwdInterceptor.bind(this));
|
||||
}
|
||||
|
||||
onGlobalClick(event) {
|
||||
let target = event.target;
|
||||
let notfound = true;
|
||||
while(target && target.tagName && (target.tagName.toUpperCase() != 'BODY') && notfound){
|
||||
if(target.tagName.toUpperCase() == 'A'){
|
||||
notfound = false;
|
||||
this.linksInterceptor(target, event);
|
||||
} else target = target.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
bckFwdInterceptor(e) { this.route(); }
|
||||
|
||||
linksInterceptor(target, e){
|
||||
if(target.hasAttribute('noroute')) return
|
||||
if(target.href.substr(0,this.defaults.origin.length) == this.defaults.origin) { //internal
|
||||
e.preventDefault()
|
||||
history.pushState({}, '', document.location)
|
||||
history.replaceState(null, '', this.cleanupUrl(target.href).pathname)
|
||||
this.route()
|
||||
}
|
||||
}
|
||||
|
||||
cleanupUrl(target) {
|
||||
let url = new URL(target || document.location, this.defaults.origin);
|
||||
url.pathname = url.pathname.replace(/\/+/g,'/');
|
||||
return(url);
|
||||
}
|
||||
|
||||
route(forcedUrl, forcedParams) {
|
||||
if(forcedUrl) history.replaceState(null, null, forcedUrl);
|
||||
|
||||
let routeinfo = this.getRoute(forcedUrl, forcedParams || {});
|
||||
|
||||
let [currentRoute, ctrlPath, ctrlClass, ctrlMethod, params, exturl] = routeinfo
|
||||
if(exturl && exturl.match(/\w{2,6}:\/\//)) {
|
||||
if(params) exturl += '?' +(new URLSearchParams(params).toString());
|
||||
document.location = exturl;
|
||||
this.routeReady = true;
|
||||
return;
|
||||
} else if(exturl && (exturl != '')) { // Internal redirect
|
||||
history.replaceState(null, '', exturl);
|
||||
this.route();
|
||||
} else {
|
||||
this.execRoute(...routeinfo);
|
||||
}
|
||||
}
|
||||
|
||||
execRoute(currentRoute, ctrlPath, ctrlClass, ctrlMethod, params, exturl){
|
||||
this.routeReady = false;
|
||||
|
||||
// Start by making sure we have the MasterController (aka "mc") loaded, if not, first, load it!
|
||||
let mcPath = app.config.router.controllersPath;
|
||||
let mcLoader;
|
||||
|
||||
if(!(app.config.router.masterController in app.LoadedClasses)){
|
||||
mcLoader = app.LoadedClasses.Assets.loadJson({
|
||||
'path': mcPath,
|
||||
'name': app.config.router.masterController+'.json',
|
||||
}).then((tcConfig) => {
|
||||
if(typeof(tcConfig)!='undefined') {
|
||||
tcConfig.routes=[]; // no subroutes for the masterController (use baseRoutes instead)
|
||||
// Avoid controllerConfigLoaded (made for normal ctrl, but merge config directly)
|
||||
this.mergeControllerConfig(currentRoute, mcPath, app.config.router.masterController, null, {} , tcConfig);
|
||||
// return the autoLoadController Promise, so the "then" below depends on ctrl loaded (not config loaded)
|
||||
return(this.autoLoadController(currentRoute, mcPath, app.config.router.masterController, null, {}));
|
||||
} else {
|
||||
console.error('Problem loading Master-Controller ! (could not load its config)');
|
||||
return(new Promise((resolve,fail) => { fail(); }));
|
||||
}
|
||||
});
|
||||
} else { mcLoader = new Promise((resolve) => { resolve(); }) }
|
||||
|
||||
mcLoader.then( () => {
|
||||
// Get controller config file (add controllersPath only to json, for the script it is done by getRoute)
|
||||
app.LoadedClasses.Assets.loadJson({
|
||||
'path': ctrlPath,
|
||||
'name': ctrlClass+'.json',
|
||||
})
|
||||
.then( (config) => {
|
||||
if(typeof(config)!='undefined') {
|
||||
this.controllerConfigLoaded(currentRoute, ctrlPath, ctrlClass, ctrlMethod, params, exturl, config);
|
||||
} else {
|
||||
let err = 'Could not load configuration of controller '+ctrlClass+' !!';
|
||||
this.redirectToHttpErrorRoute(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
controllerConfigLoaded(currentRoute, ctrlPath, ctrlClass, ctrlMethod, params, exturl, config) {
|
||||
config = config || { // Cool attitude if config not found or not clean json
|
||||
routes:[],
|
||||
dependencies:[],
|
||||
AssetsDependencies:{}
|
||||
};
|
||||
|
||||
this.mergeControllerConfig(currentRoute, ctrlPath, ctrlClass, ctrlMethod, params, config);
|
||||
|
||||
// Recompute route now that there are some sub-routes, if something changed we need to recurse
|
||||
// If nothing changed, then we're good to load, instanciate & call !
|
||||
let newCurrentRoute, newCtrlPath, newCtrlClass, newCtrlMethod, newParams;
|
||||
[newCurrentRoute, newCtrlPath, newCtrlClass, newCtrlMethod, newParams, exturl] = this.getRoute(currentRoute.forcedUrl, currentRoute.forcedParams);
|
||||
|
||||
if(exturl) { //Do not recur if we stumbbled on a exturl, just go to it
|
||||
this.route(exturl, null)
|
||||
} else if(newCurrentRoute.url!=currentRoute.url){
|
||||
this.execRoute(newCurrentRoute, newCtrlPath, newCtrlClass, newCtrlMethod, newParams, exturl);
|
||||
} else { // the else makes sure we autoLoad only at the bottom-level of the recursion
|
||||
if(typeof(this.ControllerInstances[ctrlClass])=='object') {
|
||||
// Non-persistent controllers were once were here, but not usefull, removed
|
||||
this.launchControllerMethod(currentRoute, ctrlPath, ctrlClass, ctrlMethod, params);
|
||||
} else { // Unknown ctrl : load it !
|
||||
this.autoLoadController(newCurrentRoute, newCtrlPath, newCtrlClass, newCtrlMethod, newParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mergeControllerConfig(currentRoute, ctrlPath, ctrlClass, ctrlMethod, params, config){
|
||||
this.ControllerConfigs[ctrlClass] = config
|
||||
|
||||
// Carefull not to remerge twice the same config (otherwise url concats will go bzurg)
|
||||
if(this.merged_ctrl_routes.indexOf(ctrlClass)>-1) return;
|
||||
this.merged_ctrl_routes.push(ctrlClass);
|
||||
|
||||
// Add sub routes, except for masterctrl
|
||||
if((ctrlClass!= app.config.router.masterController) || ('routes' in config)){
|
||||
let newroutes = this.cleanupRoutes(config.routes);
|
||||
for(let newroute of newroutes){
|
||||
if(currentRoute.url=='!defaultroute') continue;
|
||||
let fullurl = (currentRoute.url+'/'+newroute.url).replace(/\/+/g,'/');
|
||||
newroute.url = fullurl;
|
||||
this.routes.push(newroute);
|
||||
}
|
||||
}
|
||||
|
||||
if(!Loader.Dependencies.hasOwnProperty(ctrlClass)) Loader.Dependencies[ctrlClass] = [];
|
||||
// Add controller dependencies (not views, not)
|
||||
if('controllerDependencies' in config){
|
||||
for(var script of config.controllerDependencies){
|
||||
if(script.substring(0,4)!='http') Loader.Dependencies[ctrlClass].push(('/app/'+script).replace(/\/+/g,'/'))
|
||||
else Loader.Dependencies[ctrlClass].push(script)
|
||||
}
|
||||
}
|
||||
|
||||
// add models as controller dependencies with adapted path
|
||||
if('models' in config){
|
||||
for(var dep of config.models){
|
||||
if(typeof(dep) == 'string') {// just add model as dependency of controller, with correct path
|
||||
let model = (this.defaults.modelsPath+'/'+dep).replace(/\/+/g,'/');
|
||||
Loader.Dependencies[ctrlClass].push(model);
|
||||
} else if(typeof(dep) == 'object'){ // This model depends on other models
|
||||
let model = (this.defaults.modelsPath+'/'+dep.model).replace(/\/+/g,'/');
|
||||
// add model as dependency of controller
|
||||
Loader.Dependencies[ctrlClass].push(model);
|
||||
// add sub-dependencies without dups & with correct path
|
||||
if(!Loader.Dependencies.hasOwnProperty(model)) Loader.Dependencies[model] = [];
|
||||
for(let submodel of dep.dependencies){
|
||||
if(Loader.Dependencies[model].indexOf(submodel)<0) {
|
||||
Loader.Dependencies[model].push((this.defaults.modelsPath+'/'+submodel).replace(/\/+/g,'/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add views as controller dependencies with adapted path
|
||||
if('views' in config){
|
||||
for(var dep of config.views){
|
||||
if(typeof(dep) == 'string') {// just add view as dependency of controller, with correct path
|
||||
let view = (this.defaults.viewsPath+'/'+dep).replace(/\/+/g,'/');
|
||||
Loader.Dependencies[ctrlClass].push(view);
|
||||
} else if(typeof(dep) == 'object'){ // This view depends on other views
|
||||
let view = (this.defaults.viewsPath+'/'+dep.view).replace(/\/+/g,'/');
|
||||
// add view as dependency of controller
|
||||
Loader.Dependencies[ctrlClass].push(view);
|
||||
// add sub-dependencies without dups & with correct path
|
||||
if(!Loader.Dependencies.hasOwnProperty(view)) Loader.Dependencies[view] = [];
|
||||
for(let subview of dep.dependencies){
|
||||
if(Loader.Dependencies[view].indexOf(subview)<0) {
|
||||
Loader.Dependencies[view].push((this.defaults.viewsPath+'/'+subview).replace(/\/+/g,'/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove dups (maybe there was existing stuff before we got here)
|
||||
Loader.Dependencies[ctrlClass] = Array.from(new Set(Loader.Dependencies[ctrlClass]));
|
||||
Loader.AssetsDependencies[ctrlClass] = config.assets;
|
||||
}
|
||||
|
||||
autoLoadController(currentRoute, ctrlPath, ctrlClass, ctrlMethod, params){
|
||||
let scriptFp = (ctrlPath+'/'+ctrlClass).replace(/\/+/g,'/');
|
||||
let deps = {}; deps[scriptFp] = Loader.Dependencies[ctrlClass];
|
||||
return (
|
||||
Loader.loadScripts({
|
||||
'scripts':[scriptFp],
|
||||
'dependencies':deps,
|
||||
}).then(
|
||||
() => {
|
||||
this.controllerLoaded(currentRoute, ctrlPath, ctrlClass, ctrlMethod, params)
|
||||
},
|
||||
(error) => { // Loading of the route failed => Try to reroute to defaultroute
|
||||
let err = 'Loading of the class "'+ctrlClass+'" Failed !';
|
||||
console.error(err);
|
||||
if(currentRoute != this.httpErrorRoute) this.redirectToHttpErrorRoute(err);
|
||||
this.routeReady = true;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
registerController(ctrlClass){
|
||||
try {
|
||||
this.ControllerInstances[ctrlClass] = new app.LoadedClasses[ctrlClass]();
|
||||
} catch(e) { console.error(`"${e}" instantiating controller ${ctrlClass} `); } ;
|
||||
}
|
||||
|
||||
controllerLoaded(currentRoute, ctrlPath, ctrlClass, ctrlMethod, params){
|
||||
|
||||
this.registerController(ctrlClass);
|
||||
|
||||
if(ctrlClass != app.config.router.masterController) {
|
||||
let mTemplate
|
||||
if('template' in currentRoute)
|
||||
mTemplate = currentRoute.template;
|
||||
else
|
||||
mTemplate = app.config.router.defaultMasterTemplate;
|
||||
|
||||
// Launch the useTemplate and wait for it to be ready before launching the method.
|
||||
this.ControllerInstances[app.config.router.masterController].useTemplate(mTemplate).then(
|
||||
() => {
|
||||
this.launchControllerMethod(currentRoute, ctrlPath, ctrlClass, ctrlMethod, params);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
launchControllerMethod(currentRoute, ctrlPath, ctrlClass, ctrlMethod, params){
|
||||
if((ctrlClass in this.ControllerInstances) && (typeof(this.ControllerInstances[ctrlClass][ctrlMethod])=='function')) {
|
||||
this.currentRoute = currentRoute
|
||||
this.currentRoute.realUrl = new URL(document.location.href).pathname
|
||||
this.ControllerInstances[ctrlClass][ctrlMethod]({
|
||||
'currentRoute': currentRoute,
|
||||
'params': params
|
||||
});
|
||||
this.routeReady = true;
|
||||
if(typeof(this.callback)=='function') this.callback(ctrlClass,ctrlMethod,params);
|
||||
} else {
|
||||
let err;
|
||||
if(ctrlClass in this.ControllerInstances){
|
||||
err = `Could not find method "${ctrlMethod}" in class "${ctrlClass}"`;
|
||||
} else {
|
||||
err = `Controller "${ctrlClass}" not instantiated !`;
|
||||
}
|
||||
console.error(err);
|
||||
if(currentRoute != this.httpErrorRoute)
|
||||
this.redirectToHttpErrorRoute(err);
|
||||
this.routeReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
redirectToHttpErrorRoute(err=''){
|
||||
let ctrlPath = this.httpErrorRoute.controller.substr(0,this.httpErrorRoute.controller.lastIndexOf('/')+1);
|
||||
ctrlPath = (this.defaults.controllersPath+'/'+ctrlPath).replace(/\/+/g,'/');
|
||||
let ctrlClass = this.httpErrorRoute.controller.substr(this.httpErrorRoute.controller.lastIndexOf('/')+1);
|
||||
let ctrlMethod =this.httpErrorRoute.method;
|
||||
app.LoadedClasses.Assets.loadJson({
|
||||
'path': ctrlPath,
|
||||
'name': ctrlClass+'.json',
|
||||
}).then( (config) => {
|
||||
this.mergeControllerConfig(this.httpErrorRoute, ctrlPath, ctrlClass, ctrlMethod, {'error' : err}, config)
|
||||
this.autoLoadController(this.httpErrorRoute, ctrlPath, ctrlClass, ctrlMethod, {'error' : err});
|
||||
});
|
||||
}
|
||||
|
||||
getRoute(forcedUrl, forcedParams) {
|
||||
let pathname = this.cleanupUrl(forcedUrl).pathname;
|
||||
let bestroute = {'score':0, 'route':{}, 'parts':'', 'extractedParams':{}, 'gibberish':'', idx:0 };
|
||||
let idx = 0;
|
||||
let myRoles = (typeof(this.roles)=='function') ? this.roles() : this.roles ;
|
||||
for(let route of this.routes){
|
||||
route['forcedUrl'] = forcedUrl;
|
||||
route['forcedParams'] = forcedParams;
|
||||
let intersect = route['role'].filter(v => (myRoles.indexOf(v)>-1));
|
||||
if((route['role']!='*') && (intersect.length==0)) continue;
|
||||
|
||||
let [score, parts, paramvals, gibberish] = this.matchUrl(pathname, route);
|
||||
|
||||
// make sure a role specific route wins over a '*' role route (if already match, thus score>0, not to interfere with defaultroute).
|
||||
if((score>0) && (route['role']!='*') && (intersect.length>0)) {
|
||||
score++;
|
||||
}
|
||||
|
||||
// Best route wins, and when exaequo, childe-route (embed) wins over a parent-route
|
||||
if( (score > bestroute.score) || ((score == bestroute.score) && (idx > bestroute.idx)) ) {
|
||||
bestroute = {'score':score, 'route':route, 'parts':parts, 'extractedParams':paramvals, 'gibberish':gibberish, idx:idx };
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
if(bestroute['score']==0){ // no match => return default route
|
||||
if(this.httpErrorRoute)
|
||||
bestroute = {
|
||||
'score': 0,
|
||||
'route': this.httpErrorRoute,
|
||||
'parts': [],
|
||||
'extractedParams': {'error' : 'No route found for this URL!'} ,
|
||||
'gibberish': pathname,
|
||||
'idx': 0
|
||||
};
|
||||
else {
|
||||
console.error('No matching route for this url, and no default route defined!');
|
||||
return([null,'', '', '', {}]);
|
||||
}
|
||||
}
|
||||
|
||||
let ctrlPath, ctrlClass, ctrlMethod, methParams, exturl;
|
||||
if(bestroute.route.hasOwnProperty('controller')){ // Normal MVC route
|
||||
ctrlPath = bestroute.route.controller.substr(0,bestroute.route.controller.lastIndexOf('/')+1);
|
||||
ctrlPath = (this.defaults.controllersPath+'/'+ctrlPath).replace(/\/+/g,'/');
|
||||
ctrlClass = bestroute.route.controller.substr(bestroute.route.controller.lastIndexOf('/')+1);
|
||||
if(bestroute.route.hasOwnProperty('method') && (bestroute.route.method!='')){ // predefined Method in config
|
||||
ctrlMethod =bestroute.route.method;
|
||||
} else { // no predefined Method,
|
||||
// unused stuff in url ? use last url part
|
||||
if(bestroute.score >= 2) ctrlMethod = bestroute.parts[bestroute.parts.length-1];
|
||||
else ctrlMethod = 'index'; // nothing left in url ? default to index
|
||||
}
|
||||
|
||||
if(!bestroute.route.hasOwnProperty('extractedParams')) bestroute.params = {};
|
||||
methParams = {...bestroute.extractedParams, ...bestroute.route.params, ...forcedParams};
|
||||
exturl = '';
|
||||
} else { // External route
|
||||
exturl = bestroute.route.exturl;
|
||||
}
|
||||
return([bestroute.route, ctrlPath, ctrlClass, ctrlMethod, methParams, exturl]);
|
||||
}
|
||||
|
||||
cleanupRoutes(routes){
|
||||
const regex = /[\/:][^\/:]+/g;
|
||||
const paramregex = /^:(\w+)(\(.*\))?/;
|
||||
const pathregex = /^[-\/\w]+$/;
|
||||
const roleregex = /^[-\w]+$/;
|
||||
let cleanroutes = [];
|
||||
for(let route of routes){
|
||||
let keepit = true;
|
||||
|
||||
if(!route.hasOwnProperty('url')) {
|
||||
console.warn('Missing url in route definition, route ignored ! ',route);
|
||||
keepit = false;
|
||||
} else {
|
||||
if(route.url!='!defaultroute'){
|
||||
let routeparts = route.url.match(regex);
|
||||
if(routeparts===null) routeparts = ['/']; //Happens for empty of slah-only url; (or maybe cataclysmic ones)
|
||||
for(let routefrag of routeparts) {
|
||||
if(routefrag.charAt(0)=='/'){
|
||||
if(!(routefrag.match(pathregex))) {
|
||||
console.warn('Bad url in route definition (forbidden character in path), route ignored ! ',route);
|
||||
keepit = false;
|
||||
}
|
||||
} else {
|
||||
if(!(routefrag.match(paramregex))) {
|
||||
console.warn('Bad url in route definition (forbidden character in param), route ignored ! ',route);
|
||||
keepit = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.httpErrorRoute = route;
|
||||
}
|
||||
}
|
||||
|
||||
if(!route.hasOwnProperty('role')) {
|
||||
console.warn('Missing role in route definition, route ignored ! ',route);
|
||||
keepit = false;
|
||||
} else {
|
||||
if(typeof(route.role)=='string') route.role = [route.role];
|
||||
if(route.role!='*'){
|
||||
for(let role of route.role){ // also works if string (letter by letter but ok)
|
||||
if(!(role.match(roleregex))) {
|
||||
console.warn('Bad role in route definition (forbidden character), route ignored ! ',route);
|
||||
keepit = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( (!route.hasOwnProperty('controller')) && (!route.hasOwnProperty('exturl')) ) {
|
||||
console.warn('Missing one of controller / exturl in route definition, route ignored ! ',route);
|
||||
keepit = false;
|
||||
} else if(route.hasOwnProperty('exturl')) {
|
||||
// don't touch it !
|
||||
} else if(!(route.controller.match(pathregex))) {
|
||||
console.warn('Bad controller path in route definition (forbidden character), route ignored ! ',route);
|
||||
keepit = false;
|
||||
}
|
||||
if(keepit) cleanroutes.push(route);
|
||||
}
|
||||
return(cleanroutes);
|
||||
}
|
||||
|
||||
matchUrl(urlpath, route){
|
||||
const regex = /[\/:][^\/:]+/g;
|
||||
const paramregex = /:(\w+)(\(.*\))?/;
|
||||
|
||||
if((urlpath=='/') && (route.url=='/')) return([1, '/', {}, []]);
|
||||
|
||||
let urlpaths = urlpath.substr(1).split('/');
|
||||
let nbmatch = 0;
|
||||
let gibberish = '';
|
||||
let routeparts;
|
||||
routeparts = route.url.match(regex);
|
||||
let parts = []; let params = {};
|
||||
if(!routeparts) return([0, [], {}, '']);
|
||||
|
||||
for(let routefrag of routeparts) {
|
||||
if(nbmatch>(urlpaths.length-1)) {
|
||||
return([0, [], {}, '']);
|
||||
}
|
||||
if(routefrag.charAt(0)=='/'){ // url frag
|
||||
if(routefrag!='/') routefrag = routefrag.substr(1);
|
||||
if(routefrag!=urlpaths[nbmatch]){
|
||||
return([0, [], {}, '']);
|
||||
}
|
||||
parts.push(urlpaths[nbmatch]);
|
||||
} else { // param
|
||||
if(urlpaths[nbmatch]=='') return([0, [], {}, '']); // no '//' where param expected
|
||||
let parampattern = routefrag.match(paramregex);
|
||||
if(!parampattern) {
|
||||
console.warn('Bad parameter syntax in routes definition : '+routefrag+'(ignored)');
|
||||
} else {
|
||||
let [x, parname, parvalidation] = parampattern
|
||||
if(parvalidation){ //Validate with given regex
|
||||
let pvrx = RegExp('^'+parvalidation.substr(1, parvalidation.length-2)+'$');
|
||||
if(urlpaths[nbmatch].match(pvrx)) params[parname] = urlpaths[nbmatch];
|
||||
else return([0, [], {}, '']); // parameter with missed validation
|
||||
} else {
|
||||
params[parname] = urlpaths[nbmatch]; // take without validation
|
||||
}
|
||||
}
|
||||
}
|
||||
nbmatch++;
|
||||
}
|
||||
|
||||
let pointer;
|
||||
// Still have stuff in url, and no predefined method, so take meth from url
|
||||
if( (nbmatch<urlpaths.length) && ((!route.hasOwnProperty('method') || (route.method==''))) && (urlpaths[nbmatch]!='') ){
|
||||
parts.push(urlpaths[nbmatch]);
|
||||
pointer = nbmatch+1; // Pointer should advance as we've taken this part of the url, but does not count in score !
|
||||
} else pointer = nbmatch;
|
||||
|
||||
if(pointer<urlpaths.length){
|
||||
gibberish = urlpaths.splice(pointer).join('/');
|
||||
}
|
||||
return([nbmatch, parts, params, gibberish]);
|
||||
}
|
||||
|
||||
makelink(ctrl, method, params={}){
|
||||
let myRoles = (typeof(this.roles)=='function') ? this.roles() : this.roles ;
|
||||
for(let route of this.routes){
|
||||
let intersect = route['role'].filter(v => (myRoles.indexOf(v)>-1));
|
||||
if((route.role!='*') && (intersect.length==0)) continue
|
||||
|
||||
if(('/'+route.controller).replace(/\/+/g,'/')!=('/'+ctrl).replace(/\/+/g,'/')) continue
|
||||
let url=route.url;
|
||||
//Check if params and route params match
|
||||
let parok=true;
|
||||
let rparams = route.url.match(/:\w+/g);
|
||||
if(rparams){
|
||||
let re;
|
||||
for(let rparam of rparams){
|
||||
rparam=rparam.substr(1);
|
||||
if(!params.hasOwnProperty(rparam)) {
|
||||
parok=false;
|
||||
break;
|
||||
}
|
||||
re = RegExp(':'+rparam+'(?!\w)','g');
|
||||
url = url.replace(re, '/'+params[rparam]);
|
||||
}
|
||||
}
|
||||
if(!parok) continue;
|
||||
|
||||
if(!route.hasOwnProperty('method')){
|
||||
url+=('/'+method).replace(/\/+/g,'/');
|
||||
}
|
||||
return(url);
|
||||
}
|
||||
return('');
|
||||
}
|
||||
}
|
||||
app.registerClass('Router', Router);
|
||||
|
||||
Reference in New Issue
Block a user