]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat: handle initial navigation
authorEduardo San Martin Morote <posva13@gmail.com>
Fri, 28 Jun 2019 09:29:15 +0000 (11:29 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Fri, 28 Jun 2019 09:29:15 +0000 (11:29 +0200)
src/history/html5.ts
src/index.ts
src/router.ts

index 661b2d5f94b134373be9e387f321429a6e17a0dc..2e10e447e8ed302937f96fda7f023ed5bfab833e 100644 (file)
@@ -51,7 +51,7 @@ export class HTML5History extends BaseHistory {
 
   constructor() {
     super()
-    const to = buildFullPath()
+    const to = this.createCurrentLocation()
     // cs.log('created', to)
     this.history.replaceState(buildState(null, to, null), '', to.fullPath)
     this.location = to
@@ -157,7 +157,7 @@ export class HTML5History extends BaseHistory {
       const from = this.location
       // we have the state from the old entry, not the current one being removed
       // TODO: correctly parse pathname
-      const to = state ? state.current : buildFullPath()
+      const to = state ? state.current : this.createCurrentLocation()
       this.location = to
 
       if (
@@ -194,14 +194,14 @@ export class HTML5History extends BaseHistory {
       to,
     }
   }
-}
 
-const buildFullPath = () => {
-  const { location } = window
-  return {
-    fullPath: location.pathname + location.search + location.hash,
-    path: location.pathname,
-    query: {}, // TODO: parseQuery
-    hash: location.hash,
+  createCurrentLocation(): HistoryLocationNormalized {
+    const { location } = window
+    return {
+      fullPath: location.pathname + location.search + location.hash,
+      path: location.pathname,
+      query: this.utils.parseQuery(location.search),
+      hash: location.hash,
+    }
   }
 }
index 5d5afbd39cd1085dd40551a45311780df76915f7..8c98689b638f7a9032256f05cf76ac78d337a5d7 100644 (file)
@@ -14,7 +14,7 @@ const plugin: PluginFunction<void> = Vue => {
         // @ts-ignore
         this._routerRoot = this
         // @ts-ignore
-        this._router = this.$options.router
+        this._router = this.$options.router as Router
         // this._router.init(this)
         // @ts-ignore
         this._router.app = this
@@ -28,6 +28,9 @@ const plugin: PluginFunction<void> = Vue => {
           // undefined,
           // true
         )
+
+        // @ts-ignore
+        this._router.doInitialNavigation()
       } else {
         // @ts-ignore
         this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
index eb789946f3fc30c30cc3986298629987d4bb9fd3..68877714af3613fb8abd4445dd6233e51dde1e8b 100644 (file)
@@ -32,6 +32,8 @@ export interface RouterOptions {
 
 type ErrorHandler = (error: any) => any
 
+// resolve, reject arguments of Promise constructor
+type OnReadyCallback = [() => void, (reason?: any) => void]
 export class Router {
   protected history: BaseHistory
   private matcher: RouterMatcher
@@ -42,6 +44,8 @@ export class Router {
   private app: any
   // TODO: should these be triggered before or after route.push().catch()
   private errorHandlers: ErrorHandler[] = []
+  private ready: boolean = false
+  private onReadyCbs: OnReadyCallback[] = []
 
   constructor(options: RouterOptions) {
     this.history = options.history
@@ -217,7 +221,7 @@ export class Router {
     }
 
     // TODO: should we throw an error as the navigation was aborted
-    // TODO: needs a proper check because order could be different
+    // TODO: needs a proper check because order in query could be different
     if (
       this.currentRoute !== START_LOCATION_NORMALIZED &&
       this.currentRoute.fullPath === url.fullPath
@@ -409,4 +413,82 @@ export class Router {
     Object.defineProperty(route, 'matched', { enumerable: false })
     this.app._route = Object.freeze(route)
   }
+
+  /**
+   * Returns a Promise that resolves once the router is ready to be used for navigation
+   * Eg: Calling router.push() or router.replace(). This is necessary because we have to
+   * wait for the Vue root instance to be created
+   */
+  onReady(): Promise<void> {
+    if (this.ready) return Promise.resolve()
+    return new Promise((resolve, reject) => {
+      this.onReadyCbs.push([resolve, reject])
+    })
+  }
+
+  /**
+   * Mark the router as ready. This function is used internal and should not be called
+   * by the developper. You can optionally provide an error.
+   * This will trigger all onReady callbacks and empty the array
+   * @param {*} err
+   */
+  protected markAsReady(err?: any): void {
+    if (this.ready) return
+    for (const [resolve, reject] of this.onReadyCbs) {
+      if (err) reject(err)
+      else resolve()
+    }
+    this.onReadyCbs = []
+    this.ready = true
+  }
+
+  async doInitialNavigation(): Promise<RouteLocationNormalized> {
+    // TODO: refactor code that was duplicated from push method
+    const toLocation: RouteLocationNormalized = this.resolveLocation(
+      this.history.location,
+      this.currentRoute
+    )
+
+    this.pendingLocation = toLocation
+    // trigger all guards, throw if navigation is rejected
+    try {
+      await this.navigate(toLocation, this.currentRoute)
+    } catch (error) {
+      if (error instanceof NavigationGuardRedirect) {
+        // push was called while waiting in guards
+        if (this.pendingLocation !== toLocation) {
+          // TODO: trigger onError as well
+          throw new NavigationCancelled(toLocation, this.currentRoute)
+        }
+        // TODO: setup redirect stack
+        return this.push(error.to)
+      } else {
+        // TODO: write tests
+        // triggerError as well
+        if (this.pendingLocation !== toLocation) {
+          // TODO: trigger onError as well
+          throw new NavigationCancelled(toLocation, this.currentRoute)
+        }
+
+        this.triggerError(error)
+      }
+    }
+
+    // push was called while waiting in guards
+    if (this.pendingLocation !== toLocation) {
+      throw new NavigationCancelled(toLocation, this.currentRoute)
+    }
+
+    // NOTE: here we removed the pushing to history part as the history
+    // already contains current location
+
+    const from = this.currentRoute
+    this.currentRoute = toLocation
+    this.updateReactiveRoute()
+
+    // navigation is confirmed, call afterGuards
+    for (const guard of this.afterGuards) guard(toLocation, from)
+
+    return this.currentRoute
+  }
 }