Skip to content

Commit 0e7a182

Browse files
seanadkinsonmjackson
authored andcommitted
[added] pluggable history implementations
closes #166
1 parent f51a7c6 commit 0e7a182

11 files changed

+318
-140
lines changed

Location.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./modules/helpers/Location');

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ exports.Link = require('./Link');
44
exports.Redirect = require('./Redirect');
55
exports.Route = require('./Route');
66
exports.Routes = require('./Routes');
7+
exports.Location = require('./Location');
78
exports.goBack = require('./goBack');
89
exports.replaceWith = require('./replaceWith');
910
exports.transitionTo = require('./transitionTo');

modules/components/Routes.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var mergeProperties = require('../helpers/mergeProperties');
55
var goBack = require('../helpers/goBack');
66
var replaceWith = require('../helpers/replaceWith');
77
var transitionTo = require('../helpers/transitionTo');
8+
var Location = require('../helpers/Location');
89
var Route = require('../components/Route');
910
var Path = require('../helpers/Path');
1011
var ActiveStore = require('../stores/ActiveStore');
@@ -53,8 +54,15 @@ var Routes = React.createClass({
5354
},
5455

5556
propTypes: {
56-
location: React.PropTypes.oneOf([ 'hash', 'history' ]).isRequired,
57-
preserveScrollPosition: React.PropTypes.bool
57+
preserveScrollPosition: React.PropTypes.bool,
58+
location: function(props, propName, componentName) {
59+
var location = props[propName];
60+
if (!Location[location]) {
61+
return new Error('No matching location: "' + location +
62+
'". Must be one of: ' + Object.keys(Location) +
63+
'. See: ' + componentName);
64+
}
65+
}
5866
},
5967

6068
getDefaultProps: function () {

modules/helpers/DisabledLocation.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
var getWindowPath = require('./getWindowPath');
2+
3+
/**
4+
* Location handler that doesn't actually do any location handling. Instead, requests
5+
* are sent to the server as normal.
6+
*/
7+
var DisabledLocation = {
8+
9+
type: 'disabled',
10+
11+
init: function() { },
12+
13+
destroy: function() { },
14+
15+
getCurrentPath: function() {
16+
return getWindowPath();
17+
},
18+
19+
push: function(path) {
20+
window.location = path;
21+
},
22+
23+
replace: function(path) {
24+
window.location.replace(path);
25+
},
26+
27+
back: function() {
28+
window.history.back();
29+
}
30+
31+
};
32+
33+
module.exports = DisabledLocation;
34+

modules/helpers/HashLocation.js

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
var getWindowPath = require('./getWindowPath');
2+
3+
function getWindowChangeEvent() {
4+
return window.addEventListener ? 'hashchange' : 'onhashchange';
5+
}
6+
7+
/**
8+
* Location handler which uses the `window.location.hash` to push and replace URLs
9+
*/
10+
var HashLocation = {
11+
12+
type: 'hash',
13+
onChange: null,
14+
15+
init: function(onChange) {
16+
var changeEvent = getWindowChangeEvent();
17+
18+
if (window.location.hash === '') {
19+
this.replace('/');
20+
}
21+
22+
if (window.addEventListener) {
23+
window.addEventListener(changeEvent, onChange, false);
24+
} else {
25+
window.attachEvent(changeEvent, onChange);
26+
}
27+
28+
this.onChange = onChange;
29+
onChange();
30+
},
31+
32+
destroy: function() {
33+
var changeEvent = getWindowChangeEvent();
34+
if (window.removeEventListener) {
35+
window.removeEventListener(changeEvent, this.onChange, false);
36+
} else {
37+
window.detachEvent(changeEvent, this.onChange);
38+
}
39+
},
40+
41+
getCurrentPath: function() {
42+
return window.location.hash.substr(1);
43+
},
44+
45+
push: function(path) {
46+
window.location.hash = path;
47+
},
48+
49+
replace: function(path) {
50+
window.location.replace(getWindowPath() + '#' + path);
51+
},
52+
53+
back: function() {
54+
window.history.back();
55+
}
56+
57+
};
58+
59+
module.exports = HashLocation;
60+

modules/helpers/HistoryLocation.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
var getWindowPath = require('./getWindowPath');
2+
3+
/**
4+
* Location handler which uses the HTML5 History API to push and replace URLs
5+
*/
6+
var HistoryLocation = {
7+
8+
type: 'history',
9+
onChange: null,
10+
11+
init: function(onChange) {
12+
if (window.addEventListener) {
13+
window.addEventListener('popstate', onChange, false);
14+
} else {
15+
window.attachEvent('popstate', onChange);
16+
}
17+
this.onChange = onChange;
18+
onChange();
19+
},
20+
21+
destroy: function() {
22+
if (window.removeEventListener) {
23+
window.removeEventListener('popstate', this.onChange, false);
24+
} else {
25+
window.detachEvent('popstate', this.onChange);
26+
}
27+
},
28+
29+
getCurrentPath: function() {
30+
return getWindowPath();
31+
},
32+
33+
push: function(path) {
34+
window.history.pushState({ path: path }, '', path);
35+
this.onChange();
36+
},
37+
38+
replace: function(path) {
39+
window.history.replaceState({ path: path }, '', path);
40+
this.onChange();
41+
},
42+
43+
back: function() {
44+
window.history.back();
45+
}
46+
47+
};
48+
49+
module.exports = HistoryLocation;
50+

modules/helpers/Location.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Map of location type to handler.
3+
* @see Routes#location
4+
*/
5+
module.exports = {
6+
hash: require('./HashLocation'),
7+
history: require('./HistoryLocation'),
8+
disabled: require('./DisabledLocation'),
9+
memory: require('./MemoryLocation')
10+
};

modules/helpers/MemoryLocation.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
var invariant = require('react/lib/invariant');
2+
3+
var _lastPath;
4+
var _currentPath = '/';
5+
6+
/**
7+
* Fake location handler that can be used outside the scope of the browser. It
8+
* tracks the current and previous path, as given to #push() and #replace().
9+
*/
10+
var MemoryLocation = {
11+
12+
type: 'memory',
13+
onChange: null,
14+
15+
init: function(onChange) {
16+
this.onChange = onChange;
17+
},
18+
19+
destroy: function() {
20+
this.onChange = null;
21+
_lastPath = null;
22+
_currentPath = '/';
23+
},
24+
25+
getCurrentPath: function() {
26+
return _currentPath;
27+
},
28+
29+
push: function(path) {
30+
_lastPath = _currentPath;
31+
_currentPath = path;
32+
this.onChange();
33+
},
34+
35+
replace: function(path) {
36+
_currentPath = path;
37+
this.onChange();
38+
},
39+
40+
back: function() {
41+
invariant(
42+
_lastPath,
43+
'You cannot make the URL store go back more than once when it does not use the DOM'
44+
);
45+
46+
_currentPath = _lastPath;
47+
_lastPath = null;
48+
this.onChange();
49+
}
50+
};
51+
52+
module.exports = MemoryLocation;
53+

modules/helpers/getWindowPath.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Returns the current URL path from `window.location`, including query string
3+
*/
4+
function getWindowPath() {
5+
return window.location.pathname + window.location.search;
6+
}
7+
8+
module.exports = getWindowPath;
9+

0 commit comments

Comments
 (0)