Yay295 Yay295 - 2 months ago 6
Javascript Question

How do I use window.history in 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.)

Answer

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:

  1. 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.
  2. 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:

  1. When you navigate to another webpage from your webpage.
  2. 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. You could also set it 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.

  1. 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?
  2. 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.)
  3. 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.)
  4. Using back/forward from a refreshed page
    • Should you get new content relative to the refreshed page, or reload the previously saved state?
  5. Navigating away from your page
    • Do you need to save anything before leaving?
  6. 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() {
  // 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.
  var 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.
  var 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.
  var 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() {
  // "How do I know if the user is navigating away from my page or just refreshing it?"
  // Well, you can't. At least not with the history interface. It's possible to get the
  // HTTP Referer URL and check if it's different from your page's URL, but this won't
  // work most of the time: https://en.wikipedia.org/wiki/HTTP_referer#Referer_hiding
}

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.