How do I use window.history in JavaScript? - javascript

I found a lot of questions about this on Stack Overflow, but they were all very specific about certain parts. I did find this question whose answers provide some nice references, but they don't actually explain how it all works, and their examples hardly do anything. I want to know more about how it all works together, and I want to use vanilla JavaScript.
(Also, many of the answers on other questions are years old.)

GETTING STARTED
First of all, you can remove the window part. Just history works fine. But before we get into how everything works together, we need to know what we can use.
Important Events
window.onload
This event fires whenever your webpage is loaded. There are two cases that will fire this event:
When your webpage is navigated to from another webpage. Note that I wrote webpage, not website. Moving between pages on the same site will trigger this event.
Just after your webpage is refreshed.
window.onpopstate
This event fires when you navigate between history states that you have set. Your browser automatically sets history states (to null) during normal browsing, but navigating to/from these states will not trigger this event.
window.onunload
This event fires whenever your webpage is unloaded. There are two cases that will fire this event:
When you navigate to another webpage from your webpage.
Just before your webpage is refreshed.
Important Objects
The history interface contains five functions (described below), two read-only objects (described here), and works a bit like a linked list. The two objects contained in each 'link' of the history object are:
length - This is the number of history states for the current browser window. It starts at 1.
state - This is a JavaScript object that can contain practically anything. It is null by default.
You can access them by calling history.length and history.state respectively, though history.state can only be used to get the current history state.
Important Functions
history.go(distance)
This function does the same thing as pressing the back or forward button in your browser, with the added functionality of being able to specify exactly how far you want to go. For example, history.go(3) has the same effect as would pushing your forward button three times, without actually loading the pages between your start and end locations. A negative value will likewise move you backwards through your history. history.go(0), history.go(), and even history.go(NaN) have the same effect as refreshing the page (this does not trigger the popstate event). If you cannot move forward/backward as far as specified, the function will do nothing.
history.back()
This function has the same functionality as the back button in your browser. It is equivalent to history.go(-1). If it cannot go back, the function will do nothing.
history.forward()
This function has the same functionality as the forward button in your browser. It is equivalent to history.go(1). If it cannot go forward, the function will do nothing.
history.replaceState(state, title[, location])
This function replaces the current history state. It takes three arguments, though the last one is optional. The arguments are:
state - This is the most important argument. The object you give to this argument will be saved to history.state for later retrieval. This is a deep copy, so if you later modify the original object it will not change the saved state. You could also set this to null, but if you aren't going to use it, there's not much point in using history at all.
title - The HTML Standard suggests that a string passed to this argument could be used by the browser in the user interface, but no browser currently does anything with it.
location - This argument allows you to change the URL relative to the current page. It cannot be used to change the URL to that of another website, but it can be used to change the URL to that of another page on your website. I would advise against this however, as the page is not actually reloaded even though the URL is of another page. Using back/forward will show the changed URL, but will not change the page, and will trigger popstate rather than load or unload. Refreshing the page after changing its URL will load the page specified by the URL rather than the page you were previously on. This functionality could be used to provide a link to your page in its current state, but I would recommend only changing the query string rather than the full URL. If this argument is not used, the URL will not change.
history.pushState(state, title[, location])
This function works the same as history.replaceState, except it puts the new state after the current state instead of replacing the current state. All history states that could have previously been accessed with forward are discarded, and the new state becomes the current state.
ASSEMBLING THE PIECES
The history interface is very useful for allowing your users to navigate through dynamically generated content from within their browser without having to reload the entire page, but you need to be mindful of all the possible things your users could do that could affect the history state.
First time navigating to your page
Should your users be greeted with a menu/list, some specific dynamically generated content, or maybe some random dynamically generated content?
Will your page display correctly without history, or even JavaScript?
Using back/forward to return to your page
Should your users see the same thing they saw their first time, or should they see the result of their visit reflected in the content? (A "Welcome Back" message might be a nice touch to some, but an unwanted distraction to others.)
Refreshing your page
Should you get a new page, return to the start page, or reload the same page? (Your users probably won't expect that last one if the URL hasn't changed.)
Using back/forward from a refreshed page
Should you get new content relative to the refreshed page, or reload the previously saved state?
Navigating away from your page
Do you need to save anything before leaving?
Returning to your page via a deep link
Do you have code in place to recognize and handle a deep link?
Note there is no way to delete a saved state (other than a specific case with pushState() mentioned above). You can only replace it with new content.
PUTTING IT ALL TOGETHER
Since this is starting to get a bit wordy, lets finish it off with some code.
// This function is called when the page is first loaded, when the page is refreshed,
// and when returning to the page from another page using back/forward.
// Navigating to a different page with history.pushState and then going back
// will not trigger this event as the page is not actually reloaded.
window.onload = function() {
// You can distinguish a page load from a reload by checking performance.navigation.type.
if (window.performance && window.PerformanceNavigation) {
let type = performance.navigation.type;
if (type == PerformanceNavigation.TYPE_NAVIGATE) {
// The page was loaded.
} else if (type == PerformanceNavigation.TYPE_RELOAD) {
// The page was reloaded.
} else if (type == PerformanceNavigation.TYPE_BACK_FORWARD) {
// The page was navigated to by going back or forward,
// though *not* from a history state you have set.
}
}
// Remember that the browser automatically sets the state to null on the
// first visit, so if you check for this and find it to be null, you know
// that the user hasn't been here yet.
if (history.state == null) {
// Do stuff on first load.
} else {
// Do stuff on refresh or on returning to this page from another page
// using back/forward. You may want to make the window.onpopstate function
// below a named function, and just call that function here.
}
// You can of course have code execute in all three cases. It would go here.
// You may also wish to set the history state at this time. This could go in the
// if..else statement above if you only want to replace the state in certain
// circumstances. One reason for setting the state right away would be if the user
// navigates to your page via a deep link.
let state = ...; // There might not be much to set at this point since the page was
// just loaded, but if your page gets random content, or time-
// dependent content, you may want to save something here so it can
// be retrieved again later.
let title = ...; // Since this isn't actually used by your browser yet, you can put
// anything you want here, though I would recommend setting it to
// null or to document.title for when browsers start actually doing
// something with it.
let URL = ...; // You probably don't want to change the URL just yet since the page
// has only just been loaded, in which case you shouldn't use this
// variable. One reason you might want to change the URL is if the
// user navigated to this page with a query string in the URL. After
// reading the query string, you can remove it by setting this
// variable to: location.origin + location.pathname
history.replaceState(state, title, URL); // Since the page has just been loaded, you
// don't want to push a new state; you should
// just replace the current state.
}
// This function is called when navigating between states that you have set.
// Since the purpose of `history` is to allow dynamic content changes without
// reloading the page (ie contacting the server), the code in this function
// should be fairly simple. Just things like replacing text content and images.
window.onpopstate = function() {
// Do things with history.state here.
}
// This function is called right before the page is refreshed, and right
// before leaving the page (not counting history.replaceState). This is
// your last chance to set the page's history state before leaving.
window.onunload = function() {
// Finalize the history state here.
}
Notice that I never called history.pushState anywhere. This is because history.pushState should not be called anywhere in these functions. It should be called by the function that actually changes the page in some way that you want your users to be able to use the back button to undo.
So in conclusion, a generic setup might work like this:
Check if (history.state == null) in the window.onload function.
If true, overwrite the history state with new information.
If false, use the history state to restore the page.
While the user is navigating the page, call history.pushState when important things happen that should be undoable with the back button.
If/When the user uses their back button and the popstate event is triggered, use the history state you set to return the page to its previous state.
Do likewise if/when the user then uses their forward button.
Use the unload event to finalize the history state before the user leaves the page.

Related

Javascript History back error how to delete history back

can't we use window.history.back(); twice time in same web browser
Lets say I have use window.history.back() where it does store how to delete it from a browser
You may have moved on by now, but... as far as I know there's no way to delete a history entry (or state).
One option I've been looking into is to handle the history yourself in JavaScript and use the window.history object as a carrier of sorts.
Basically, when the page first loads you create your custom history object (we'll go with an array here, but use whatever makes sense for your situation), then do your initial pushState. I would pass your custom history object as the state object, as it may come in handy if you also need to handle users navigating away from your app and coming back later.
var myHistory = [];
function pageLoad() {
window.history.pushState(myHistory, "<name>", "<url>");
//Load page data.
}
Now when you navigate, you add to your own history object (or don't - the history is now in your hands!) and use replaceState to keep the browser out of the loop.
function nav_to_details() {
myHistory.push("page_im_on_now");
window.history.replaceState(myHistory, "<name>", "<url>");
//Load page data.
}
When the user navigates backwards, they'll be hitting your "base" state (your state object will be null) and you can handle the navigation according to your custom history object. Afterward, you do another pushState.
function on_popState() {
// Note that some browsers fire popState on initial load,
// so you should check your state object and handle things accordingly.
// (I did not do that in these examples!)
if (myHistory.length > 0) {
var pg = myHistory.pop();
window.history.pushState(myHistory, "<name>", "<url>");
//Load page data for "pg".
} else {
//No "history" - let them exit or keep them in the app.
}
}
The user will never be able to navigate forward using their browser buttons because they are always on the newest page.
From the browser's perspective, every time they go "back", they've immediately pushed forward again.
From the user's perspective, they're able to navigate backwards through the pages but not forward (basically simulating the smartphone "page stack" model).
From the developer's perspective, you now have a high level of control over how the user navigates through your application, while still allowing them to use the familiar navigation buttons on their browser. You can add/remove items from anywhere in the history chain as you please. If you use objects in your history array, you can track extra information about the pages as well (like field contents and whatnot).
You could also extend the idea to handle forward navigation. You would need 3 pages - a "back" handler page, a "forward" handler page, and a "rest" page in between them where the user will typically be. You would then need to traverse back and forth along your history object instead of simply pushing/popping the items

How to handle browser refresh when using dojo/hash?

On a single page app, my problem is I'm subscribing to the hashchange event to render the content (create and destroy widgets that represent my pages), but the function only gets fired when the hash actually changes, such as when the browser back and forward buttons are clicked.
My main javascript file that always gets loaded on first time and also on refresh contain the following
topic.subscribe("/dojo/hashchange", function(newhash){
//set content based on hash
});
When refresh is clicked, it doesn't get fired and I'm left with a blank page (all my logic to render the page lies inside the anonymous function for that topic I subscribed to)
your subscribe isn't being called on a refresh as the hash didn't actually change. you're subscribing to hash changes, changes that happen after the page as loaded.
using this subscribe method things can get out of hand quickly. you may want to look at using route. some links -
https://developer.mozilla.org/en-US/docs/Web/Events/hashchange
https://dojotoolkit.org/documentation/tutorials/1.9/hash/
https://dojotoolkit.org/reference-guide/1.10/dojo/router.html
https://www.sitepen.com/blog/2014/06/18/dojo-faq-does-dojo-have-routing-like-backbone-and-embe/

JavaScript Historic back/forward

I'm building a little CoffeeScript application with 10 buttons and a container (simple). When the user press on one of the button : the container change.
The buttons look like a navbar and instead of using links (that will reload the entire page), I used javascript (Coffeescript, jquery or whatever) to change the content of the page (with some Ajax query to load data).
The problem is that the back and forward button of the browser can't work with that solution... and I need to find a solution for that. Routing maybe ?
I really like the way Asana.com resolved this issue: actually the address change but the content seems not to be entirely reloaded.
What do you suggest ? Thanks for the help
Hashes. The simplest solution is to define an URL hash every time the user clicks on a button. For example:
location.href = "#" + button.id;
With that, you create a history entry, and the user can press back or forward in the browser.
But how can you check when this happens? There's the hashchange event:
window.onhashchange = function() {
var state = location.hash.substring(1); // chomps the initial #
...
};
Basing your code on the state variable, you can trigger your AJAX calls from there.
By the way, you can change your code altogether, using links instead of buttons with an hash as the href property, which does not reload the page, but creates an history entry and fires the hashchange event.
The hashchange event is supported by every modern browser (that support history.pushState too, a more flexible and powerful way to control your history) and IE8-9.

history.pushState - not working?

I want to change html without reload. I do it like:
$('#left_menu_item').click(function(e) {
if (!!(window.history && history.pushState)) {
e.preventDefault();
history.pushState(null, null, newUrl);
}
});
It works correctly. But if I want to go back with "Back" button - browser change url on previous, but it not reload page. Why?
this behaviour is expected and is in accordance with the specifications of manipulating the history stack.
this is a relatively complex problem to explain. but in short think of it as this: any history entry the user pushes on the history stack (using pushState etc) doesn't merit a page load when you move from it because it is considered a fake (user generated) history entry.
why?
this behaviour is a good thing and is consistent with the intent of giving the developer more control over the page without being forced to reload it (think of it like ajax: you can do things that were previously only possible by page reloading like fetching data but now you can do it without reloading the page using the XMLHttpRequest object).. if you want to mimic the behaviour of reloading the page when clicking the back button.. you can simply call location.reload() when you handle the window.onpopstate event
how?
this may be outside the scope of your question but i just wanted to put it there to describe what we're talking about
let me explain by using an existing example here (excerpted text will be italicised):
Suppose http://mozilla.org/foo.html executes the following JavaScript:
var stateObj = { foo: "bar" };
history.pushState(stateObj, "page 2", "bar.html");
This will cause the URL bar to display http://mozilla.org/bar.html, but won't cause the browser to load bar.html or even check that bar.html exists.
think of it as that you are creating an entry in the history stack that is not associated with an actual page load.. rather a 'fake' page load (ie you are just using javascript to manipulate the dom and insert html)..
Suppose now that the user now navigates to http://google.com, then clicks back. At this point, the URL bar will display http://mozilla.org/bar.html, and the page will get a popstate event whose state object contains a copy of stateObj. The page itself will look like foo.html, although the page might modify its contents during the popstate event.
the point here is that bar.html is a fake history entry that sits on top of the original http://mozilla.org/foo.html.. so you will see on the url http://mozilla.org/bar.html but the contents will belong to foo (in this example notice that we didnt manipulate the content of the dom when we pushed bar.html.. if we did like in your example.. then that content will also show up). the key thing here is that the page reloads!.. because we are serving a page that has a genuin entry on the history stack (even if on the url.. we are displaying a url that is associated with a fake entry on the history stack).
also separate this discussion from the page manually handling the popstate event.. that's a different story and will just complicate things.
If we click back again, the URL will change to http://mozilla.org/foo.html, and the document will get another popstate event, this time with a null state object. Here too, going back doesn't change the document's contents from what they were in the previous step, although the document might update its contents manually upon receiving the popstate event.
here.. the page will not load!.. that's because we are making the transfer from a fake history stack entry to the real one (and the real one was already loaded in the previous step.. so the page reloaded and that's it).
that's it for the example. the concept is kind of hard to explain and i encourage you to test your code by clicking through a combination of real and fake pages and you will see a pattern of when the page actually loads and when it doesn't..
window.onpopstate = function(event) {
if(event && event.state) {
location.reload();
}
}
This is what I use :)

Using history.state to save scrollTop?

My over-engineered webapp:
Uses history.pushState and popState events, to animate movement between URLs, without having to use a # in the URL.
Uses an outer div to handle the scroll-bar, and the html and body have overflow:hidden. This is so when the user clicks back, it will not jump to the top before animating left.
Now my question is, if the user clicks the forward button again, how can I preserve where they were scrolled / scroll them back down to where they were.
My first thought is to use the history.state object as a place to save the scrolled position, and then load it up when the user clicks forward. (I would have to replaceState on the first page which has no history.state object.) The problem with this idea is that once the user clicks back, I no longer have access to add the scrollTop to the current state.
My next idea is to use a separate array, so I pull up the scroll location by index, but the problem with that is that if the user closes the tab and then re-opens it, or if they navigate to another site and come back to mine later, their forward-backward history is saved, but my separate array of scrollTops would be lost.
What is your suggestion?
As already mentioned, one can use the state object of the history API to track the current state of the page (e.g. scroll positions or form field entries).
At some point, of course, one has to store the page state into the history state object. When the browser has fired a popstate event, however, it is already too late because the previous history state will already be replaced by the popped one.
So one has to store the page state before the popstate event. As there is no beforepopstate event, however, the only possible way to do this is to listen constantly to changes in the page state (e.g. to scroll or input events) and constantly update the current history state object with replaceState.
In Chromium-based browsers, however, every call to replaceState shows the loading indicator briefly to the user. If you don't want this, just call replaceState once after a popstate event. In this call, store a unique id in the history state object. Use this unique id as a key to store further state in the browser's session storage. (You would have to model a cache because entries might become state.)
Jörn Zaefferer has written an approach for exactly this problem: https://github.com/jzaefferer/pitfalls-examples/blob/master/app/gallery/gallery.js#L29-37
(If your native language is german, take a look at his talk: http://www.youtube.com/watch?v=RGdbfKgPKI8)
With pushState you can add data to your history states ,like this:
var stateObj = { scrollTop: "500px" };
history.pushState(stateObj, "page 2", "page.html");
When the event of window.onpopstate is fired, it will return the stateObj (event.state), and there is your scrollTop position.
There is a nice plugin to control windows history:
jQuery Dynamic URL: https://github.com/promatik/jQuery-Dynamic-URL
Look at this example: http://promatik.no.sapo.pt/github/dynamic-url/

Categories

Resources