UNPKG

@transmutable/bink

Version:

A sleek and reactive framework for web pages

104 lines (92 loc) 3.35 kB
import EventHandler from './EventHandler.js' /** Router maps [`window.history`](https://developer.mozilla.org/en-US/docs/Web/API/Window/history) events and URL path fragments (the part after the `#` in https://example.com/path#one-two-three) to events. This is usually used by an {@link App} to show and hide {@link Component}s based on the URL. @example <caption>routing `/^blog\/([0-9]+)\/app\/([0-9a-z]+)$/` to an event with parameters for blog and app IDs</caption> let router = new Router() // Set up a couple of routes, each with a URL matching regexs and a route name // matches http://<domain>/<path> and http://<domain>/<path># router.addRoute(/^$/, 'default') // matches http://<domain>/<path>#blog/1123/app/abc-123 router.addRoute(/^blog\/([0-9]+)\/app\/([0-9a-z\-]+)$/, 'blog-app') // Listen for the route router.addListener('blog-app', (routeName, hash, ...regexMatches, ...parameters) => { // 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'] // `parameters` is empty in this example but could be any number of extra items in the route (see `addRoute`) }) // Start must be called in order to begin routing router.start() */ const Router = class extends EventHandler { constructor() { super() /** @type {boolean} */ this.cleanedUp = false /** @type {Array<Router>} */ this.routes = [] this._checkHash = this._checkHash.bind(this) window.addEventListener('hashchange', this._checkHash, false) } cleanup() { if (this.cleanedUp) return this.cleanedUp = true window.removeEventListener('hashchange', this._checkHash) super.cleanup() } /** @param {RegExp} regex - The regular expression that matches the incoming hash changes @param {string} eventName - The event name used when triggering @param {...*} parameters - Parameters passed without modification to listeners after the event name and matches */ addRoute(regex, eventName, ...parameters) { const route = new Route(regex, eventName, ...parameters) this.routes.push(route) this.trigger(Router.RouteAddedEvent, this, route) } /** This must be called in order to start routing. */ start() { this._checkHash() // This event is triggered after the hash check (and its subsequent event) so that listeners are notified when in a fully started state this.trigger(Router.StartedRoutingEvent, this) } _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 = Symbol('route-added') Router.StartedRoutingEvent = Symbol('started-routing') Router.UnknownRouteEvent = Symbol('unknown-route') /* Route tracks URL routes for {@link Router} */ const Route = class { constructor(regex, eventName, ...parameters) { this.regex = regex this.eventName = eventName this.parameters = parameters } /** @return {boolean} true if this route matches a given path */ matches(path) { return path.match(this.regex) } } export default Router export { Router, Route }