Home Reference Source

src/Router.js

import EventHandler from './EventHandler.js'

/*
	Router maps window.history events and URL path fragments to events
	For example, routing /^blog\/([0-9]+)\/app\/([0-9a-z]+)$/ to an event with parameters for blog and app IDs:

		let router = new Router()

		// Set up a couple of routes, each with a URL matching regexs and a route name
		router.addRoute(/^$/, 'default') // matches http://<domain>/<path> and http://<domain>/<path>#
		router.addRoute(/^blog\/([0-9]+)\/app\/([0-9a-z\-]+)$/, 'blog-app') // matches http://<domain>/<path>#blog/1123/app/abc-123

		// Listen for the route
		router.addListener((routeName, hash, ...regexMatches) => {
			// If this was an event triggered by routing to #blog/1123/app/abc-123 then:
			// `routeName` would be 'blog-app'
			// `hash` would be 'blog/1123/app/abc-123'
			// `regexMatches` would be ['1123', 'abc-123']
		}, 'blog-app')
*/
const Router = class extends EventHandler {
	constructor() {
		super()
		this.cleanedUp = false
		this.routes = []
		this.hashListener = this._checkHash.bind(this)
		window.addEventListener('hashchange', this.hashListener, false)
	}
	cleanup() {
		if (this.cleanedUp) return
		this.cleanedUp = true
		window.removeEventListener('hashchange', this.hashListener)
		super.cleanup()
	}
	addRoute(regex, eventName, ...parameters) {
		this.routes.push(new Route(regex, eventName, ...parameters))
	}
	start() {
		this._checkHash()
	}
	_checkHash() {
		this._handleNewPath(document.location.hash.slice(1))
	}
	_handleNewPath(path) {
		for (const route of this.routes) {
			const matches = route.matches(path)
			if (matches == null) {
				continue
			}
			this.trigger(route.eventName, ...matches, ...route.parameters)
			return
		}
		this.trigger(Router.UnknownRouteEvent, path)
	}
}

Router.RouteAddedEvent = 'route-added'
Router.StartedRoutingEvent = 'started-routing'
Router.UnknownRouteEvent = 'unknown-route'

export default Router

/*
	Route tracks routes for Router
*/
const Route = class {
	constructor(regex, eventName, ...parameters) {
		this.regex = regex
		this.eventName = eventName
		this.parameters = parameters
	}
	matches(path) {
		return path.match(this.regex)
	}
}