diff --git a/docs/en/advanced/navigation-guards.md b/docs/en/advanced/navigation-guards.md index 5217e958d..71b3d9a45 100644 --- a/docs/en/advanced/navigation-guards.md +++ b/docs/en/advanced/navigation-guards.md @@ -116,17 +116,19 @@ beforeRouteEnter (to, from, next) { } ``` -Note that `beforeRouteEnter` is the only guard that supports passing a callback to `next`. For `beforeRouteUpdate` and `beforeRouteLeave`, `this` is already available, so passing a callback is unnecessary and therefore *not supported*: - ```js beforeRouteUpdate (to, from, next) { - // just use `this` + // may use `this` this.name = to.params.name - next() + + // may also pass a callback to next + next(vm => { + vm.name = to.params.name + }) } ``` -The **leave guard** is usually used to prevent the user from accidentally leaving the route with unsaved edits. The navigation can be canceled by calling `next(false)`. +The **leave guard** is usually used to prevent the user from accidentally leaving the route with unsaved edits. The navigation can be canceled by calling `next(false)`. Note that `beforeRouteLeave` is the only guard that does not support passing a callback to `next`. ```js beforeRouteLeave (to, from , next) { diff --git a/examples/navigation-guards/app.js b/examples/navigation-guards/app.js index 86f33c95e..d9ca3db7d 100644 --- a/examples/navigation-guards/app.js +++ b/examples/navigation-guards/app.js @@ -33,7 +33,7 @@ const Baz = { }, template: `
-

baz ({{ saved ? 'saved' : 'not saved' }})

+

baz ({{ saved ? 'saved' : 'not saved' }})

`, @@ -46,7 +46,7 @@ const Baz = { } } -// Baz implements an in-component beforeRouteEnter hook +// Qux implements an in-component beforeRouteEnter hook const Qux = { data () { return { @@ -87,6 +87,57 @@ const Quux = { } } +// Nested implements an in-component beforeRouteUpdate hook that uses +// next(vm => {}) +const Nested = { + data () { + return { + calls: 0, + updates: 0 + } + }, + template: ` +
+

+ nested calls:{{ calls }} updates:{{ updates }} +

+ +
+ `, + beforeRouteUpdate (to, from, next) { + // calls is incremented every time anything navigates, even if the + // navigation is canceled by a child component. + this.calls++ + + next(vm => { + // updates is only incremented once all children confirm and complete + // navigation. If any children load data async, then this will not be + // called until all children have called next() themselves. If any child + // cancels navigation, then this will never be called. + vm.updates++ + }) + } +} + +// Buux implements a cancelable beforeRouteUpdate hook +const Buux = { + data () { + return { + prevId: 0 + } + }, + template: `
buux id:{{ $route.params.id }} prevId:{{ prevId }}
`, + beforeRouteUpdate (to, from, next) { + if (window.confirm(`Navigate to ${to.path}?`)) { + next(vm => { + vm.prevId = from.params.id + }) + } else { + next(false) + } + } +} + const router = new VueRouter({ mode: 'history', base: __dirname, @@ -114,7 +165,14 @@ const router = new VueRouter({ } }, // in-component beforeRouteUpdate hook - { path: '/quux/:id', component: Quux } + { path: '/quux/:id', component: Quux }, + { path: '/nested', component: Nested, + children: [ + { path: '', component: Home }, + { path: 'qux', component: Qux }, + { path: 'buux/:id', component: Buux } + ] + } ] }) @@ -140,6 +198,10 @@ new Vue({
  • /qux-async
  • /quux/1
  • /quux/2
  • +
  • /nested
  • +
  • /nested/qux
  • +
  • /nested/buux/1
  • +
  • /nested/buux/2
  • diff --git a/src/history/base.js b/src/history/base.js index 0c90dafa7..f8f135353 100644 --- a/src/history/base.js +++ b/src/history/base.js @@ -112,13 +112,15 @@ export class History { activated } = resolveQueue(this.current.matched, route.matched) + const postCbs = [] + const isValid = () => this.current === route const queue: Array = [].concat( // in-component leave guards extractLeaveGuards(deactivated), // global before hooks this.router.beforeHooks, // in-component update hooks - extractUpdateHooks(updated), + extractUpdateHooks(updated, postCbs, isValid), // in-config enter guards activated.map(m => m.beforeEnter), // async components @@ -161,11 +163,9 @@ export class History { } runQueue(queue, iterator, () => { - const postEnterCbs = [] - const isValid = () => this.current === route // wait until async components are resolved before // extracting in-component enter guards - const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid) + const enterGuards = extractEnterGuards(activated, postCbs, isValid) const queue = enterGuards.concat(this.router.resolveHooks) runQueue(queue, iterator, () => { if (this.pending !== route) { @@ -175,7 +175,7 @@ export class History { onComplete(route) if (this.router.app) { this.router.app.$nextTick(() => { - postEnterCbs.forEach(cb => { cb() }) + postCbs.forEach(cb => { cb() }) }) } }) @@ -266,10 +266,6 @@ function extractLeaveGuards (deactivated: Array): Array return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true) } -function extractUpdateHooks (updated: Array): Array { - return extractGuards(updated, 'beforeRouteUpdate', bindGuard) -} - function bindGuard (guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard { if (instance) { return function boundRouteGuard () { @@ -278,24 +274,37 @@ function bindGuard (guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard { } } +function extractUpdateHooks ( + updated: Array, + cbs: Array, + isValid: () => boolean +): Array { + return extractGuards(updated, 'beforeRouteUpdate', (guard, instance, match, key) => { + var boundGuard = bindGuard(guard, instance) + if (boundGuard) { + return bindCbGuard(boundGuard, match, key, cbs, isValid) + } + }) +} + function extractEnterGuards ( activated: Array, cbs: Array, isValid: () => boolean ): Array { return extractGuards(activated, 'beforeRouteEnter', (guard, _, match, key) => { - return bindEnterGuard(guard, match, key, cbs, isValid) + return bindCbGuard(guard, match, key, cbs, isValid) }) } -function bindEnterGuard ( +function bindCbGuard ( guard: NavigationGuard, match: RouteRecord, key: string, cbs: Array, isValid: () => boolean ): NavigationGuard { - return function routeEnterGuard (to, from, next) { + return function (to, from, next) { return guard(to, from, cb => { next(cb) if (typeof cb === 'function') { diff --git a/test/e2e/specs/navigation-guards.js b/test/e2e/specs/navigation-guards.js index a5afbc104..3798e222c 100644 --- a/test/e2e/specs/navigation-guards.js +++ b/test/e2e/specs/navigation-guards.js @@ -8,7 +8,7 @@ module.exports = { browser .url('http://localhost:8080/navigation-guards/') .waitForElementVisible('#app', 1000) - .assert.count('li a', 8) + .assert.count('li a', 12) .assert.containsText('.view', 'home') .click('li:nth-child(2) a') @@ -130,6 +130,32 @@ module.exports = { .click('li:nth-child(7) a') .assert.urlEquals('http://localhost:8080/navigation-guards/quux/1') .assert.containsText('.view', 'id:1 prevId:2') - .end() + .click('li:nth-child(9) a') + .assert.urlEquals('http://localhost:8080/navigation-guards/nested') + .assert.containsText('.view', 'nested calls:0 updates:0') + .click('li:nth-child(10) a') + .assert.containsText('.view', 'nested calls:1 updates:0') + .waitFor(300) + .assert.urlEquals('http://localhost:8080/navigation-guards/nested/qux') + .assert.containsText('.view', 'nested calls:1 updates:1') + .assert.containsText('.view', 'Qux') + .click('li:nth-child(11) a') + .assert.urlEquals('http://localhost:8080/navigation-guards/nested/buux/1') + .assert.containsText('.view', 'nested calls:2 updates:2') + .assert.containsText('.view', 'buux id:1 prevId:0') + .click('li:nth-child(12) a') + .dismissAlert() + .assert.urlEquals('http://localhost:8080/navigation-guards/nested/buux/1') + .assert.containsText('.view', 'nested calls:3 updates:2') + .assert.containsText('.view', 'buux id:1 prevId:0') + .click('li:nth-child(12) a') + .acceptAlert() + .assert.urlEquals('http://localhost:8080/navigation-guards/nested/buux/2') + .assert.containsText('.view', 'nested calls:4 updates:3') + .assert.containsText('.view', 'buux id:2 prevId:1') + .click('li:nth-child(9) a') + .assert.urlEquals('http://localhost:8080/navigation-guards/nested') + .assert.containsText('.view', 'nested calls:5 updates:4') + .end() } }