/* @flow */
|
|
import type VueRouter from '../index'
|
import { stringifyQuery } from './query'
|
|
const trailingSlashRE = /\/?$/
|
|
export function createRoute (
|
record: ?RouteRecord,
|
location: Location,
|
redirectedFrom?: ?Location,
|
router?: VueRouter
|
): Route {
|
const stringifyQuery = router && router.options.stringifyQuery
|
|
let query: any = location.query || {}
|
try {
|
query = clone(query)
|
} catch (e) {}
|
|
const route: Route = {
|
name: location.name || (record && record.name),
|
meta: (record && record.meta) || {},
|
path: location.path || '/',
|
hash: location.hash || '',
|
query,
|
params: location.params || {},
|
fullPath: getFullPath(location, stringifyQuery),
|
matched: record ? formatMatch(record) : []
|
}
|
if (redirectedFrom) {
|
route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
|
}
|
return Object.freeze(route)
|
}
|
|
function clone (value) {
|
if (Array.isArray(value)) {
|
return value.map(clone)
|
} else if (value && typeof value === 'object') {
|
const res = {}
|
for (const key in value) {
|
res[key] = clone(value[key])
|
}
|
return res
|
} else {
|
return value
|
}
|
}
|
|
// the starting route that represents the initial state
|
export const START = createRoute(null, {
|
path: '/'
|
})
|
|
function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
|
const res = []
|
while (record) {
|
res.unshift(record)
|
record = record.parent
|
}
|
return res
|
}
|
|
function getFullPath (
|
{ path, query = {}, hash = '' },
|
_stringifyQuery
|
): string {
|
const stringify = _stringifyQuery || stringifyQuery
|
return (path || '/') + stringify(query) + hash
|
}
|
|
export function isSameRoute (a: Route, b: ?Route, onlyPath: ?boolean): boolean {
|
if (b === START) {
|
return a === b
|
} else if (!b) {
|
return false
|
} else if (a.path && b.path) {
|
return a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && (onlyPath ||
|
a.hash === b.hash &&
|
isObjectEqual(a.query, b.query))
|
} else if (a.name && b.name) {
|
return (
|
a.name === b.name &&
|
(onlyPath || (
|
a.hash === b.hash &&
|
isObjectEqual(a.query, b.query) &&
|
isObjectEqual(a.params, b.params))
|
)
|
)
|
} else {
|
return false
|
}
|
}
|
|
function isObjectEqual (a = {}, b = {}): boolean {
|
// handle null value #1566
|
if (!a || !b) return a === b
|
const aKeys = Object.keys(a).sort()
|
const bKeys = Object.keys(b).sort()
|
if (aKeys.length !== bKeys.length) {
|
return false
|
}
|
return aKeys.every((key, i) => {
|
const aVal = a[key]
|
const bKey = bKeys[i]
|
if (bKey !== key) return false
|
const bVal = b[key]
|
// query values can be null and undefined
|
if (aVal == null || bVal == null) return aVal === bVal
|
// check nested equality
|
if (typeof aVal === 'object' && typeof bVal === 'object') {
|
return isObjectEqual(aVal, bVal)
|
}
|
return String(aVal) === String(bVal)
|
})
|
}
|
|
export function isIncludedRoute (current: Route, target: Route): boolean {
|
return (
|
current.path.replace(trailingSlashRE, '/').indexOf(
|
target.path.replace(trailingSlashRE, '/')
|
) === 0 &&
|
(!target.hash || current.hash === target.hash) &&
|
queryIncludes(current.query, target.query)
|
)
|
}
|
|
function queryIncludes (current: Dictionary<string>, target: Dictionary<string>): boolean {
|
for (const key in target) {
|
if (!(key in current)) {
|
return false
|
}
|
}
|
return true
|
}
|
|
export function handleRouteEntered (route: Route) {
|
for (let i = 0; i < route.matched.length; i++) {
|
const record = route.matched[i]
|
for (const name in record.instances) {
|
const instance = record.instances[name]
|
const cbs = record.enteredCbs[name]
|
if (!instance || !cbs) continue
|
delete record.enteredCbs[name]
|
for (let i = 0; i < cbs.length; i++) {
|
if (!instance._isBeingDestroyed) cbs[i](instance)
|
}
|
}
|
}
|
}
|