analytics/assets/js/stats/router.js

82 lines
2.0 KiB
JavaScript

const PARAMETER_REGEXP = /([:*])(\w+)/g;
const REPLACE_VARIABLE_REGEXP = '([^\/]+)';
export default class Router {
constructor() {
this.routes = []
this.updateLinkHandlers()
window.addEventListener('popstate', this.resolve.bind(this));
}
on(path, handler) {
const {regex, paramNames} = this._generateRouteRegex(path)
this.routes.push({regex, paramNames, handler})
return this
}
resolve() {
const match = this._matchRoute()
if (match) {
match.route.handler(match.params)
}
return this
}
navigate(to) {
history.pushState({}, '', to)
this.resolve()
}
updateLinkHandlers() {
const self = this
const links = document.querySelectorAll('[data-pushstate]')
for (const link of links) {
if (!link.hasListenerAttached) {
link.addEventListener('click', function (e) {
if (e.ctrlKey || e.metaKey) {
return false;
}
e.preventDefault();
const location = link.getAttribute('href');;
self.navigate(location.replace(/\/+$/, '').replace(/^\/+/, '/'));
})
link.hasListenerAttached = true;
}
}
}
_matchRoute() {
const currentPath = window.location.pathname
for (const route of this.routes) {
const match = currentPath.replace(/^\/+/, '/').match(route.regex)
if (match) {
const params = this._extractParams(match, route.paramNames)
return {route, params}
}
}
}
_generateRouteRegex(path) {
const paramNames = []
const regex = path.replace(PARAMETER_REGEXP, function (full, dots, name) {
paramNames.push(name);
return REPLACE_VARIABLE_REGEXP;
})
return {regex: new RegExp(regex), paramNames}
}
_extractParams(match, paramNames) {
return match.slice(1, match.length).reduce(function(params, value, index) {
if (params === null) params = {};
params[paramNames[index]] = decodeURIComponent(value);
return params;
}, null);
}
}