powahroot/Client/Router.mjs

106 lines
2.8 KiB
JavaScript

"use strict";
import EventEmitter from 'event-emitter-es6';
class ClientRouter extends EventEmitter {
constructor(options) {
super();
/***** Settings *****/
this.listen_pushstate = true;
this.verbose = false;
/********************/
for(let key in options) {
this[key] = options[key];
}
/********************/
/** Whether we should handle popstate events or not. @type {Boolean} */
this.handle_popstates = true;
this.routes = [];
window.addEventListener("popstate", ((event) => {
if(!this.handle_popstates) return;
console.info(`[ClientRouter] Handling popstate - path: ${window.location.hash.substr(1)} (state`, event.state, `)`);
this.navigate(window.location.hash.substr(1));
}).bind(this));
}
add_404(callback) {
this.callback_404 = callback;
}
add_page(routespec, callback) {
this.routes.push({
spec: routespec,
match: this.pathspec_to_regex(routespec),
callback
});
}
navigate(path) {
for(let route_info of this.routes) {
const matches = path.match(route_info.match.regex);
if(matches) {
if(this.verbose) console.log(`%c[Router] Matched against ${route_info.match.regex}!`, "color: hsl(331, 76%, 40%); font-weight: bold;");
// Build the parameters object
let params = {};
for(let i = 1; i < matches.length; i++) { // Skip the top-level group match
params[route_info.match.tokens[i-1]] = matches[i];
}
// Don't handle any popstates potentially generated by changing the hash
this.handle_popstates = false;
window.location.hash = `#${path}`;
this.handle_popstates = true;
route_info.callback(params);
return;
}
if(this.verbose) console.debug(`%c[Router] No match against ${route_info.match.regex} - moving on.`, "color: hsl(331, 76%, 40%)");
}
if(this.verbose) console.warn(`Couldn't find a match for '${path}'.`);
if(typeof this.callback_404 == "function")
this.callback_404(path);
}
navigate_current_hash() {
this.navigate(window.location.hash.substr(1));
}
/**
* Converts a path specification into a regular expression.
* From the server-side sibling of this (client-side) router.
* @param {string} pathspec The path specification to convert.
* @return {RegExp} The resulting regular expression
*/
pathspec_to_regex(pathspec) {
if(pathspec == "*") // Support wildcards
return { regex: /^/, tokens: [] };
let tokens = [];
let regex = new RegExp("^" + pathspec.replace(/::?([a-zA-Z0-9\-_]+)/g, (substr/*, index, template (not used)*/) => {
tokens.push(substr.replace(/:/g, ""));
// FUTURE: We could add optional param support here too
if(substr.startsWith("::"))
return `(.+)`;
else
return `([^\/]+)`;
}) + "$", "i");
if(this.verbose) console.info("[router/verbose] Created regex", regex);
return { regex, tokens };
}
}
export default ClientRouter;