Skip to content

Commit 78316c0

Browse files
author
Joe Nields
committed
feature: add support for attaching state to location
1 parent 17f9664 commit 78316c0

File tree

13 files changed

+154
-36
lines changed

13 files changed

+154
-36
lines changed

Diff for: flow/declarations.js

+2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ declare type Location = {
8484
path?: string;
8585
hash?: string;
8686
query?: Dictionary<string>;
87+
state?: mixed;
8788
params?: Dictionary<string>;
8889
append?: boolean;
8990
replace?: boolean;
@@ -96,6 +97,7 @@ declare type Route = {
9697
name: ?string;
9798
hash: string;
9899
query: Dictionary<string>;
100+
state: mixed;
99101
params: Dictionary<string>;
100102
fullPath: string;
101103
matched: Array<RouteRecord>;

Diff for: src/history/abstract.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ export class AbstractHistory extends History {
4242

4343
getCurrentLocation () {
4444
const current = this.stack[this.stack.length - 1]
45-
return current ? current.fullPath : '/'
45+
return current
46+
? {
47+
path: current.path,
48+
query: current.query,
49+
hash: current.hash,
50+
state: current.state
51+
}
52+
: { path: '/' }
4653
}
4754

4855
ensureURL () {

Diff for: src/history/base.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class History {
2828
+push: (loc: RawLocation) => void;
2929
+replace: (loc: RawLocation) => void;
3030
+ensureURL: (push?: boolean) => void;
31-
+getCurrentLocation: () => string;
31+
+getCurrentLocation: () => Location;
3232

3333
constructor (router: Router, base: ?string) {
3434
this.router = router

Diff for: src/history/hash.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import type Router from '../index'
44
import { History } from './base'
5-
import { cleanPath } from '../util/path'
5+
import { cleanPath, parsePath } from '../util/path'
6+
import { resolveQuery, stringifyQuery } from '../util/query'
67
import { getLocation } from './html5'
78
import { setupScroll, handleScroll } from '../util/scroll'
89
import { pushState, replaceState, supportsPushState } from '../util/push-state'
@@ -74,12 +75,16 @@ export class HashHistory extends History {
7475
}
7576

7677
getCurrentLocation () {
77-
return getHash()
78+
const { path, query, hash } = parsePath(getHash())
79+
return { path, query: resolveQuery(query), hash }
7880
}
7981
}
8082

8183
function checkFallback (base) {
82-
const location = getLocation(base)
84+
const rawLocation = getLocation(base)
85+
const location = typeof rawLocation === 'string'
86+
? rawLocation
87+
: `${rawLocation.path || ''}${stringifyQuery(rawLocation.query || {})}${rawLocation.hash || ''}`
8388
if (!/^\/#/.test(location)) {
8489
window.location.replace(
8590
cleanPath(base + '/#' + location)
@@ -114,15 +119,15 @@ function getUrl (path) {
114119

115120
function pushHash (path) {
116121
if (supportsPushState) {
117-
pushState(getUrl(path))
122+
pushState(undefined, getUrl(path))
118123
} else {
119124
window.location.hash = path
120125
}
121126
}
122127

123128
function replaceHash (path) {
124129
if (supportsPushState) {
125-
replaceState(getUrl(path))
130+
replaceState(undefined, getUrl(path))
126131
} else {
127132
window.location.replace(getUrl(path))
128133
}

Diff for: src/history/html5.js

+18-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import type Router from '../index'
44
import { History } from './base'
55
import { cleanPath } from '../util/path'
6+
import { resolveQuery } from '../util/query'
67
import { START } from '../util/route'
78
import { setupScroll, handleScroll } from '../util/scroll'
89
import { pushState, replaceState, supportsPushState } from '../util/push-state'
@@ -44,7 +45,8 @@ export class HTML5History extends History {
4445
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
4546
const { current: fromRoute } = this
4647
this.transitionTo(location, route => {
47-
pushState(cleanPath(this.base + route.fullPath))
48+
const state = typeof location === 'string' ? undefined : location.state
49+
pushState(state, cleanPath(this.base + route.fullPath))
4850
handleScroll(this.router, route, fromRoute, false)
4951
onComplete && onComplete(route)
5052
}, onAbort)
@@ -53,7 +55,11 @@ export class HTML5History extends History {
5355
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
5456
const { current: fromRoute } = this
5557
this.transitionTo(location, route => {
56-
replaceState(cleanPath(this.base + route.fullPath))
58+
const state = typeof location === 'string' ? undefined : location.state
59+
replaceState(
60+
state,
61+
cleanPath(this.base + route.fullPath),
62+
)
5763
handleScroll(this.router, route, fromRoute, false)
5864
onComplete && onComplete(route)
5965
}, onAbort)
@@ -62,19 +68,25 @@ export class HTML5History extends History {
6268
ensureURL (push?: boolean) {
6369
if (getLocation(this.base) !== this.current.fullPath) {
6470
const current = cleanPath(this.base + this.current.fullPath)
65-
push ? pushState(current) : replaceState(current)
71+
if (push) pushState(this.current.state, current)
72+
else replaceState(this.current.state, current)
6673
}
6774
}
6875

69-
getCurrentLocation (): string {
76+
getCurrentLocation (): Location {
7077
return getLocation(this.base)
7178
}
7279
}
7380

74-
export function getLocation (base: string): string {
81+
export function getLocation (base: string): Location {
7582
let path = decodeURI(window.location.pathname)
7683
if (base && path.indexOf(base) === 0) {
7784
path = path.slice(base.length)
7885
}
79-
return (path || '/') + window.location.search + window.location.hash
86+
return {
87+
path: (path || '/'),
88+
query: resolveQuery((window.location.search: string)),
89+
hash: (window.location.hash: string),
90+
state: window.history.state ? window.history.state.state : undefined
91+
}
8092
}

Diff for: src/util/deepEqual.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const hasOwn = Object.prototype.hasOwnProperty
2+
3+
function is (x, y) {
4+
if (x === y) {
5+
// is(-0, +0) === false
6+
return x !== 0 || y !== 0 || 1 / x === 1 / y
7+
} else {
8+
/* eslint-disable no-self-compare */
9+
return x !== x && y !== y // is(NaN, NaN) === true
10+
/* eslint-enable no-self-compare */
11+
}
12+
}
13+
14+
// deep equal, with guard against circular references
15+
const guardedDeepEqual = (objA, objB, bufferA, bufferB) => {
16+
if (is(objA, objB)) return true
17+
18+
if (
19+
typeof objA !== 'object' ||
20+
objA === null ||
21+
typeof objB !== 'object' ||
22+
objB === null
23+
) {
24+
return false
25+
}
26+
const seenA = bufferA.indexOf(objA) !== -1
27+
const seenB = bufferB.indexOf(objB) !== -1
28+
29+
if (seenA || seenB) return seenA && seenB
30+
31+
bufferA.push(objA)
32+
bufferB.push(objB)
33+
34+
const keysA = Object.keys(objA)
35+
const keysB = Object.keys(objB)
36+
37+
if (keysA.length !== keysB.length) return false
38+
39+
for (let i = 0; i < keysA.length; i += 1) {
40+
if (!hasOwn.call(objB, keysA[i])) return false
41+
const valA = objA[keysA[i]]
42+
const valB = objB[keysA[i]]
43+
if (
44+
typeof valA === 'object' &&
45+
valA &&
46+
typeof valB === 'object' &&
47+
valB &&
48+
!guardedDeepEqual(valA, valB, bufferA, bufferB)
49+
) {
50+
return false
51+
} else if (!is(valA, valB)) {
52+
return false
53+
}
54+
}
55+
56+
return true
57+
}
58+
59+
export default (objA, objB) => guardedDeepEqual(objA, objB, [], [])

Diff for: src/util/location.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export function normalizeLocation (
5757
_normalized: true,
5858
path,
5959
query,
60-
hash
60+
hash,
61+
state: next.state
6162
}
6263
}

Diff for: src/util/push-state.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,23 @@ export function setStateKey (key: string) {
3737
_key = key
3838
}
3939

40-
export function pushState (url?: string, replace?: boolean) {
40+
export function pushState (state: mixed, url: string, replace?: boolean) {
4141
saveScrollPosition()
4242
// try...catch the pushState call to get around Safari
4343
// DOM Exception 18 where it limits to 100 pushState calls
4444
const history = window.history
4545
try {
4646
if (replace) {
47-
history.replaceState({ key: _key }, '', url)
47+
history.replaceState({ key: _key, state }, '', url)
4848
} else {
4949
_key = genKey()
50-
history.pushState({ key: _key }, '', url)
50+
history.pushState({ key: _key, state }, '', url)
5151
}
5252
} catch (e) {
5353
window.location[replace ? 'replace' : 'assign'](url)
5454
}
5555
}
5656

57-
export function replaceState (url?: string) {
58-
pushState(url, true)
57+
export function replaceState (state: mixed, url: string) {
58+
pushState(state, url, true)
5959
}

Diff for: src/util/route.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import type VueRouter from '../index'
44
import { stringifyQuery } from './query'
5+
import deepEqual from './deepEqual'
56

67
const trailingSlashRE = /\/?$/
78

@@ -23,6 +24,7 @@ export function createRoute (
2324
meta: (record && record.meta) || {},
2425
path: location.path || '/',
2526
hash: location.hash || '',
27+
state: location.state,
2628
query,
2729
params: location.params || {},
2830
fullPath: getFullPath(location, stringifyQuery),
@@ -79,14 +81,16 @@ export function isSameRoute (a: Route, b: ?Route): boolean {
7981
return (
8082
a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
8183
a.hash === b.hash &&
82-
isObjectEqual(a.query, b.query)
84+
isObjectEqual(a.query, b.query) &&
85+
deepEqual(a.state, b.state)
8386
)
8487
} else if (a.name && b.name) {
8588
return (
8689
a.name === b.name &&
8790
a.hash === b.hash &&
8891
isObjectEqual(a.query, b.query) &&
89-
isObjectEqual(a.params, b.params)
92+
isObjectEqual(a.params, b.params) &&
93+
deepEqual(a.state, b.state)
9094
)
9195
} else {
9296
return false

0 commit comments

Comments
 (0)