export interface Route { path: string callback: Function } export interface RouterRule { variable: RegExp } export class Router { public data: string[] public matchIsFound: boolean = false public noMatchCallback: Function = () => { } public pathData: any[] public queryStringsData: string[] public rootCallback: Function public routes: Route[] public rules: RouterRule[] constructor() { this.reset() } public reset(): Router { this.rules = new Array this.routes = new Array this.pathData = new Array this.queryStringsData = new Array this.data = new Array return this } public root(callback: Function): Router { this.rootCallback = callback return this } public noMatch(callback: Function): Router { this.noMatchCallback = callback return this } public addRule(variable: string, regex: RegExp): Router { this.rules[variable] = regex return this } public setRules(rules: Array): Router { for (let variable in rules) { this.rules[variable] = rules[variable] } return this } public match(path: string, callback: Function): Router { this.routes.push({ callback: callback, path: path }) return this } public route(): Router { for (let route of this.routes) { if (this.matchRoute(route)) { break } } // If there's no more match: if (this.matchIsFound === false) { this.noMatchCallback() } return this } public matchRoute(route: Route): boolean { // Break down route path to fragments: let routePath: string = this.removeForwardSlashEdges(route.path) let routePathFragments: string[] = routePath.split('/') // get current path and break it into fragments let path: string = this.removeForwardSlashEdges(window.location.pathname) if (path === '') { this.matchIsFound = true this.aggregateData() this.rootCallback(this) return true } let pathFragments: string[] = path.split('/') //return false if the number of fragments do not match if (pathFragments.length !== routePathFragments.length) { return false } let counter: number = 0 let data: string[] = [] for (let fragment in routePathFragments) { // If routeFrament is a variable: if (fragment.match(/^\{[a-zA-Z0-9\-\_]+\}$/g)) { // Remove curly brackets. let key = fragment.replace(/({|})/g, '') // If there is a rule with key and // path fragment match rule or // there's no rule matching key and path fragment is not empty. if ( this.rules[key] && pathFragments[counter].match(this.rules[key]) || (!this.rules[key] && pathFragments[counter] !== '') ) { data[key] = pathFragments[counter] } else if (this.rules[key] && !pathFragments[counter].match(this.rules[key])) { return false } // If it's not a variable. } else { // Return false if route fragment doesn't match path fragment. if (fragment !== pathFragments[counter]) { return false } } counter++ } // Passed all tests // it's a match! this.matchIsFound = true this.pathData = data this.aggregateData() route.callback() return true } // private private aggregateData(): Router { if (this.pathData) { for (let key in this.pathData) { this.data[key] = this.pathData[key] } } if (this.queryStringsData) { for (let key in this.queryStringsData) { this.data[key] = this.queryStringsData[key] } } return this } private getqueryStringsData(): Router { this.queryStringsData = new Array let queryString: string = window.location.search if (queryString) { // Remove question mark. let queryStrings: string[] = queryString.replace(/^\?/g, '').split('&') for (let string in queryStrings) { let datum: string[] = string.split('=') this.queryStringsData[datum[0]] = datum[1] } } return this } private removeForwardSlashEdges(string: string): string { return string.replace(/^\//g, '').replace(/\/$/g, '') } }