Display dynamic page content in combination with the back button

In times of dynamically reloaded page content, it often happens that jumping back to the previous page produces an unexpected result. The range of possibilities here is wide and can extend to the browser's start page, even though you clicked like crazy on the previous page. With HTML5, there is a very simple way to counteract this.

Reading duration: approx. 5 Minutes

Imagine you are clicking through a list of products in an online store and as you scroll, more and more items are loaded. This happens a few times until you find an interesting item and click on it. As the item does not have the desired color, you go back to the list - using the browser's back button. You see the list, but not where you were last. Even worse. The reloaded articles are missing from the list and you have to scroll through the list again. This takes time and is frustrating.

The browser can normally remember the point on the page from which the visitor navigated to the next page. However, as soon as content is reloaded (e.g. via Ajax), the browser can no longer offer this functionality. A lack of history is a devastating user experience, especially for single page applications, i.e. pages that load content entirely via JavaScript. The browser history only knows the initial state of the page. The back button would refer to the previous page. This could be, for example, the hit list of a search engine or a blank page/the start page of the browser if the URL was entered directly. It would be desirable, however, for the browser to take the user back to the exact position they were at before clicking the back button.

Diagram of the back-and-forth navigation

Why is everything different again when using Ajax?

Because the page content is subsequently changed, the browser history knows nothing about the changed content. The browser does everything correctly and displays the previous page as it received it from the server. This can be, for example, a store page with the first 20 items, although the visitor had previously been shown more than these 20 items by scrolling and reloading. Dynamic content is - as the name suggests - dynamic.

And what can you do about it?

Thanks to the constant further development of web standards, HTML5 makes it possible to customize the browser history as desired using the "window.history" object. This allows current entries to be adjusted, new entries to be created and a jump to a specific entry is also possible. Of course, there are also JavaScript frameworks that address this problem. However, these are not always easy to use, as various factors are very important here, e.g. how exactly the reloading of the page content is implemented. In some cases, a few lines of code are enough to implement the desired functionality.

Useful links on the topic:

Browser compatibility

MDN WebGuide

Our sample project (download via Github)

What basic adjustments are necessary?

The following code examples refer to our sample project, which can be downloaded at https://github.com/punktDe/demo-browser-history.git.

In our example, we have a page with a menu that replaces the content of the page. There is also a link to another page. Without the adjustments to the history, the page could be operated normally, but the history was only expanded when navigating via hyperlinks. As a result, it was not possible to get the last state of the page by using the back button.

Sample menu, which you get from the code example

Below you will find the adjustments that have solved this problem.

Extending the browser history

New entries must be added to the browser history using the history.pushState() method. In this method, we can provide the browser with any information as the first parameter, which we can then access in the return event - the "popstate" event. In our example, we pass the reloaded HTML code and the ID of the active menu element in an object. You can also set the title of the page as the second parameter. A URL can be stored as the third parameter. This can also contain information such as GET parameters or an anchor (.../home.html#about-us). In our case, this is the current page using "window.location", as the URL of the page will not change when the menu is clicked.

$('#menu a').each(function() {
   $(this).click(function() {
      var activeLinkId = $(this).attr('id');
 
      $.ajax({
         method: "GET",
         url: $(this).data('url'),
         dataType: "HTML"
      })
      .done(function(html) {
         var dataObject = {
            html: html,
            activeLinkId: activeLinkId
         };
 
         // this will add a new entry for the browser history like it would happen if you
         // click on a hyperlink on the page with additional information we can use as needed
         history.pushState(dataObject, null, window.location);
 
         setCurrentPage(html, activeLinkId);
      });
   });
});

As we know in the click event what was clicked and what content was reloaded, we can simply pack this information into a JavaScript object and transfer it to the new "history" object to be created. This ensures that the back button now remains on the same page. However, nothing else will happen, as the URL in our example does not cause the content to change. We need the "popstate" event for this.

Responding to the back and forward buttons

An EventListener must be registered for the"popstate" event so that the page is actually updated after a click on the back button (or forward button). In this event, the page can be adjusted according to the information stored in the history. Without adjustments in the event, only the URL stored via history.pushState() is called. If no parameters are stored here that influence the state of the page, the content is not automatically reloaded.

window.addEventListener('popstate', function(event) {
   if (event.state !== null && event.state !== undefined) {
      setCurrentPage(event.state.html, event.state.activeLinkId);
   }
});

No JavaScript calls that manipulate the history should be made within the "popstate" event. Otherwise the history will change even though you have used the back button, for example.

Update current history entry

If a page is constantly changing due to various clicks, e.g. in an online store by setting filters in the item list, then the history.replaceState() method becomes interesting. This updates the current history entry with the same parameters as the history.pushState() method. This ensures that the last active state is correctly stored in the history.

Adjustments to the history when loading the page

The previous adjustments would help with a single page application, i.e. a page that loads everything dynamically and has no normal hyperlinks. In our example, we have therefore added a hyperlink to an Impress page. Because what does the browser display if the menu is clicked a few times first, then the Impress page is accessed via a hyperlink and now the back button is clicked? Correct, the normal content of the Index.html is displayed and not the last state of the menu with changed HTML.

if (history.state === null || history.state === undefined) {
   // store current page state in the current history
   var dataObject = {
      html: jQuery('#replace-container').wrap('<pseudo/>').parent().html(),
      activeLinkId: ''
   };
 
   // this replaces the current browser history entry with information (html and current active menu id)
   // we use in our popstate event to handle the state of the page on history navigation
   history.replaceState(dataObject, null, window.location);
} else {
   // set the page state by given information from the history state
   setCurrentPage(history.state.html, history.state.activeLinkId);
}

If the history for the current entry does not contain a history.state value, which is the case when the page is called up via the address bar or when a hyperlink is clicked, then we overwrite the current history with the corresponding information. If we have the information, we can assume that a browser button (back/forward) has just been pressed and do the same as in the "popstate" event.

Conclusion

Depending on the complexity, the page (pages with dynamically reloading content) can be adapted so that the back button restores a previous state of the page as desired. This ensures less frustration for the site visitor and makes the site easier to use overall. For single page applications in particular, adjusting the history is a big step towards better usability. After all, the back button has existed for longer than the reloading of content and should therefore continue to do what is expected of it.

Share:

More articles

Gesagt, getan
Jürgen Egeling, Inhaber / Product Owner at punkt.de
Working at punkt.de