Skip to content

Commit

Permalink
[added] pluggable history implementations
Browse files Browse the repository at this point in the history
closes #166
  • Loading branch information
seanadkinson authored and mjackson committed Aug 13, 2014
1 parent f51a7c6 commit 0e7a182
Show file tree
Hide file tree
Showing 11 changed files with 318 additions and 140 deletions.
1 change: 1 addition & 0 deletions Location.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./modules/helpers/Location');
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ exports.Link = require('./Link');
exports.Redirect = require('./Redirect');
exports.Route = require('./Route');
exports.Routes = require('./Routes');
exports.Location = require('./Location');
exports.goBack = require('./goBack');
exports.replaceWith = require('./replaceWith');
exports.transitionTo = require('./transitionTo');
Expand Down
12 changes: 10 additions & 2 deletions modules/components/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var mergeProperties = require('../helpers/mergeProperties');
var goBack = require('../helpers/goBack');
var replaceWith = require('../helpers/replaceWith');
var transitionTo = require('../helpers/transitionTo');
var Location = require('../helpers/Location');
var Route = require('../components/Route');
var Path = require('../helpers/Path');
var ActiveStore = require('../stores/ActiveStore');
Expand Down Expand Up @@ -53,8 +54,15 @@ var Routes = React.createClass({
},

propTypes: {
location: React.PropTypes.oneOf([ 'hash', 'history' ]).isRequired,
preserveScrollPosition: React.PropTypes.bool
preserveScrollPosition: React.PropTypes.bool,
location: function(props, propName, componentName) {
var location = props[propName];
if (!Location[location]) {
return new Error('No matching location: "' + location +
'". Must be one of: ' + Object.keys(Location) +
'. See: ' + componentName);
}
}
},

getDefaultProps: function () {
Expand Down
34 changes: 34 additions & 0 deletions modules/helpers/DisabledLocation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
var getWindowPath = require('./getWindowPath');

/**
* Location handler that doesn't actually do any location handling. Instead, requests
* are sent to the server as normal.
*/
var DisabledLocation = {

type: 'disabled',

init: function() { },

destroy: function() { },

getCurrentPath: function() {
return getWindowPath();
},

push: function(path) {
window.location = path;
},

replace: function(path) {
window.location.replace(path);
},

back: function() {
window.history.back();
}

};

module.exports = DisabledLocation;

60 changes: 60 additions & 0 deletions modules/helpers/HashLocation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
var getWindowPath = require('./getWindowPath');

function getWindowChangeEvent() {
return window.addEventListener ? 'hashchange' : 'onhashchange';
}

/**
* Location handler which uses the `window.location.hash` to push and replace URLs
*/
var HashLocation = {

type: 'hash',
onChange: null,

init: function(onChange) {
var changeEvent = getWindowChangeEvent();

if (window.location.hash === '') {
this.replace('/');
}

if (window.addEventListener) {
window.addEventListener(changeEvent, onChange, false);
} else {
window.attachEvent(changeEvent, onChange);
}

this.onChange = onChange;
onChange();
},

destroy: function() {
var changeEvent = getWindowChangeEvent();
if (window.removeEventListener) {
window.removeEventListener(changeEvent, this.onChange, false);
} else {
window.detachEvent(changeEvent, this.onChange);
}
},

getCurrentPath: function() {
return window.location.hash.substr(1);
},

push: function(path) {
window.location.hash = path;
},

replace: function(path) {
window.location.replace(getWindowPath() + '#' + path);
},

back: function() {
window.history.back();
}

};

module.exports = HashLocation;

50 changes: 50 additions & 0 deletions modules/helpers/HistoryLocation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
var getWindowPath = require('./getWindowPath');

/**
* Location handler which uses the HTML5 History API to push and replace URLs
*/
var HistoryLocation = {

type: 'history',
onChange: null,

init: function(onChange) {
if (window.addEventListener) {
window.addEventListener('popstate', onChange, false);
} else {
window.attachEvent('popstate', onChange);
}
this.onChange = onChange;
onChange();
},

destroy: function() {
if (window.removeEventListener) {
window.removeEventListener('popstate', this.onChange, false);
} else {
window.detachEvent('popstate', this.onChange);
}
},

getCurrentPath: function() {
return getWindowPath();
},

push: function(path) {
window.history.pushState({ path: path }, '', path);
this.onChange();
},

replace: function(path) {
window.history.replaceState({ path: path }, '', path);
this.onChange();
},

back: function() {
window.history.back();
}

};

module.exports = HistoryLocation;

10 changes: 10 additions & 0 deletions modules/helpers/Location.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Map of location type to handler.
* @see Routes#location
*/
module.exports = {
hash: require('./HashLocation'),
history: require('./HistoryLocation'),
disabled: require('./DisabledLocation'),
memory: require('./MemoryLocation')
};
53 changes: 53 additions & 0 deletions modules/helpers/MemoryLocation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
var invariant = require('react/lib/invariant');

var _lastPath;
var _currentPath = '/';

/**
* Fake location handler that can be used outside the scope of the browser. It
* tracks the current and previous path, as given to #push() and #replace().
*/
var MemoryLocation = {

type: 'memory',
onChange: null,

init: function(onChange) {
this.onChange = onChange;
},

destroy: function() {
this.onChange = null;
_lastPath = null;
_currentPath = '/';
},

getCurrentPath: function() {
return _currentPath;
},

push: function(path) {
_lastPath = _currentPath;
_currentPath = path;
this.onChange();
},

replace: function(path) {
_currentPath = path;
this.onChange();
},

back: function() {
invariant(
_lastPath,
'You cannot make the URL store go back more than once when it does not use the DOM'
);

_currentPath = _lastPath;
_lastPath = null;
this.onChange();
}
};

module.exports = MemoryLocation;

9 changes: 9 additions & 0 deletions modules/helpers/getWindowPath.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Returns the current URL path from `window.location`, including query string
*/
function getWindowPath() {
return window.location.pathname + window.location.search;
}

module.exports = getWindowPath;

0 comments on commit 0e7a182

Please sign in to comment.