Why does my history.pushState call result in a null state? - javascript

I'm following some tutorials to learn how to use the history events in JS to deal with what is essentially paging on one of my forms. I have added the following code to an onClick event that changes the page:
state = {'actionCode': 'pageChange', 'pageNum': pageNum};
window.history.pushState(state, 'Page ' + pageNum, '/subF/fileName.cfm#page' + pageNum);
console.log(state);
I tried a number of other variations originally, including blank or null title and url arguments.
I then added this to the bottom of my JS file to see what I have to work with:
function checkState(e) {
console.log(e);
console.log(history.state);
}
$(function() {
window.onpopstate = checkState;
});
What I expected to see after changing 'pages' (running the first snippet of code) and then clicking the back button was a e.state object containing actionCode and pageNum variables. Instead, I see the state appear as null even though the object itself appears to hold the data immediately after passing said object to pushState:
I get the same null value when dumping history.state, so I assume the problem is with the push and not the get, or that I'm completely misunderstanding how these functions work.
What I expected to be able to do was add code to checkState that looks at the 'actionCode' and takes appropriate action based on that, reference the variables I know will exist in the state object for that particular actionCode.

In order for the onpopstate event to get triggered, you have to perform an actual "change history" action, i.e. clicking the browser BACK/FORWARD button or manually calling history.back() / history.forward() / history.go(). Simply pushing/replacing a state won't trigger an event.
You can try this:
<script>
window.onpopstate = function(event) {
console.log(event);
};
const state1 = {'actionCode': 'pageChange', 'pageNum': 1};
const state2 = {'actionCode': 'pageChange', 'pageNum': 2};
history.pushState(state1, 'Page ' + state1.pageNum, '/subF/fileName.cfm#page' + state1.pageNum);
history.pushState(state2, 'Page ' + state2.pageNum, '/subF/fileName.cfm#page' + state2.pageNum);
history.go(-1);
</script>
Here, the invocation of history.go(-1) will load the previous page from the session history thus firing an onpopstate event and you will see the state is there.
You can learn more about the peculiarities here: MDN page.
If you're trying to simulate handling of location/state change for new entries, you'll have to manually fire a PopStateEvent(or any custom one with a respective handler).
const event = new PopStateEvent('popstate', { state: state });
window.dispatchEvent(event);
or simply:
window.dispatchEvent(new Event('popstate'));

Related

React app detect if user refresh window or navigated away

I need to be able to detect if the user refreshed the window or navigated away (say google.com) and then return to the page so I can trigger an event.
I tried different onbeforeunload but it wasnt detecting it.
I tried, this and it wasnt working or triggering the console.log.
componentDidUpdate()
{
window.addEventListener("onbeforeunload",function (){
console.log('Page Refreshed')
})
}
However, that doesnt even trigger the console.log message. I have also tried beforeunload and same results
any ideas why?
The best way to detect a page reload is to use window.performance.navigation which returns an object for the page containing two key-value pairs { redirectCount: 0 , type: 0 } shown below:
console.log(window.performance.navigation)
When the key type is equal to a value which is non-zero for example - type: 1 it depicts a page reload.
Note: Most probably the snippet won't work as expected. In that case you can switch to browser console and check it out yourself and don't forget to check the Preserve log checkbox under network tab of browser console.
To check if page is navigating away. Try unload event instead of onbeforeunload.
window.addEventListener("unload", function (e) {
console.log('going away', e)
})
Lastly, in case of page load you can use lifecycle hooks. You can run some code as soon as the page loads in useEffect() of react-hooks in react or componentDidMount() if you're using class based syntax.
useEffect()
useEffect(() => {
// Run as soon as page loads
console.log('page loaded');
});
componentDidMount()
componentDidMount() {
// Run as soon as page loads
console.log('page loaded');
}
The event name is not recognized, in addEventListener the event name should be "beforeunload".
"onbeforeonload" is window property, use it like "window.onbeforeunload = funcName".
If your console log didnt show, it might be cleared by browser, try persist the log, then see it console.log shows up
Try this
window.onbeforeunload = function(event) {
console.log('Page Refreshed');
};
If you don't see the console you have to preserve the logs in the console.

Storage event not firing

Attaching the event:
$(window).on("storage", function (e) {
//callback not getting hit
});
Trying to fire the event:
localStorage.setItem("test", "123");
I have two tabs open, both listening to the storage event. I can see the localStorage getting updated correctly on both tabs. However when I change localStorage on one tab, the other never fires the event. Any ideas?
Tried on Chrome/Firefox. Domain format is https://www.xxx.yyy.zzz.
StorageEvent is fired in different page with the same domain.
From MDN
The StorageEvent is fired whenever a change is made to the Storage object.
This won't work on the same page that is making the changes — it is really a way for other pages on the domain using the storage to sync any changes that are made。
As others in the answers noted, the storage event only get picked up (by the listener) if the localStorage was changed in another browser's tab/window (of the same app), but not within the context of the current tab.
Detect storage changes in the current tab:
window.addEventListener('storage', console.log)
window.localStorage.setItem('test', '123')
window.dispatchEvent( new Event('storage') ) // <-----
A manual storage event is dispatched.
This will effectively trigger the storage event listener twice on other tabs/windows (of the same app), but in certain situations this shouldn't be a problem.
If even testing between different tabs/pages and still not seeing the event... I've found that the event will only fire if the key already exists.
It seems it's more like an onchange event.
Set a default value to the localStorage key, if even undefined and then test.
I'd call this a Chrome bug, as Firefox and Safari are firing correctly, but it is what it is.
An additional point to vsync's answer above is you can fire StorageEvent instead of Event and pass in an object so the fired event will match the browser default.
const oldValue = window.localStorage.getItem('test');
window.localStorage.setItem('test', newValue);
const event = new StorageEvent('storage', {
key: 'test',
oldValue,
newValue,
...
});
window.dispatchEvent(event);
Problem was caused by document.domain overriding in code. After I removed the document.domain setter, events worked correctly.
Seems this is caused by a bug in Chrome.
https://bugs.chromium.org/p/chromium/issues/detail?id=136356&q=label%3AOWP-Standards-Compatibility&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Cr%20Status%20Owner%20Summary%20OS%20Modified
window.addEventListener('storage', function (e) {
console.log("storage event occured here");
},false);
Storage Listner get called in other tabs , other than source tab. Just add debugger to other tabs.
You could always use a utility like localDataStorage to fire the events for you, in the same window/tab, whenever a key value changes, such as those made by the set or remove methods. You can also use it to transparently set/get any of the following "types": Array, Boolean, Date, Float, Integer, Null, Object or String.
[DISCLAIMER] I am the author of the utility [/DISCLAIMER]
Once you instantiate the utility, the following snippet will allow you to monitor the events:
function localStorageChangeEvents( e ) {
console.log(
"timestamp: " + e.detail.timestamp + " (" + new Date( e.detail.timestamp ) + ")" + "\n" +
"key: " + e.detail.key + "\n" +
"old value: " + e.detail.oldval + "\n" +
"new value: " + e.detail.newval + "\n"
);
};
document.addEventListener(
"localDataStorage"
, localStorageChangeEvents
, false
);
I was trying to logout from other tabs if logged out from one tab. The problem I faced was setting the same value over and over.
Thats why the Storage event not fired in other tabs. A simple hack was to add a salt/random stuff everytime to emit the event.
window.localStorage.setItem('logout-event',Math.random().toString())
Then the events are available in other tabs! A better way to handle events from other tabs is to use BroadcastChannel

How to handle back button while changing the browser-URL with HTML5 pushState

I’ve made a one page site. When user clicks on the menu buttons, content is loaded with ajax.
It works fine.
In order to improve SEO and to allow user to copy / past URL of different content, i use
function show_content() {
// change URL in browser bar)
window.history.pushState("", "Content", "/content.php");
// ajax
$content.load("ajax/content.php?id="+id);
}
It works fine. URL changes and the browser doesn’t reload the page
However, when user clicks on back button in browser, the url changes and the content have to be loaded.
I've done this and it works :
window.onpopstate = function(event) {
if (document.location.pathname == '/4-content.php') {
show_content_1();
}
else if (document.location.pathname == '/1-content.php') {
show_content_2();
}
else if (document.location.pathname == '/6-content.php') {
show_content_();
}
};
Do you know if there is a way to improve this code ?
What I did was passing an object literal to pushState() on page load. This way you can always go back to your first created pushState. In my case I had to push twice before I could go back. Pushing a state on page load helped me out.
HTML5 allows you to use data-attributes so for your triggers you can use those to bind HTML data.
I use a try catch because I didn't had time to find a polyfill for older browsers. You might want to check Modernizr if this is needed in your case.
PAGELOAD
try {
window.history.pushState({
url: '',
id: this.content.data("id"), // html data-id
label: this.content.data("label") // html data-label
}, "just content or your label variable", window.location.href);
} catch (e) {
console.log(e);
}
EVENT HANDLERS
An object filled with default information
var obj = {
url: settings.assetsPath, // this came from php
lang: settings.language, // this came from php
historyData: {}
};
Bind the history.pushState() trigger. In my case a delegate since I have dynamic elements on the page.
// click a trigger -> push state
this.root.on("click", ".cssSelector", function (ev) {
var path = [],
urlChunk = document.location.pathname; // to follow your example
// some data-attributes you need? like id or label
// override obj.historyData
obj.historyData.id = $(ev.currentTarget).data("id");
// create a relative path for security reasons
path.push("..", obj.lang, label, urlChunk);
path = path.join("/");
// attempt to push a state
try {
window.history.pushState(obj.historyData, label, path);
this.back.fadeIn();
this.showContent(obj.historyData.id);
} catch (e) {
console.log(e);
}
});
Bind the history.back() event to a custom button, link or something.
I used .preventDefault() since my button is a link.
// click back arrow -> history
this.back.on("click", function (ev) {
ev.preventDefault();
window.history.back();
});
When history pops back -> check for a pushed state unless it was the first attempt
$(window).on("popstate", function (ev) {
var originalState = ev.originalEvent.state || obj.historyData;
if (!originalState) {
// no history, hide the back button or something
this.back.fadeOut();
return;
} else {
// do something
this.showContent(obj.historyData.id);
}
});
Using object literals as a parameter is handy to pass your id's. Then you can use one function showContent(id).
Wherever I've used this it's nothing more than a jQuery object/function, stored inside an IIFE.
Please note I put these scripts together from my implementation combined with some ideas from your initial request. So hopefully this gives you some new ideas ;)

browser back and forward button does not invoke callback method with statechange event of history.js

I used (https://github.com/browserstate/history.js) and have a piece of code like this
History.Adapter.bind(window, 'statechange', function() {
var State = History.getState();
alert('Inside History.Adapter.bind: ' + State.data.myData);
});
function manageHistory(url, data, uniqueId){
var History = window.History;
if ( !History.enabled ) { return false; }
History.replaceState({myData: data}, null, '?stateHistory=' + uniqueId);
}
if I invoke manageHistory() after my ajax call, then History.Adapter.bind callback method get invoked correctly. However, if I click the browser back and then click forward button that result in page navigation from B to A, then A back to B, call back method inside History.Adapter.bind does not get invoked. This happen on both chrome and IE9. Anyone know how to fix this issue, I need to get the State after click browser back and then forward, to update my DOM. Please help
Note: I use version 1.7.1 jquery.history.js for html4 browser IE9
UPDATE (May 3 2013): Talk a bit more about my requirement
Sorry I was busy with other task, that not until now that I have sometimes to look at this issue. So oncomplete of an ajax call, I took that information that return to me, and push it into state. My requirement is: that if I navigate from page A to page B, and then on page B, I execute numbers of ajax requests that result in DOM manipulation (let call each ajax request that result in DOM manipulation a state). If I click the back button, I should go back to page A, and if I click the forward button, I should go to page B with the last state that I was in.
So my thought was, oncomplete of my ajax request, I would replace the last state in history with my current state (my json object is the html that I receive back from the ajax request). If I use push state, let say I am on page B, and I click one button, my page now change to aaa, I pushState aaa. Then if I click other button, my page now change to bbb, I pushState bbb. If I click the back button now, I would still be on page B, with my state in History.Adapter.bind(window, 'statechange', function() {}) show as aaa, if I click back button again, i would go to page A. I do not want this behavior. I want that if I am on page B with state bbb, and click back, I would go to page A, and if I click forward, I would go to page B with state bbb. I hope this make more sense. Please help
What you need to do is as follows:
If the page you came from is another page (e.g. you open page B and the previous page was page A) you do a pushState.
If on the other hand the previous page was the same page as you're currently on you will simply do a replaceState, replacing the information with information about your current state.
(the tracking of the previous page you will need to do manually in some variable)
This means that if you go from A to B (pushState) to new state of B (replaceState), back button (goes to state info of A) and next forward (goes to the newest state of B). Additionally if you open A the first time you need to do a replaceState with information about the state of A so that A will have some state at least (otherwise it will have an empty data object).
It might be useful to look at this answer of mine, which is simply about the way you would built an ordered history stack with pushStates. Not exactly what you want of course, but it's a bit simpler written and together with the information in this answer it should get you the behaviour you want.
I see that you are using History.replaceState which remove the last history state in the stack and replace it by the state given in parameters.
I am using History.pushState in my website, and doesn't face such an issue because this function doesnt pull the last state but add the new state above it. It is making the back-forward buttons work correcly.
I hope it helps you.
Edit: Using the example of change of select tag as event listenner:
function manageHistory(url, data, uniqueId){
var History = window.History;
if ( !History.enabled ) { return false; }
History.replaceState({myData: data}, null, '?stateHistory=' + uniqueId);
}
$('select.select').change(function(e) {
e.preventDefault();
// get url
// get data
// get uniqueId
manageHistory(url, data, uniqueId)
});
History.Adapter.bind(window, 'statechange', function() {
var State = History.getState();
// Launch the ajax call and update DOM using State.data.myData
});
Also,
relpaceState will always erase B state.
Use pushState for A and B states.
And use replacestate for aaa, bbb, ccc states.
function manageHistoryBack(url, data, uniqueId){
var History = window.History;
if ( !History.enabled ) { return false; }
History.replaceState({myData: data}, null, '?stateHistory=' + uniqueId);
}
function manageHistory(url, data, uniqueId){
var History = window.History;
if ( !History.enabled ) { return false; }
History.pushState({myData: data}, null, '?stateHistory=' + uniqueId);
}
// Event listenner triggering aaa,bbb,ccc
$('select.select').change(function(e) {
e.preventDefault();
// get url
// get data
// get uniqueId
manageHistoryBack(url, data, uniqueId)
});
// Event listenner triggering A, B
$('select.select').change(function(e) {
e.preventDefault();
// get url
// get data
// get uniqueId
manageHistory(url, data, uniqueId)
});
History.Adapter.bind(window, 'statechange', function() {
var State = History.getState();
// Launch the ajax call and update DOM using State.data.myData
});
Good luck

javascript history onpopstate

I am trying to understand HTML5 history object. Here is a simple example which I started off with.
function addDialog(){
document.getElementById('d').style.display ='';
history.pushState({name:"changed"},"","#newURL");
}
window.onpopstate = function(e){
alert(e.state);
}
I have a div with an id d for which display property is none. On clicking a link, I will display the div and change the history so that new url will be loaded.
When I copy paste the new url, popstate event is fired and I get null for e.state.
From what I understand, if I load the new url http://example.com#newURL, e.state should point to the object which I pushed using pushstate.
Please correct me if I am wrong and also I would like to know when e.state gets populated.
As I tested, the e.state only get the pop state that you add to the history when you click in the Back or Forward button. Otherwise, it will give you the null.
You can use parameters to your URL so you can test if the request came from a history call or from an URL in the location bar.
onpopstate = function(event) {
alert('popEvent: ' + event);
if(event.state){
setupPage(event.state);
} else {
setupPage(getQStringParam('zid'));
}
}

Categories