"use strict"
/**
* ----------------------------------------------------------------------------------------------
* Imports
* ----------------------------------------------------------------------------------------------
*/
import Route from 'route-parser';
import {eventBus, AdmEventBus} from '../event/AdmEventBus';
import {joinPath} from '../utils/AdmUtils';
/**
* ----------------------------------------------------------------------------------------------
* Class AdmRouter - Handling app routing
* ----------------------------------------------------------------------------------------------
*/
class AdmRouter extends AdmEventBus{
/**
* Create a Router.
* @param {object} config - Configuration object of router
* @example
* //Example 1:
* const config = {
* root: '',
* routes: [{path, config, handler}],
* listen: true,
* change: () => {}
* fail: () => {}
* initComplete = () => {} || null;
* };
*
* const router = new AdmRouter(config);
*
* //Example 2:
* const router = new AdmRouter({root: ''});
* router
* .add("/", (routeData) => { ... }, configData)
* .add("/*", (routeData) => { ... }, configData)
* .add("*", (routeData) => { console.log("404"); })
* .listen()
*/
constructor(config = {}){
super();
// Public Properties
this.root = config.root || '';
this.path404 = config.path404 || '';
this.routes = config.routes || [];
this.initComplete = config.initComplete || null;
// Private Properties
this._routes = [];
this._changeHandler = null;
this._failHandler = null;
this._lastRoute = null;
// Initialize routes
if(config.routes) {
config.routes.forEach(item => { this.add(item.path, item.config, item.handler); })
}
if(config.listen){
this.listen();
}
if(config.change){
this._changeHandler = config.change;
}
if(config.fail){
this._failHandler = config.fail;
}
eventBus.addEventListener("navigate", (event) => {
this.navigate(event.detail.path, event.detail.data, '', event.detail.replace);
})
}
/**
* Add a route.
* @param {string} route - Expression of a route. For a catchall or 404 error route add an asteric ("*") as last route
* @param {object} config - Configuration of a route (optional)
* @param {function} handler - Handler by matching of a route (optional)
* @return {object} The router instance
*/
add(route, config = null, handler = null){
if(!route){
throw new Error(`AdmRouter:add - Adding Route "${route}" failed - Invalid arguments`);
}
const path = joinPath(this.root, route);
route = new Route(path);
this._routes.push({route, handler, config});
return this;
}
/**
* Remove a route.
* @param {string} route - Expression of a route
* @return {object} The router instance
*/
remove(route){
const numRoutes = this._routes.length;
for(let i = 0; i < numRoutes; i++) {
const path = joinPath(this.root, route);
if(this._routes[i].route.spec === path) {
this._routes.splice(i, 1);
break;
}
}
return this;
}
/**
* Remove all routes.
* @return {object} The router instance
*/
removeAll(){
this._routes = [];
return this;
}
/**
* Listen to url changes.
* @return {object} The router instance
*/
listen(){
window.addEventListener('popstate', (event) => {
const matchData = this.match(location.pathname + location.search, event.state);
this._callRouteHandler(matchData);
})
return this;
}
/**
* Match a route.
* @param {string} path - Path from an url
* @param {string} data - ThroughPass data (optional)
* @return {object} Match data from route
*/
match(path, data = null){
let matchData = {
route: null,
result: {
path: path,
pathParams: null,
throughPassData: data,
config: null,
status: 404
}
};
const numRoutes = this._routes.length;
for(let i = 0; i < numRoutes; i++) {
let match = this._routes[i].route.match(path);
if(match){
matchData.route = this._routes[i]
matchData.result.status = 200;
matchData.result.pathParams = match;
matchData.result.config = Object.assign({}, this._routes[i].config);
break;
}
}
return matchData;
}
/**
* Match a route.
* @param {string} path - Path to navigate
* @param {string} data - ThroughPass data (optional)
* @param {string} title - Title from website (optional)
* @param {boolean} replace - History replace - default false (optional)
*/
navigate(path, data = null, title = '', replace = false){
if(path !== location.pathname + location.search){
if(replace){
window.history.replaceState(data, title, path);
}else{
window.history.pushState(data, title, path);
}
}
const matchData = this.match(path, data);
this._callRouteHandler(matchData);
}
/**
* [Private] Handle route handlers.
* @param {string} matchData - result of match function
*/
_callRouteHandler(matchData){
this.lastRoute = matchData.result;
if(matchData?.route){
if(matchData.route.handler){
matchData.route.handler(matchData.result);
}
else if(this._changeHandler){
this._changeHandler(matchData.result);
}
else{
this.dispatchEvent("routeChanged", matchData.result);
}
}else{
if(this._failHandler){
this._failHandler(matchData.result);
}
else{
this.dispatchEvent("routeFailed", matchData.result);
}
}
}
}
export { AdmRouter }
Source