Improved Crossroads

This commit is contained in:
djmaze 2021-10-19 11:31:44 +02:00
parent 1f187dd9f2
commit 9f5f066dbf
2 changed files with 70 additions and 108 deletions

View file

@ -108,7 +108,7 @@ export class AbstractSettingsScreen extends AbstractScreen {
rules = { rules = {
subname: /^(.*)$/, subname: /^(.*)$/,
normalize_: (rquest, vals) => { normalize_: (rquest, vals) => {
vals.subname = undefined === vals.subname ? defaultRoute : pString(vals.subname); vals.subname = null == vals.subname ? defaultRoute : pString(vals.subname);
return [vals.subname]; return [vals.subname];
} }
}; };

View file

@ -12,7 +12,7 @@
// Crossroads -------- // Crossroads --------
//==================== //====================
class Crossroads { global.Crossroads = class Crossroads {
constructor() { constructor() {
this._routes = []; this._routes = [];
@ -25,38 +25,19 @@
} }
parse(request) { parse(request) {
var routes = this._getMatchedRoutes(request || ''), request = request || '';
i = 0, var i = 0,
n = routes.length,
cur;
if (n) {
//shold be incremental loop, execute routes in order
while (i < n) {
cur = routes[i];
cur.route.callback && cur.route.callback(...cur.params);
cur.isFirst = !i;
i += 1;
}
}
}
_getMatchedRoutes(request) {
var res = [],
routes = this._routes, routes = this._routes,
n = routes.length, n = routes.length,
route; route;
//should be decrement loop since higher priorities are added at the end of array //should be decrement loop since higher priorities are added at the end of array
while (n) { while (n--) {
route = routes[--n]; route = routes[n];
if ((!res.length || route.greedy) && route.match(request)) { if ((!i || route.greedy) && route.match(request)) {
res.push({ route.callback && route.callback(...route._getParamsArray(request));
route : route, ++i;
params : route._getParamsArray(request)
});
} }
} }
return res;
} }
} }
@ -66,35 +47,28 @@
class Route { class Route {
constructor(pattern, callback, router) { constructor(pattern, callback, router) {
this.greedy = false;
this.rules = {};
var isRegexPattern = pattern instanceof RegExp; var isRegexPattern = pattern instanceof RegExp;
this._router = router; Object.assign(this, {
this._pattern = pattern; greedy: false,
this._paramsIds = isRegexPattern ? null : patternLexer.getParamIds(this._pattern); rules: {},
this._optionalParamsIds = isRegexPattern ? null : patternLexer.getOptionalParamsIds(this._pattern); _router: router,
this._matchRegexp = isRegexPattern ? pattern : patternLexer.compilePattern(pattern); _pattern: pattern,
this.callback = isFunction(callback) ? callback : null; _paramsIds: isRegexPattern ? null : captureVals(PARAMS_REGEXP, pattern),
_optionalParamsIds: isRegexPattern ? null : captureVals(OPTIONAL_PARAMS_REGEXP, pattern),
_matchRegexp: isRegexPattern ? pattern : compilePattern(pattern),
callback: isFunction(callback) ? callback : null
});
} }
match(request) { match(request) {
return this._matchRegexp.test(request) && this._validateParams(request); //validate params even if regexp because of `request_` rule. // validate params even if regexp.
}
_validateParams(request) {
var values = this._getParamsObject(request); var values = this._getParamsObject(request);
return 0 == Object.keys(this.rules).filter(key => return this._matchRegexp.test(request)
key !== 'normalize_' && !this._isValidParam(request, key, values) && 0 == Object.entries(this.rules).filter(([key, validationRule]) => {
).length; var val = values[key],
}
_isValidParam(request, prop, values) {
var validationRule = this.rules[prop],
val = values[prop],
isValid = false; isValid = false;
if (key === 'normalize_'
if (val == null && this._optionalParamsIds && this._optionalParamsIds.indexOf(prop) !== -1) { || (val == null && this._optionalParamsIds && this._optionalParamsIds.indexOf(key) !== -1)) {
isValid = true; isValid = true;
} }
else if (validationRule instanceof RegExp) { else if (validationRule instanceof RegExp) {
@ -106,28 +80,27 @@
else if (isFunction(validationRule)) { else if (isFunction(validationRule)) {
isValid = validationRule(val, request, values); isValid = validationRule(val, request, values);
} }
// fail silently if validationRule is from an unsupported type
return isValid; //fail silently if validationRule is from an unsupported type return !isValid;
}).length;
} }
_getParamsObject(request) { _getParamsObject(request) {
var values = patternLexer.getParamValues(request, this._matchRegexp), var values = getParamValues(request, this._matchRegexp) || [],
o = {},
n = values.length; n = values.length;
if (this._paramsIds) {
while (n--) { while (n--) {
o[n] = values[n]; //for RegExp pattern and also alias to normal paths values[this._paramsIds[n]] = values[n];
this._paramsIds && (o[this._paramsIds[n]] = values[n]);
} }
o.request_ = request; }
o.vals_ = values; return values;
return o;
} }
_getParamsArray(request) { _getParamsArray(request) {
var norm = this.rules.normalize_; var norm = this.rules.normalize_;
return isFunction(norm) return isFunction(norm)
? norm(request, this._getParamsObject(request)) ? norm(request, this._getParamsObject(request))
: patternLexer.getParamValues(request, this._matchRegexp); : getParamValues(request, this._matchRegexp);
} }
} }
@ -166,41 +139,30 @@
return vals; return vals;
}, },
tokenize = pattern => { getParamValues = (request, regexp) => {
//save chars that shouldn't be escaped
return pattern.replace(OPTIONAL_SLASHES_REGEXP, '$1'+ SAVE_OPTIONAL_SLASHES +'$2')
.replace(REQUIRED_SLASHES_REGEXP, '$1'+ SAVE_REQUIRED_SLASHES +'$2')
.replace(OPTIONAL_PARAMS_REGEXP, SAVE_OPTIONAL_PARAMS)
.replace(REQUIRED_PARAMS_REGEXP, SAVE_REQUIRED_PARAMS);
},
untokenize = pattern => {
return pattern.replace(SAVED_OPTIONAL_SLASHES_REGEXP, '\\/?')
.replace(SAVED_REQUIRED_SLASHES_REGEXP, '\\/')
.replace(SAVED_OPTIONAL_REGEXP, '([^\\/]+)?/?')
.replace(SAVED_REQUIRED_REGEXP, '([^\\/]+)');
},
patternLexer = {
getParamIds : pattern => captureVals(PARAMS_REGEXP, pattern),
getOptionalParamsIds : pattern => captureVals(OPTIONAL_PARAMS_REGEXP, pattern),
getParamValues : (request, regexp) => {
var vals = regexp.exec(request); var vals = regexp.exec(request);
if (vals) { if (vals) {
vals.shift(); vals.shift();
} }
return vals; return vals;
}, },
compilePattern : pattern => { compilePattern = pattern => {
pattern = pattern return new RegExp('^' + (pattern
? untokenize( ? pattern
tokenize(pattern.replace(UNNECESSARY_SLASHES_REGEXP, '')).replace(ESCAPE_CHARS_REGEXP, '\\$&') // tokenize, save chars that shouldn't be escaped
) .replace(UNNECESSARY_SLASHES_REGEXP, '')
: ''; .replace(OPTIONAL_SLASHES_REGEXP, '$1'+ SAVE_OPTIONAL_SLASHES +'$2')
return new RegExp('^'+ pattern + '/?$'); //trailing slash is optional .replace(REQUIRED_SLASHES_REGEXP, '$1'+ SAVE_REQUIRED_SLASHES +'$2')
} .replace(OPTIONAL_PARAMS_REGEXP, SAVE_OPTIONAL_PARAMS)
.replace(REQUIRED_PARAMS_REGEXP, SAVE_REQUIRED_PARAMS)
.replace(ESCAPE_CHARS_REGEXP, '\\$&')
// untokenize
.replace(SAVED_OPTIONAL_SLASHES_REGEXP, '\\/?')
.replace(SAVED_REQUIRED_SLASHES_REGEXP, '\\/')
.replace(SAVED_OPTIONAL_REGEXP, '([^\\/]+)?/?')
.replace(SAVED_REQUIRED_REGEXP, '([^\\/]+)')
: ''
) + '/?$'); //trailing slash is optional
}; };
global.Crossroads = Crossroads;
})(this); })(this);