Reputation: 6609
When I hit the backwards or forwards button and the popstate event fires, can I get the state object of the previous state? As in not the state object provided by e.state but the one I just back/forwarded away from?
Alternatively, can I detect whether it was the back or forwards button that was pressed?
I need this since I have multiple subsystems that all need to use the js history at the same time, I could save the state of all of them when pushing of course but then, when I pop I have to restore the state to all of them too which is undesired as it resets objects that I don't need to. For example
state 1 = {a:1, b:1, c:1}
push a = 2
state 2 = {a:2, b:1, c:1}
hit back button, popstate fires state 1 to me
restart a with 1, b with 1, c with 1 while I only need to restart a
Alternative fantasy solution
....
hit back button, popstate fires state 1 to me
I also get state 2 (the one I just moved away from) through some black magic
do a differential comparison between the 2 states, find out that I only need to modify a
restart a with 1
EDIT: oh also, to clarify, there is no easy way to check what state a, b and c currently are on the page (since its a bit more complicated than abc and 123)
Upvotes: 9
Views: 9035
Reputation: 370
I think Richard J's code is good. Just to give you another example code which is slightly different with his coding.
Please bear in mind that, popstate
is only triggered when the "back" is triggered. You cannot interrupt it by using event.preventDefault()
. This event is called after the change (i.e. event.state === history.state
).
There is no event triggering in pushState
and replaceState
.
The improvement in the following code is that you can know all the changes instead of just popstate. Please remember that History API is useful in Single Page Application (SPA) but do not mix it with normal hyperlink navigation, which would be a disaster.
// setup a object 'stateMgr' to manage the stuff.
const stateMgr = {
currentState: null,
prevState: null,
modifyByPushState(newState){
this.prevState = this.currentState;
this.currentState = newState;
this.onStateChanged(null);
},
modifyByOnPopState(evt){
this.prevState = this.currentState;
this.currentState = evt.state;
this.onStateChanged(null);
},
modifyByReplaceState(newState){
let replacedState = this.currentState;
this.currentState = newState;
this.onStateChanged(replacedState);
},
onStateChanged(replacedState){
// here you can have this.currentState and this.prevState
window.dispatchEvent(new CustomEvent('statechanged', {detail: {
currentState:this.currentState,
prevState:this.prevState,
replacedState
}}))
}
}
// global methods for use
function pushState(state, title, href){
window.history.pushState(state, title, href);
stateMgr.modifyByPushState(state);
}
function replaceState(state, title, href){
window.history.replaceState(state, title, href);
stateMgr.modifyByReplaceState(state);
}
// notify the popstate using modifyByOnPopState(event) in the event handling
window.addEventListener('popstate', function(evt){
stateMgr.modifyByOnPopState(evt);
}, false);
// this is a customized event you can obtain all changes of state
window.addEventListener('statechanged', function(evt){
const {currentState, prevState, replacedState} = evt.detail;
console.log(currentState, prevState, replacedState);
}, false);
// Implement the click hanlders
function addRow(t){
let li = document.createElement('li');
li.append(t);
document.querySelector('ul').append(li);
}
var uid = 0;
var dateOffset = Date.now() - 0x24000;
function a(){
pushState({uid: ++uid, d: Math.random().toFixed(2), v: Date.now()-dateOffset}, '', location.href);
addRow('Push');
}
function b(){
replaceState({uid: ++uid, d: Math.random().toFixed(2), v: Date.now()-dateOffset}, '', location.href);
addRow('Replace');
}
function c(){
addRow('Go Back');
window.history.go(-1);
}
<button onclick="a()">push</button>
<button onclick="b()">replace</button>
<button onclick="c()">back</button>
<ul></ul>
Upvotes: 0
Reputation: 500
This is my source you can see full resource from https://gist.github.com/HasanDelibas/12050fc59d675181ea973d21f882081a
This library contains:
state
must be object at history.pushState( **state**, ...)
(function(){
let stateSymbol = "__state__index__";
history.stateIndex =-1;
history.states=[];
let pushState = history.pushState;
function add(data,title,url){
if(data==null) data={};
if(typeof data!="object") data={data:data};
data[stateSymbol] = (history.stateIndex+1);
history.states.splice(history.stateIndex+1,0,[data,title,url])
history.states.splice(history.stateIndex+2)
history.stateIndex++;
}
history.pushState =function(data,title,url=null){
add(data,title,url);
pushState.bind(history)(data,title,url);
}
addEventListener("popstate",function(e){
var eventObject= {};
var newStateIndex = e.state!=null ? e.state[stateSymbol] : -1;
eventObject.from = history.states[history.stateIndex];
eventObject.to = newStateIndex>-1 ? history.states[newStateIndex] : null;
eventObject.side = history.stateIndex>newStateIndex ? "back" : "forward";
if( newStateIndex > -1 && !(newStateIndex in history.states) ){
add(history.state,"",window.location.href);
}
window.dispatchEvent(new CustomEvent("historyChange", {detail: eventObject} ))
history.stateIndex = e.state!=null ? e.state[stateSymbol] : -1;
});
})();
Now you can get all states with history.states
object,
and detect history change with addEventListener("popstate",function(e))
Using
/**
* @param e.detail.from [data,title,url]
* @param e.detail.to [data,title,url]
* @param e.detail.side "back" | "forward"
*/
addEventListener("historyChange",function(e){
var from = e.detail.from; // [ data , title , url ]
var to = e.detail.to; // [ data , title , url ]
var side = e.detail.side; // "back" | "forward"
console.log( `You changed history. Side is ${e.detail.side}.\nFrom:${e.detail.from[2]}\nTo:${e.detail.to[2]}`)
})
history.pushState("1", "DENEME-TEST" ,"?1");
history.pushState("2", "DENEME-TEST" ,"?2");
// list of history states
console.log( history.states )
/*
[
[ {...} , "DENEME-TEST" ,"?1" ]
[ {...} , "DENEME-TEST" ,"?2" ]
]
*/
// get history current state index
console.log( history.stateIndex )
/*
1
*/
Upvotes: 0
Reputation: 7313
The answer is to decorate pushState
to record the current state in the application. So, something like:
var app = {
currentState: false,
originalPushState: false,
pushState : function(state, title, href) {
app.currentState = state;
app.originalPushState.call(history, state, title, href);
},
onPopstate : function(event) {
var newState = event.state;
var oldState = app.currentState;
app.currentState = newState;
// you now have the current state and the state you came from
},
init : function() {
window.onpopstate = app.onPopstate;
app.originalPushState = history.pushState;
history.pushState = app.pushState;
}
}
app.init();
As an aside, popstate
is quite confusing. When you pop a regular array you get the value that was popped, not the value that is now at the end of it, so semantically it's a bit odd. Furthermore, history.state
always contains the current state, so event.state
is redundant. Strange choice by the designers, I'd say, more sensible if something event.popped
was a pointer to the state of the page that was actually popped (perhaps only available if it's in the same domain as your site).
Upvotes: 8
Reputation: 5828
onpopstate
is fired after the state change so you cannot get the originating state, but you can do it the other way around, like having a copy of the states and indicator of current state, and update your copy in the onpopstate
manually.
Upvotes: 3