Skip to content

Update Histories #1363

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 21 additions & 27 deletions modules/BrowserHistory.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ import DOMHistory from './DOMHistory';
import { getWindowPath, supportsHistory } from './DOMUtils';
import NavigationTypes from './NavigationTypes';

function updateCurrentState(extraState) {
var state = window.history.state;

if (state)
window.history.replaceState(Object.assign(state, extraState), '');
}

/**
* A history implementation for DOM environments that support the
* HTML5 history API (pushState, replaceState, and the popstate event).
Expand Down Expand Up @@ -42,16 +35,24 @@ class BrowserHistory extends DOMHistory {
}

setup() {
if (this.location == null)
this._updateLocation();
if (this.location != null)
return;

var path = getWindowPath();
var key = null;
if (this.isSupported && window.history.state)
key = window.history.state.key;

super.setup(path, { key });
}

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

this._updateLocation(NavigationTypes.POP);
this._notifyChange();
var path = getWindowPath();
var key = event.state && event.state.key;
this.handlePop(path, { key });
}

addChangeListener(listener) {
Expand Down Expand Up @@ -79,31 +80,24 @@ class BrowserHistory extends DOMHistory {
}

// http://www.w3.org/TR/2011/WD-html5-20110113/history.html#dom-history-pushstate
pushState(state, path) {
push(path, key) {
if (this.isSupported) {
updateCurrentState(this.getScrollPosition());

state = this._createState(state);

var state = { key };
window.history.pushState(state, '', path);
this.location = this.createLocation(path, state, NavigationTypes.PUSH);
this._notifyChange();
} else {
window.location = path;
return state;
}

window.location = path;
}

// http://www.w3.org/TR/2011/WD-html5-20110113/history.html#dom-history-replacestate
replaceState(state, path) {
replace(path, key) {
if (this.isSupported) {
state = this._createState(state);

var state = { key };
window.history.replaceState(state, '', path);
this.location = this.createLocation(path, state, NavigationTypes.REPLACE);
this._notifyChange();
} else {
window.location.replace(path);
return state;
}
window.location.replace(path);
}

}
Expand Down
30 changes: 30 additions & 0 deletions modules/DOMHistory.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,36 @@ class DOMHistory extends History {
window.history.go(n);
}

saveState(key, state) {
window.sessionStorage.setItem(key, JSON.stringify(state));
}

readState(key) {
var json = window.sessionStorage.getItem(key);

if (json) {
try {
return JSON.parse(json);
} catch (error) {
// Ignore invalid JSON in session storage.
}
}

return null;
}

pushState(state, path) {
var location = this.location;
if (location && location.state && location.state.key) {
var key = location.state.key;
var curState = this.readState(key);
var scroll = this.getScrollPosition();
this.saveState(key, {...curState, ...scroll});
}

super.pushState(state, path);
}

}

export default DOMHistory;
96 changes: 32 additions & 64 deletions modules/HashHistory.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import warning from 'warning';
import DOMHistory from './DOMHistory';
import NavigationTypes from './NavigationTypes';
import { getHashPath, replaceHashPath } from './DOMUtils';
import { isAbsolutePath } from './URLUtils';

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

function saveState(path, queryKey, state) {
window.sessionStorage.setItem(state.key, JSON.stringify(state));
return addQueryStringValueToPath(path, queryKey, state.key);
}

function readState(path, queryKey) {
var sessionKey = getQueryStringValueFromPath(path, queryKey);
var json = sessionKey && window.sessionStorage.getItem(sessionKey);

if (json) {
try {
return JSON.parse(json);
} catch (error) {
// Ignore invalid JSON in session storage.
}
}

return null;
}

function updateCurrentState(queryKey, extraState) {
var path = getHashPath();
var state = readState(path, queryKey);

if (state)
saveState(path, queryKey, Object.assign(state, extraState));
}

/**
* A history implementation for DOM environments that uses window.location.hash
* to store the current path. This is essentially a hack for older browsers that
Expand All @@ -79,17 +50,15 @@ class HashHistory extends DOMHistory {
this.queryKey = this.queryKey ? DefaultQueryKey : null;
}

_updateLocation(navigationType) {
var path = getHashPath();
var state = this.queryKey ? readState(path, this.queryKey) : null;
this.location = this.createLocation(path, state, navigationType);
}

setup() {
if (this.location == null) {
ensureSlash();
this._updateLocation();
}
if (this.location != null)
return;

ensureSlash();

var path = getHashPath();
var key = getQueryStringValueFromPath(path, this.queryKey);
super.setup(path, { key });
}

handleHashChange() {
Expand All @@ -99,8 +68,9 @@ class HashHistory extends DOMHistory {
if (this._ignoreNextHashChange) {
this._ignoreNextHashChange = false;
} else {
this._updateLocation(NavigationTypes.POP);
this._notifyChange();
var path = getHashPath();
var key = getQueryStringValueFromPath(path, this.queryKey);
this.handlePop(path, { key });
}
}

Expand Down Expand Up @@ -128,40 +98,38 @@ class HashHistory extends DOMHistory {
}
}

pushState(state, path) {
warning(
this.queryKey || state == null,
'HashHistory needs a queryKey in order to persist state'
);

push(path, key) {
var actualPath = path;
if (this.queryKey)
updateCurrentState(this.queryKey, this.getScrollPosition());
actualPath = addQueryStringValueToPath(path, this.queryKey, key);

state = this._createState(state);

if (this.queryKey)
path = saveState(path, this.queryKey, state);

this._ignoreNextHashChange = true;
window.location.hash = path;

this.location = this.createLocation(path, state, NavigationTypes.PUSH);
if (actualPath === getHashPath()) {
warning(
false,
'HashHistory can not push the current path'
);
} else {
this._ignoreNextHashChange = true;
window.location.hash = actualPath;
}

this._notifyChange();
return { key: this.queryKey && key };
}

replaceState(state, path) {
state = this._createState(state);

replace(path, key) {
var actualPath = path;
if (this.queryKey)
path = saveState(path, this.queryKey, state);
actualPath = addQueryStringValueToPath(path, this.queryKey, key);


this._ignoreNextHashChange = true;
replaceHashPath(path);
if (actualPath !== getHashPath())
this._ignoreNextHashChange = true;

this.location = this.createLocation(path, state, NavigationTypes.REPLACE);
replaceHashPath(actualPath);

this._notifyChange();
return { key: this.queryKey && key };
}

makeHref(path) {
Expand Down
99 changes: 85 additions & 14 deletions modules/History.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import invariant from 'invariant';
import warning from 'warning';
import NavigationTypes from './NavigationTypes';
import { getPathname, getQueryString, parseQueryString } from './URLUtils';
import Location from './Location';

var RequiredHistorySubclassMethods = [ 'pushState', 'replaceState', 'go' ];

function createRandomKey() {
return Math.random().toString(36).substr(2);
}
var RequiredHistorySubclassMethods = [ 'push', 'replace', 'go' ];

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

this.parseQueryString = options.parseQueryString || parseQueryString;
this.changeListeners = [];
this.location = null;
}

_notifyChange() {
Expand All @@ -48,6 +45,81 @@ class History {
});
}

setup(path, entry = {}) {
if (this.location)
return;

if (!entry.key)
entry = this.replace(path, this.createRandomKey());

var state = null;
if (typeof this.readState === 'function')
state = this.readState(entry.key);

this._update(path, state, entry, NavigationTypes.POP, false);
}

handlePop(path, entry = {}) {
var state = null;
if (entry.key && typeof this.readState === 'function')
state = this.readState(entry.key);

this._update(path, state, entry, NavigationTypes.POP);
}

createRandomKey() {
return Math.random().toString(36).substr(2);
}

_saveNewState(state) {
var key = this.createRandomKey();

if (state != null) {
invariant(
typeof this.saveState === 'function',
'%s needs a saveState method in order to store state',
this.constructor.name
);

this.saveState(key, state);
}

return key;
}

pushState(state, path) {
var key = this._saveNewState(state);

var entry = null;
if (this.path === path) {
entry = this.replace(path, key) || {};
} else {
entry = this.push(path, key) || {};
}

warning(
entry.key || state == null,
'%s does not support storing state',
this.constructor.name
);

this._update(path, state, entry, NavigationTypes.PUSH);
}

replaceState(state, path) {
var key = this._saveNewState(state);

var entry = this.replace(path, key) || {};

warning(
entry.key || state == null,
'%s does not support storing state',
this.constructor.name
);

this._update(path, state, entry, NavigationTypes.REPLACE);
}

back() {
this.go(-1);
}
Expand All @@ -56,20 +128,19 @@ class History {
this.go(1);
}

_createState(state) {
state = state || {};

if (!state.key)
state.key = createRandomKey();
_update(path, state, entry, navigationType, notify=true) {
this.path = path;
this.location = this._createLocation(path, state, entry, navigationType);

return state;
if (notify)
this._notifyChange();
}

createLocation(path, state, navigationType) {
_createLocation(path, state, entry, navigationType) {
var pathname = getPathname(path);
var queryString = getQueryString(path);
var query = queryString ? this.parseQueryString(queryString) : null;
return new Location(pathname, query, state, navigationType);
return new Location(pathname, query, {...state, ...entry}, navigationType);
}

}
Expand Down
Loading