Skip to content

Commit 11cdab3

Browse files
committed
Add location.state
1 parent ddd9af8 commit 11cdab3

File tree

11 files changed

+208
-142
lines changed

11 files changed

+208
-142
lines changed

modules/BrowserHistory.js

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import DOMHistory from './DOMHistory';
2-
import { getWindowPath, supportsHistory } from './DOMUtils';
2+
import { getWindowPath, getWindowScrollPosition, supportsHistory } from './DOMUtils';
33
import NavigationTypes from './NavigationTypes';
4-
5-
function createRandomKey() {
6-
return Math.random().toString(36).substr(2);
7-
}
4+
import Location from './Location';
85

96
/**
107
* A history implementation for DOM environments that support the
@@ -18,26 +15,31 @@ function createRandomKey() {
1815
*/
1916
export class BrowserHistory extends DOMHistory {
2017

21-
constructor(getScrollPosition) {
22-
super(getScrollPosition);
18+
constructor(getScrollPosition=getWindowScrollPosition) {
19+
super();
20+
this.getScrollPosition = getScrollPosition;
2321
this.handlePopState = this.handlePopState.bind(this);
2422
this.isSupported = supportsHistory();
2523
}
2624

2725
_updateLocation(navigationType) {
28-
var key = null;
26+
var state = null;
2927

3028
if (this.isSupported) {
31-
var state = window.history.state;
32-
key = state && state.key;
29+
state = window.history.state || {};
3330

34-
if (!key) {
35-
key = createRandomKey();
36-
window.history.replaceState({ key }, '');
31+
if (!state.key) {
32+
state.key = createRandomKey();
33+
window.history.replaceState(state, '');
3734
}
3835
}
3936

40-
this.location = this._createLocation(getWindowPath(), key, navigationType);
37+
this.location = new Location(state, getWindowPath(), navigationType);
38+
}
39+
40+
setup() {
41+
if (this.location == null)
42+
this._updateLocation();
4143
}
4244

4345
handlePopState(event) {
@@ -72,31 +74,33 @@ export class BrowserHistory extends DOMHistory {
7274
}
7375
}
7476

75-
setup() {
76-
if (this.location == null)
77-
this._updateLocation();
78-
}
79-
8077
// http://www.w3.org/TR/2011/WD-html5-20110113/history.html#dom-history-pushstate
81-
push(path) {
78+
pushState(state, path) {
8279
if (this.isSupported) {
83-
this._recordScrollPosition();
80+
var currentState = window.history.state;
8481

85-
var key = createRandomKey();
86-
window.history.pushState({ key }, '', path);
87-
this.location = this._createLocation(path, key, NavigationTypes.PUSH);
82+
if (currentState) {
83+
Object.assign(currentState, this.getScrollPosition());
84+
window.history.replaceState(currentState, '');
85+
}
86+
87+
state = this._createState(state);
88+
89+
window.history.pushState(state, '', path);
90+
this.location = new Location(state, path, NavigationTypes.PUSH);
8891
this._notifyChange();
8992
} else {
9093
window.location = path;
9194
}
9295
}
9396

9497
// http://www.w3.org/TR/2011/WD-html5-20110113/history.html#dom-history-replacestate
95-
replace(path) {
98+
replaceState(state, path) {
9699
if (this.isSupported) {
97-
var key = createRandomKey();
98-
window.history.replaceState({ key }, '', path);
99-
this.location = this._createLocation(path, key, NavigationTypes.REPLACE);
100+
state = this._createState(state);
101+
102+
window.history.replaceState(state, '', path);
103+
this.location = new Location(state, path, NavigationTypes.REPLACE);
100104
this._notifyChange();
101105
} else {
102106
window.location.replace(path);

modules/ChangeEmitter.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
class ChangeEmitter {
2+
3+
constructor() {
4+
this.changeListeners = [];
5+
}
6+
7+
_notifyChange() {
8+
for (var i = 0, len = this.changeListeners.length; i < len; ++i)
9+
this.changeListeners[i].call(this);
10+
}
11+
12+
addChangeListener(listener) {
13+
this.changeListeners.push(listener);
14+
}
15+
16+
removeChangeListener(listener) {
17+
this.changeListeners = this.changeListeners.filter(function (li) {
18+
return li !== listener;
19+
});
20+
}
21+
22+
}
23+
24+
export default ChangeEmitter;

modules/DOMHistory.js

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
11
import History from './History';
2-
import { getWindowScrollPosition } from './DOMUtils';
3-
import Location from './Location';
42

53
/**
64
* A history interface that assumes a DOM environment.
75
*/
8-
export class DOMHistory extends History {
9-
10-
constructor(getScrollPosition=getWindowScrollPosition) {
11-
super();
12-
this.getScrollPosition = getScrollPosition;
13-
this.scrollHistory = {};
14-
}
6+
class DOMHistory extends History {
157

168
go(n) {
179
if (n === 0)
@@ -20,20 +12,6 @@ export class DOMHistory extends History {
2012
window.history.go(n);
2113
}
2214

23-
_createLocation(path, key, navigationType) {
24-
var scrollKey = key || path;
25-
var scrollPosition = this.scrollHistory[scrollKey];
26-
27-
return new Location(path, key, navigationType, scrollPosition);
28-
}
29-
30-
_recordScrollPosition() {
31-
var location = this.location;
32-
var scrollKey = location.key || location.path;
33-
34-
this.scrollHistory[scrollKey] = this.getScrollPosition();
35-
}
36-
3715
}
3816

3917
export default DOMHistory;

modules/DOMUtils.js

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
1-
function getHashPath() {
1+
export var canUseDOM = !!(
2+
(typeof window !== 'undefined' &&
3+
window.document && window.document.createElement)
4+
);
5+
6+
export function getHashPath() {
27
return decodeURI(
38
// We can't use window.location.hash here because it's not
49
// consistent across browsers - Firefox will pre-decode it!
510
window.location.href.split('#')[1] || ''
611
);
712
}
813

9-
function replaceHashPath(path) {
14+
export function replaceHashPath(path) {
1015
window.location.replace(
1116
window.location.pathname + window.location.search + '#' + path
1217
);
1318
}
1419

15-
function getWindowPath() {
20+
export function getWindowPath() {
1621
return decodeURI(
1722
window.location.pathname + window.location.search
1823
);
1924
}
2025

21-
function getWindowScrollPosition() {
26+
export function getWindowScrollPosition() {
2227
return {
23-
x: window.pageXOffset || document.documentElement.scrollLeft,
24-
y: window.pageYOffset || document.documentElement.scrollTop
28+
scrollX: window.pageXOffset || document.documentElement.scrollLeft,
29+
scrollY: window.pageYOffset || document.documentElement.scrollTop
2530
};
2631
}
2732

@@ -31,18 +36,10 @@ function getWindowScrollPosition() {
3136
* https://github.com/Modernizr/Modernizr/blob/master/feature-detects/history.js
3237
* changed to avoid false negatives for Windows Phones: https://github.com/rackt/react-router/issues/586
3338
*/
34-
function supportsHistory() {
39+
export function supportsHistory() {
3540
var ua = navigator.userAgent;
3641
if ((ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) && ua.indexOf('Mobile Safari') !== -1 && ua.indexOf('Chrome') === -1 && ua.indexOf('Windows Phone') === -1) {
3742
return false;
3843
}
3944
return window.history && 'pushState' in window.history;
4045
}
41-
42-
module.exports = {
43-
getHashPath,
44-
replaceHashPath,
45-
getWindowPath,
46-
getWindowScrollPosition,
47-
supportsHistory
48-
};

modules/HashHistory.js

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import DOMHistory from './DOMHistory';
22
import NavigationTypes from './NavigationTypes';
3-
import { getHashPath, replaceHashPath } from './DOMUtils';
3+
import { getHashPath, getWindowScrollPosition, replaceHashPath } from './DOMUtils';
44
import { isAbsolutePath } from './URLUtils';
5+
import Location from './Location';
56

67
function ensureSlash() {
78
var path = getHashPath();
@@ -21,13 +22,29 @@ function ensureSlash() {
2122
*/
2223
export class HashHistory extends DOMHistory {
2324

24-
constructor(getScrollPosition) {
25-
super(getScrollPosition);
25+
constructor(getScrollPosition=getWindowScrollPosition) {
26+
super();
27+
this.getScrollPosition = getScrollPosition;
2628
this.handleHashChange = this.handleHashChange.bind(this);
29+
30+
// We keep known states in memory so we don't have to keep a key
31+
// in the URL. We could have persistence if we did, but we can always
32+
// add that later if people need it. I suspect that at this point
33+
// most users who need state are already using BrowserHistory.
34+
this.states = {};
2735
}
2836

2937
_updateLocation(navigationType) {
30-
this.location = this._createLocation(getHashPath(), null, navigationType);
38+
var path = getHashPath();
39+
var state = this.states[path] || null;
40+
this.location = new Location(state, path, navigationType);
41+
}
42+
43+
setup() {
44+
if (this.location == null) {
45+
ensureSlash();
46+
this._updateLocation();
47+
}
3148
}
3249

3350
handleHashChange() {
@@ -61,30 +78,41 @@ export class HashHistory extends DOMHistory {
6178
}
6279
}
6380

64-
setup() {
65-
if (this.location == null) {
66-
ensureSlash();
67-
this._updateLocation();
81+
pushState(state, path) {
82+
var location = this.location;
83+
var currentState = location && this.states[location.path];
84+
85+
if (currentState) {
86+
Object.assign(currentState, this.getScrollPosition());
87+
this.states[location.path] = currentState;
6888
}
69-
}
7089

71-
push(path) {
72-
this._recordScrollPosition();
90+
state = this._createState(state);
91+
this.states[path] = state;
7392

7493
this._ignoreHashChange = true;
7594
window.location.hash = path;
7695
this._ignoreHashChange = false;
7796

78-
this.location = this._createLocation(path, null, NavigationTypes.PUSH);
97+
this.location = new Location(state, path, NavigationTypes.PUSH);
98+
7999
this._notifyChange();
80100
}
81101

82-
replace(path) {
102+
replaceState(state, path) {
103+
state = this._createState(state);
104+
105+
var location = this.location;
106+
107+
if (location && this.states[location.path])
108+
this.states[location.path] = state;
109+
83110
this._ignoreHashChange = true;
84111
replaceHashPath(path);
85112
this._ignoreHashChange = false;
86113

87-
this.location = this._createLocation(path, null, NavigationTypes.REPLACE);
114+
this.location = new Location(state, path, NavigationTypes.REPLACE);
115+
88116
this._notifyChange();
89117
}
90118

modules/History.js

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
import invariant from 'invariant';
2+
import ChangeEmitter from './ChangeEmitter';
23

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

510
/**
611
* A history interface that normalizes the differences across
712
* various environments and implementations. Requires concrete
813
* subclasses to implement the following methods:
914
*
10-
* - push(path)
11-
* - replace(path)
15+
* - pushState(state, path)
16+
* - replaceState(state, path)
1217
* - go(n)
1318
*/
14-
export class History {
19+
class History extends ChangeEmitter {
1520

1621
constructor() {
22+
super();
23+
1724
RequiredSubclassMethods.forEach(function (method) {
1825
invariant(
1926
typeof this[method] === 'function',
@@ -22,25 +29,9 @@ export class History {
2229
);
2330
}, this);
2431

25-
this.changeListeners = [];
2632
this.location = null;
2733
}
2834

29-
_notifyChange() {
30-
for (var i = 0, len = this.changeListeners.length; i < len; ++i)
31-
this.changeListeners[i].call(this);
32-
}
33-
34-
addChangeListener(listener) {
35-
this.changeListeners.push(listener);
36-
}
37-
38-
removeChangeListener(listener) {
39-
this.changeListeners = this.changeListeners.filter(function (li) {
40-
return li !== listener;
41-
});
42-
}
43-
4435
back() {
4536
this.go(-1);
4637
}
@@ -49,6 +40,15 @@ export class History {
4940
this.go(1);
5041
}
5142

43+
_createState(state) {
44+
state = state || {};
45+
46+
if (!state.key)
47+
state.key = createRandomKey();
48+
49+
return state;
50+
}
51+
5252
}
5353

5454
export default History;

0 commit comments

Comments
 (0)