const Observable = require('frappejs/utils/observable'); module.exports = class Router extends Observable { constructor() { super(); this.last_route = null; this.current_page = null; this.static_routes = []; this.dynamic_routes = []; } add(route, handler) { let page = {handler: handler, route: route}; // '/todo/:name/:place'.match(/:([^/]+)/g); page.param_keys = route.match(/:([^/]+)/g); if (page.param_keys) { // make expression // '/todo/:name/:place'.replace(/\/:([^/]+)/g, "\/([^/]+)"); page.depth = route.split('/').length; page.expression = route.replace(/\/:([^/]+)/g, "\/([^/]+)"); this.dynamic_routes.push(page); this.sort_dynamic_routes(); } else { this.static_routes.push(page); this.sort_static_routes(); } } sort_dynamic_routes() { // routes with more parts first this.dynamic_routes = this.dynamic_routes.sort((a, b) => { if (a.depth < b.depth) { return 1; } else if (a.depth > b.depth) { return -1; } else { if (a.param_keys.length !== b.param_keys.length) { return a.param_keys.length > b.param_keys.length ? 1 : -1; } else { return a.route.length > b.route.length ? 1 : -1; } } }) } sort_static_routes() { // longer routes on first this.static_routes = this.static_routes.sort((a, b) => { return a.route.length > b.route.length ? 1 : -1; }); } listen() { window.addEventListener('hashchange', (event) => { let route = this.getRoute_string(); if (this.last_route !== route) { this.show(route); } }); } // split and get routes getRoute() { let route = this.getRoute_string(); if (route) { return route.split('/'); } else { return []; } } async setRoute(...parts) { const route = parts.join('/'); // setting this first, does not trigger show via hashchange, // since we want to this with async/await, we need to trigger // the show method this.last_route = route; window.location.hash = route; await this.show(route); } async show(route) { if (route && route[0]==='#') { route = route.substr(1); } this.last_route = route; if (!route) { route = this.default; } let page = this.match(route); if (page) { if (typeof page.handler==='function') { await page.handler(page.params); } else { await page.handler.show(page.params); } } else { await this.match('not-found').handler({route: route}); } await this.trigger('change'); } match(route) { // match static for(let page of this.static_routes) { if (page.route === route) { return {handler: page.handler}; } } // match dynamic for(let page of this.dynamic_routes) { let matches = route.match(new RegExp(page.expression)); if (matches && matches.length == page.param_keys.length + 1) { let params = {} for (let i=0; i < page.param_keys.length; i++) { params[page.param_keys[i].substr(1)] = matches[i + 1]; } return {handler:page.handler, params: params}; } } } getRoute_string() { let route = window.location.hash; if (route && route[0]==='#') { route = route.substr(1); } return route; } }