],
})
+r.beforeEach((to, from, next) => {
+ console.log(`Guard from ${from.fullPath} to ${to.fullPath}`)
+ if (to.params.id === 'no-name') return next(false)
+ next()
+})
+
// const h = new HTML5History()
// @ts-ignore
const h = r.history
h.listen((to, from, { type }) => {
console.log(`popstate(${type})`, { to, from })
})
+async function run() {
+ // r.push('/multiple/one/two')
-// r.push('/multiple/one/two')
+ // h.push('/hey')
+ // h.push('/hey?lol')
+ // h.push('/foo')
+ // h.push('/replace-me')
+ // h.replace('/bar')
-// h.push('/hey')
-// h.push('/hey?lol')
-// h.push('/foo')
-// h.push('/replace-me')
-// h.replace('/bar')
+ // r.push('/about')
+ await r.push({
+ path: '/',
+ })
-// r.push('/about')
-r.push({
- path: '/',
-})
+ await r.push({
+ name: 'user',
+ params: {
+ id: '6',
+ },
+ })
-r.push({
- name: 'user',
- params: {
- id: '6',
- },
-})
+ await r.push({
+ name: 'user',
+ params: {
+ id: '5',
+ },
+ })
-r.push({
- name: 'user',
- params: {
- id: '5',
- },
-})
+ try {
+ await r.push({
+ params: {
+ id: 'no-name',
+ },
+ })
+ } catch (err) {
+ console.log('Navigation aborted', err)
+ }
-r.push({
- params: {
- id: 'no-name',
- },
-})
+ await r.push({
+ hash: '#hey',
+ })
+}
-r.push({
- hash: '#hey',
-})
+run()
import * as utils from './utils'
+import { ListenerRemover } from '../types'
export type HistoryQuery = Record<string, string | string[]>
): void
}
-export type RemoveListener = () => void
-
export abstract class BaseHistory {
// previousState: object
location: HistoryLocationNormalized = START
* @param callback callback to be called whenever the route changes
* @returns
*/
- abstract listen(callback: NavigationCallback): RemoveListener
+ abstract listen(callback: NavigationCallback): ListenerRemover
/**
* ensure the current location matches the external source
// call all listeners
const navigationInfo = {
type:
- from === state.forward ? NavigationType.back : NavigationType.forward,
+ // TODO: we should save somekind of id to detect the navigation type
+ state.forward && from.fullPath === state.forward.fullPath
+ ? NavigationType.back
+ : NavigationType.forward,
}
this._listeners.forEach(listener =>
listener(this.location, from, navigationInfo)
START_LOCATION_NORMALIZED,
RouteLocationNormalized,
MatcherLocationNormalized,
+ ListenerRemover,
+ NavigationGuard,
+ TODO,
+ NavigationGuardCallback,
} from './types/index'
-interface RouterOptions {
+export interface RouterOptions {
history: BaseHistory
routes: RouteRecord[]
}
export class Router {
protected history: BaseHistory
private matcher: RouterMatcher
+ private beforeGuards: NavigationGuard[] = []
currentRoute: Readonly<RouteLocationNormalized> = START_LOCATION_NORMALIZED
constructor(options: RouterOptions) {
* Trigger a navigation, should resolve all guards first
* @param to Where to go
*/
- push(to: RouteLocation) {
+ async push(to: RouteLocation) {
let url: HistoryLocationNormalized
let location: MatcherLocationNormalized
if (typeof to === 'string' || 'path' in to) {
})
}
- // TODO: call hooks, guards
+ // TODO: refactor in a function, some kind of queue
+ const toLocation: RouteLocationNormalized = { ...url, ...location }
+ await this.navigate(toLocation, this.currentRoute)
this.history.push(url)
- this.currentRoute = {
- ...url,
- ...location,
+ this.currentRoute = toLocation
+ }
+
+ async navigate(
+ to: RouteLocationNormalized,
+ from: RouteLocationNormalized
+ ): Promise<TODO> {
+ // TODO: Will probably need to be some kind of queue in the future that allows to remove
+ // elements and other stuff
+ const guards: Promise<any>[] = []
+
+ for (const guard of this.beforeGuards) {
+ guards.push(
+ new Promise((resolve, reject) => {
+ const next: NavigationGuardCallback = (valid?: boolean) => {
+ if (valid === false) reject(new Error('Aborted'))
+ else resolve()
+ }
+
+ guard(to, from, next)
+ })
+ )
+ }
+
+ console.log('Guarding against', guards.length, 'guards')
+ for (const guard of guards) {
+ await guard
}
}
getRouteRecord(location: RouteLocation) {}
+
+ beforeEach(guard: NavigationGuard): ListenerRemover {
+ this.beforeGuards.push(guard)
+ return () => {
+ this.beforeGuards.splice(this.beforeGuards.indexOf(guard), 1)
+ }
+ }
}
import { HistoryQuery } from '../history/base'
-type TODO = any
+export type TODO = any
+
+export type ListenerRemover = () => void
// TODO: support numbers for easier writing but cast them
export type RouteParams = Record<string, string | string[]>
// record?
params: RouteLocationNormalized['params']
}
+
+export interface NavigationGuardCallback {
+ (): void
+ (valid: false): void
+}
+export interface NavigationGuard {
+ (
+ to: RouteLocationNormalized,
+ from: RouteLocationNormalized,
+ next: NavigationGuardCallback
+ ): any
+}