Skip to content

Commit ba23ce6

Browse files
agundermannmjackson
authored andcommitted
Rewrite internal behavior of Histories
1 parent cd49f2e commit ba23ce6

File tree

6 files changed

+210
-144
lines changed

6 files changed

+210
-144
lines changed

modules/BrowserHistory.js

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,6 @@ import DOMHistory from './DOMHistory';
22
import { getWindowPath, supportsHistory } from './DOMUtils';
33
import NavigationTypes from './NavigationTypes';
44

5-
function updateCurrentState(extraState) {
6-
var state = window.history.state;
7-
8-
if (state)
9-
window.history.replaceState(Object.assign(state, extraState), '');
10-
}
11-
125
/**
136
* A history implementation for DOM environments that support the
147
* HTML5 history API (pushState, replaceState, and the popstate event).
@@ -42,16 +35,24 @@ class BrowserHistory extends DOMHistory {
4235
}
4336

4437
setup() {
45-
if (this.location == null)
46-
this._updateLocation();
38+
if (this.location != null)
39+
return;
40+
41+
var path = getWindowPath();
42+
var key = null;
43+
if (this.isSupported && window.history.state)
44+
key = window.history.state.key;
45+
46+
super.setup(path, {key});
4747
}
4848

4949
handlePopState(event) {
5050
if (event.state === undefined)
5151
return; // Ignore extraneous popstate events in WebKit.
5252

53-
this._updateLocation(NavigationTypes.POP);
54-
this._notifyChange();
53+
var path = getWindowPath();
54+
var key = event.state && event.state.key;
55+
this.handlePop(path, {key});
5556
}
5657

5758
addChangeListener(listener) {
@@ -79,31 +80,24 @@ class BrowserHistory extends DOMHistory {
7980
}
8081

8182
// http://www.w3.org/TR/2011/WD-html5-20110113/history.html#dom-history-pushstate
82-
pushState(state, path) {
83+
push(path, key) {
8384
if (this.isSupported) {
84-
updateCurrentState(this.getScrollPosition());
85-
86-
state = this._createState(state);
87-
85+
var state = { key };
8886
window.history.pushState(state, '', path);
89-
this.location = this.createLocation(path, state, NavigationTypes.PUSH);
90-
this._notifyChange();
91-
} else {
92-
window.location = path;
87+
return state;
9388
}
89+
90+
window.location = path;
9491
}
9592

9693
// http://www.w3.org/TR/2011/WD-html5-20110113/history.html#dom-history-replacestate
97-
replaceState(state, path) {
94+
replace(path, key) {
9895
if (this.isSupported) {
99-
state = this._createState(state);
100-
96+
var state = { key };
10197
window.history.replaceState(state, '', path);
102-
this.location = this.createLocation(path, state, NavigationTypes.REPLACE);
103-
this._notifyChange();
104-
} else {
105-
window.location.replace(path);
98+
return state;
10699
}
100+
window.location.replace(path);
107101
}
108102
}
109103

modules/DOMHistory.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,36 @@ class DOMHistory extends History {
1717
window.history.go(n);
1818
}
1919

20+
saveState(key, state) {
21+
window.sessionStorage.setItem(key, JSON.stringify(state));
22+
}
23+
24+
readState(key) {
25+
var json = window.sessionStorage.getItem(key);
26+
27+
if (json) {
28+
try {
29+
return JSON.parse(json);
30+
} catch (error) {
31+
// Ignore invalid JSON in session storage.
32+
}
33+
}
34+
35+
return null;
36+
}
37+
38+
pushState(state, path) {
39+
var location = this.location;
40+
if (location && location.state && location.state.key) {
41+
var key = location.state.key;
42+
var curState = this.readState(key);
43+
var scroll = this.getScrollPosition();
44+
this.saveState(key, {...curState, ...scroll});
45+
}
46+
47+
super.pushState(state, path);
48+
}
49+
2050
}
2151

2252
export default DOMHistory;

modules/HashHistory.js

Lines changed: 32 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import warning from 'warning';
22
import DOMHistory from './DOMHistory';
3-
import NavigationTypes from './NavigationTypes';
43
import { getHashPath, replaceHashPath } from './DOMUtils';
54
import { isAbsolutePath } from './URLUtils';
65

@@ -26,34 +25,6 @@ function getQueryStringValueFromPath(path, key) {
2625
return match && match[1];
2726
}
2827

29-
function saveState(path, queryKey, state) {
30-
window.sessionStorage.setItem(state.key, JSON.stringify(state));
31-
return addQueryStringValueToPath(path, queryKey, state.key);
32-
}
33-
34-
function readState(path, queryKey) {
35-
var sessionKey = getQueryStringValueFromPath(path, queryKey);
36-
var json = sessionKey && window.sessionStorage.getItem(sessionKey);
37-
38-
if (json) {
39-
try {
40-
return JSON.parse(json);
41-
} catch (error) {
42-
// Ignore invalid JSON in session storage.
43-
}
44-
}
45-
46-
return null;
47-
}
48-
49-
function updateCurrentState(queryKey, extraState) {
50-
var path = getHashPath();
51-
var state = readState(path, queryKey);
52-
53-
if (state)
54-
saveState(path, queryKey, Object.assign(state, extraState));
55-
}
56-
5728
/**
5829
* A history implementation for DOM environments that uses window.location.hash
5930
* to store the current path. This is essentially a hack for older browsers that
@@ -79,17 +50,15 @@ class HashHistory extends DOMHistory {
7950
this.queryKey = this.queryKey ? DefaultQueryKey : null;
8051
}
8152

82-
_updateLocation(navigationType) {
83-
var path = getHashPath();
84-
var state = this.queryKey ? readState(path, this.queryKey) : null;
85-
this.location = this.createLocation(path, state, navigationType);
86-
}
87-
8853
setup() {
89-
if (this.location == null) {
90-
ensureSlash();
91-
this._updateLocation();
92-
}
54+
if (this.location != null)
55+
return;
56+
57+
ensureSlash();
58+
59+
var path = getHashPath();
60+
var key = getQueryStringValueFromPath(path, this.queryKey);
61+
super.setup(path, { key });
9362
}
9463

9564
handleHashChange() {
@@ -99,8 +68,9 @@ class HashHistory extends DOMHistory {
9968
if (this._ignoreNextHashChange) {
10069
this._ignoreNextHashChange = false;
10170
} else {
102-
this._updateLocation(NavigationTypes.POP);
103-
this._notifyChange();
71+
var path = getHashPath();
72+
var key = getQueryStringValueFromPath(path, this.queryKey);
73+
this.handlePop(path, { key });
10474
}
10575
}
10676

@@ -128,40 +98,38 @@ class HashHistory extends DOMHistory {
12898
}
12999
}
130100

131-
pushState(state, path) {
132-
warning(
133-
this.queryKey || state == null,
134-
'HashHistory needs a queryKey in order to persist state'
135-
);
136-
101+
push(path, key) {
102+
var actualPath = path;
137103
if (this.queryKey)
138-
updateCurrentState(this.queryKey, this.getScrollPosition());
104+
actualPath = addQueryStringValueToPath(path, this.queryKey, key);
139105

140-
state = this._createState(state);
141106

142-
if (this.queryKey)
143-
path = saveState(path, this.queryKey, state);
144-
145-
this._ignoreNextHashChange = true;
146-
window.location.hash = path;
147-
148-
this.location = this.createLocation(path, state, NavigationTypes.PUSH);
107+
if (actualPath === getHashPath()) {
108+
warning(
109+
false,
110+
'HashHistory can not push the current path'
111+
);
112+
} else {
113+
this._ignoreNextHashChange = true;
114+
window.location.hash = actualPath;
115+
}
149116

150-
this._notifyChange();
117+
return { key: this.queryKey && key };
151118
}
152119

153-
replaceState(state, path) {
154-
state = this._createState(state);
155120

121+
replace(path, key) {
122+
var actualPath = path;
156123
if (this.queryKey)
157-
path = saveState(path, this.queryKey, state);
124+
actualPath = addQueryStringValueToPath(path, this.queryKey, key);
125+
158126

159-
this._ignoreNextHashChange = true;
160-
replaceHashPath(path);
127+
if (actualPath !== getHashPath())
128+
this._ignoreNextHashChange = true;
161129

162-
this.location = this.createLocation(path, state, NavigationTypes.REPLACE);
130+
replaceHashPath(actualPath);
163131

164-
this._notifyChange();
132+
return { key: this.queryKey && key };
165133
}
166134

167135
makeHref(path) {

modules/History.js

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import invariant from 'invariant';
2+
import warning from 'warning';
3+
import NavigationTypes from './NavigationTypes';
24
import { getPathname, getQueryString, parseQueryString } from './URLUtils';
35
import Location from './Location';
46

5-
var RequiredHistorySubclassMethods = [ 'pushState', 'replaceState', 'go' ];
6-
7-
function createRandomKey() {
8-
return Math.random().toString(36).substr(2);
9-
}
7+
var RequiredHistorySubclassMethods = [ 'push', 'replace', 'go' ];
108

119
/**
1210
* A history interface that normalizes the differences across
@@ -30,7 +28,6 @@ class History {
3028

3129
this.parseQueryString = options.parseQueryString || parseQueryString;
3230
this.changeListeners = [];
33-
this.location = null;
3431
}
3532

3633
_notifyChange() {
@@ -48,28 +45,97 @@ class History {
4845
});
4946
}
5047

51-
back() {
52-
this.go(-1);
48+
setup(path, entry = {}) {
49+
if (this.location)
50+
return;
51+
52+
if (!entry.key)
53+
entry = this.replace(path, this.createRandomKey());
54+
55+
var state = null;
56+
if (typeof this.readState === 'function')
57+
state = this.readState(entry.key);
58+
59+
this.location = this._createLocation(path, state, entry);
5360
}
5461

55-
forward() {
56-
this.go(1);
62+
handlePop(path, entry = {}) {
63+
var state = null;
64+
if (entry.key && typeof this.readState === 'function')
65+
state = this.readState(entry.key);
66+
67+
this.location = this._createLocation(path, state, entry, NavigationTypes.POP);
68+
this._notifyChange();
69+
}
70+
71+
createRandomKey() {
72+
return Math.random().toString(36).substr(2);
5773
}
5874

59-
_createState(state) {
60-
state = state || {};
75+
_saveNewState(state) {
76+
var key = this.createRandomKey();
77+
78+
if (state != null) {
79+
invariant(
80+
typeof this.saveState === 'function',
81+
'%s needs a saveState method in order to store state',
82+
this.constructor.name
83+
);
6184

62-
if (!state.key)
63-
state.key = createRandomKey();
85+
this.saveState(key, state);
86+
}
87+
88+
return key;
89+
}
6490

65-
return state;
91+
pushState(state, path) {
92+
var key = this._saveNewState(state);
93+
94+
var entry = null;
95+
if (this.location && this.location.path === path) {
96+
entry = this.replace(path, key) || {};
97+
} else {
98+
entry = this.push(path, key) || {};
99+
}
100+
101+
warning(
102+
entry.key || state == null,
103+
'%s does not support storing state',
104+
this.constructor.name
105+
);
106+
107+
this.location = this._createLocation(path, state, entry, NavigationTypes.PUSH);
108+
this._notifyChange();
109+
}
110+
111+
replaceState(state, path) {
112+
var key = this._saveNewState(state);
113+
114+
var entry = this.replace(path, key) || {};
115+
116+
warning(
117+
entry.key || state == null,
118+
'%s does not support storing state',
119+
this.constructor.name
120+
);
121+
122+
this.location = this._createLocation(path, state, entry, NavigationTypes.REPLACE);
123+
this._notifyChange();
124+
}
125+
126+
back() {
127+
this.go(-1);
128+
}
129+
130+
forward() {
131+
this.go(1);
66132
}
67133

68-
createLocation(path, state, navigationType) {
134+
_createLocation(path, state, entry, navigationType) {
69135
var pathname = getPathname(path);
70136
var queryString = getQueryString(path);
71137
var query = queryString ? this.parseQueryString(queryString) : null;
72-
return new Location(pathname, query, state, navigationType);
138+
return new Location(path, pathname, query, {...state, ...entry}, navigationType);
73139
}
74140

75141
}

0 commit comments

Comments
 (0)