Skip to content

Commit

Permalink
Add support for layered routing.
Browse files Browse the repository at this point in the history
  • Loading branch information
Dirbaio committed Jul 10, 2019
1 parent 799ceca commit b3d2a86
Show file tree
Hide file tree
Showing 13 changed files with 2,761 additions and 1,660 deletions.
1,045 changes: 639 additions & 406 deletions dist/vue-router.common.js

Large diffs are not rendered by default.

995 changes: 615 additions & 380 deletions dist/vue-router.esm.browser.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/vue-router.esm.browser.min.js

Large diffs are not rendered by default.

1,045 changes: 639 additions & 406 deletions dist/vue-router.esm.js

Large diffs are not rendered by default.

1,045 changes: 639 additions & 406 deletions dist/vue-router.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/vue-router.min.js

Large diffs are not rendered by default.

18 changes: 16 additions & 2 deletions src/components/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export default {
exact: Boolean,
append: Boolean,
replace: Boolean,
addLayer: Boolean,
removeLayer: Boolean,
activeClass: String,
exactActiveClass: String,
event: {
Expand Down Expand Up @@ -67,9 +69,21 @@ export default {
const handler = e => {
if (guardEvent(e)) {
if (this.replace) {
router.replace(location)
if (this.addLayer) {
router.replaceAddLayer(location)
} else if (this.removeLayer) {
router.replaceRemoveLayer()
} else {
router.replaceLayer(this._routerLayer, location)
}
} else {
router.push(location)
if (this.addLayer) {
router.pushAddLayer(location)
} else if (this.removeLayer) {
router.pushRemoveLayer()
} else {
router.pushLayer(this._routerLayer, location)
}
}
}
}
Expand Down
14 changes: 13 additions & 1 deletion src/components/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export default {
name: {
type: String,
default: 'default'
},
nextLayer: {
type: Boolean,
default: false
}
},
render (_, { props, children, parent, data }) {
Expand All @@ -18,9 +22,16 @@ export default {
// so that components rendered by router-view can resolve named slots
const h = parent.$createElement
const name = props.name
const route = parent.$route
const cache = parent._routerViewCache || (parent._routerViewCache = {})

const layer = parent._routerLayer + (props.nextLayer ? 1 : 0)
// render empty node if we don't have this high layers
if (parent._routerRoot._routes.length <= layer) {
cache[name] = null
return h()
}
const route = parent._routerRoot._routes[layer]

// determine current view depth, also check to see if the tree
// has been toggled inactive but kept-alive.
let depth = 0
Expand Down Expand Up @@ -64,6 +75,7 @@ export default {
) {
matched.instances[name] = val
}
vm._routerLayer = layer
}

// also register instance in prepatch hook
Expand Down
114 changes: 82 additions & 32 deletions src/history/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import { NavigationDuplicated } from './errors'
export class History {
router: Router
base: string
current: Route
pending: ?Route
current: Array<Route>;
pending: ?Array<Route>;
cb: (r: Route) => void
ready: boolean
readyCbs: Array<Function>
Expand All @@ -26,16 +26,19 @@ export class History {

// implemented by sub-classes
+go: (n: number) => void
+push: (loc: RawLocation) => void
+replace: (loc: RawLocation) => void
+navigateAllLayers: (locs: Array<RawLocation>, push: boolean) => void
+navigateLastLayer: (loc: RawLocation, push: boolean) => void
+navigateLayer: (layer: number, loc: RawLocation, push: boolean) => void
+navigateAddLayer: (loc: RawLocation, push: boolean) => void
+navigateRemoveLayer: (push: boolean) => void
+ensureURL: (push?: boolean) => void
+getCurrentLocation: () => string

constructor (router: Router, base: ?string) {
this.router = router
this.base = normalizeBase(base)
// start with a route object that stands for "nowhere"
this.current = START
this.current = [START]
this.pending = null
this.ready = false
this.readyCbs = []
Expand All @@ -62,24 +65,31 @@ export class History {
this.errorCbs.push(errorCb)
}

getClosestCurrent (n: number) {
if (n >= this.current.length) {
return this.current[this.current.length - 1]
}
return this.current[n]
}

transitionTo (
location: RawLocation,
locations: Array<RawLocation>,
onComplete?: Function,
onAbort?: Function
) {
const route = this.router.match(location, this.current)
const routes = locations.map((location, i) => this.router.match(location, this.getClosestCurrent(i)))
this.confirmTransition(
route,
() => {
this.updateRoute(route)
onComplete && onComplete(route)
routes,
(equalLayers) => {
this.updateCurrent(routes, equalLayers)
onComplete && onComplete(routes)
this.ensureURL()

// fire ready cbs once
if (!this.ready) {
this.ready = true
this.readyCbs.forEach(cb => {
cb(route)
cb(routes)
})
}
},
Expand All @@ -97,7 +107,7 @@ export class History {
)
}

confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
confirmTransition (routes: Array<Route>, onComplete: Function, onAbort?: Function) {
const current = this.current
const abort = err => {
// after merging https://github.com/vuejs/vue-router/pull/2771 we
Expand All @@ -116,18 +126,27 @@ export class History {
}
onAbort && onAbort(err)
}
if (
isSameRoute(route, current) &&
// in the case the route map has been dynamically appended to
route.matched.length === current.matched.length
) {

let equalLayers = 0
for (equalLayers = 0; equalLayers < routes.length && equalLayers < current.length; equalLayers++) {
if (
!isSameRoute(routes[equalLayers], current[equalLayers]) ||
// in the case the route map has been dynamically appended to
routes[equalLayers].matched.length !== current[equalLayers].matched.length
) {
break
}
}

if (equalLayers === routes.length && equalLayers === current.length) {
this.ensureURL()
return abort(new NavigationDuplicated(route))
return abort(new NavigationDuplicated(routes))
}

const { updated, deactivated, activated } = resolveQueue(
this.current.matched,
route.matched
const { updated, deactivated, activated } = resolveQueues(
current,
routes,
equalLayers
)

const queue: Array<?NavigationGuard> = [].concat(
Expand All @@ -143,13 +162,13 @@ export class History {
resolveAsyncComponents(activated)
)

this.pending = route
this.pending = routes
const iterator = (hook: NavigationGuard, next) => {
if (this.pending !== route) {
if (this.pending !== routes) {
return abort()
}
try {
hook(route, current, (to: any) => {
hook(routes, current, (to: any) => {
if (to === false || isError(to)) {
// next(false) -> abort navigation, ensure current URL
this.ensureURL(true)
Expand Down Expand Up @@ -178,17 +197,17 @@ export class History {

runQueue(queue, iterator, () => {
const postEnterCbs = []
const isValid = () => this.current === route
const isValid = () => this.current === routes
// wait until async components are resolved before
// extracting in-component enter guards
const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
const queue = enterGuards.concat(this.router.resolveHooks)
runQueue(queue, iterator, () => {
if (this.pending !== route) {
if (this.pending !== routes) {
return abort()
}
this.pending = null
onComplete(route)
onComplete(equalLayers)
if (this.router.app) {
this.router.app.$nextTick(() => {
postEnterCbs.forEach(cb => {
Expand All @@ -200,12 +219,12 @@ export class History {
})
}

updateRoute (route: Route) {
updateCurrent (routes: Array<Route>, equalLayers: number) {
const prev = this.current
this.current = route
this.cb && this.cb(route)
this.current = routes
this.cb && this.cb(routes, equalLayers)
this.router.afterHooks.forEach(hook => {
hook && hook(route, prev)
hook && hook(routes, prev)
})
}
}
Expand All @@ -230,6 +249,37 @@ function normalizeBase (base: ?string): string {
return base.replace(/\/$/, '')
}

function resolveQueues (
current: Array<Route>,
next: Array<Route>,
equalLayers: number
): {
updated: Array<RouteRecord>,
activated: Array<RouteRecord>,
deactivated: Array<RouteRecord>
} {
const res = {
updated: [],
activated: [],
deactivated: []
}

const min = Math.min(current.length, next.length)
for (let i = equalLayers; i < min; i++) {
const r = resolveQueue(current[i].matched, next[i].matched)
res.updated.push(...r.updated)
res.activated.push(...r.activated)
res.deactivated.push(...r.deactivated)
}
for (let i = min; i < current.length; i++) {
res.deactivated.push(...current[i].matched)
}
for (let i = min; i < next.length; i++) {
res.activated.push(...next[i].matched)
}
return res
}

function resolveQueue (
current: Array<RouteRecord>,
next: Array<RouteRecord>
Expand Down
57 changes: 42 additions & 15 deletions src/history/html5.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { History } from './base'
import { cleanPath } from '../util/path'
import { START } from '../util/route'
import { setupScroll, handleScroll } from '../util/scroll'
import { pushState, replaceState, supportsPushState } from '../util/push-state'
import { pushState, supportsPushState } from '../util/push-state'

export class HTML5History extends History {
constructor (router: Router, base: ?string) {
Expand All @@ -29,7 +29,11 @@ export class HTML5History extends History {
return
}

this.transitionTo(location, route => {
let locations = [location]
if (window.history.state.state) {
locations = window.history.state.state
}
this.transitionTo(locations, route => {
if (supportsScroll) {
handleScroll(router, route, current, true)
}
Expand All @@ -41,28 +45,51 @@ export class HTML5History extends History {
window.history.go(n)
}

push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
navigateAllLayers (locations: Array<RawLocation>, push: boolean, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushState(cleanPath(this.base + route.fullPath))
this.transitionTo(locations, routes => {
this.ensureURL(push)
const route = this.current[this.current.length - 1]
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}

replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
replaceState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
navigateLastLayer (location: RawLocation, push: boolean, onComplete?: Function, onAbort?: Function) {
const locations = [
...this.current.slice(0, -1).map(r => r.fullPath),
location
]
this.navigateAllLayers(locations, push, onComplete, onAbort)
}

navigateLayer (layer: number, location: RawLocation, push: boolean, onComplete?: Function, onAbort?: Function) {
const locations = [
...this.current.slice(0, layer).map(r => r.fullPath),
location,
...this.current.slice(layer + 1).map(r => r.fullPath)
]
this.navigateAllLayers(locations, push, onComplete, onAbort)
}

navigateAddLayer (location: RawLocation, push: boolean, onComplete?: Function, onAbort?: Function) {
const locations = [
...this.current.map(r => r.fullPath),
location
]
this.navigateAllLayers(locations, push, onComplete, onAbort)
}

navigateRemoveLayer (location: RawLocation, push: boolean, onComplete?: Function, onAbort?: Function) {
const locations = this.current.slice(0, -1).map(r => r.fullPath)
this.navigateAllLayers(locations, push, onComplete, onAbort)
}

ensureURL (push?: boolean) {
if (getLocation(this.base) !== this.current.fullPath) {
const current = cleanPath(this.base + this.current.fullPath)
push ? pushState(current) : replaceState(current)
const route = this.current[this.current.length - 1]
if (getLocation(this.base) !== route.fullPath) {
const path = cleanPath(this.base + route.fullPath)
pushState(path, this.current.map(r => r.fullPath), !push)
}
}

Expand Down
Loading

0 comments on commit b3d2a86

Please sign in to comment.