Manage Url History

With Html5 we can manage the forward/backward button history queue, as well as the displayed URL in the address bar of the browser. HTML5 brings us several important enhancements to the browser’s window.history object, commonly referred to as the History API.

The browser has long supported a History API. The difference that HTML5 brings is the enhanced functionality of pushState(...), replaceState(...) and popstate.

To test if the browser supports the enhanced History API, use the following code.
var history_support = !!(window.history && window.history.pushState);

When you change the URL in the address bar, the browser initiates a new request to the server for that new page. But, today’s complex web applications more commonly use Ajax to load only new information, without full-page refreshes. This leads to a disconnect, where web applications can’t update the address bar URL, because they don’t want a browser page refresh. To change the URL in the address bar without forcing a new page load, use the history.pushState(...) method. This method updates the URL in the address bar, and creates a special state-oriented entry in the browser’s history.

Notes
The new URL you pass to pushState() or replaceState() must be the same origin (domain, etc) as the current page, or the API throws an error. You can change the path, filename, query string, and hash portions of the URL, just not the protocol/schema, domain, or port. It would be a security risk, to allow mixing of URL origins in the state queue. Use normal location/history manipulation if you need to navigate across different origins.


So if a user then clicks the back button in their browser, instead of doing a reload of the previous page, the browser fires the new popstate event, which your application can respond to by setting the page back to that previous state.

Below is the example that allow you to handle forward/backward navigation with only state changes (and not separate page loads), as well as keeping the displayed URL in the address bar up to date.

This example keeps track of whether an element is visible or not, and maintains this state in the browser’s forward/backward navigation stack—as well as reflecting that state in the browser address bar URL—so that the current state can be copied and pasted or bookmarked.

snippet
<html>
<head>
<title>History Exmaple</title>
<script>
function showText(updateHistory) {
document.getElementById("long_desc").style.display = "block";
if (updateHistory) history.pushState(null, null, "?show");
}

function hideText(updateHistory) {
document.getElementById("long_desc").style.display = "none";
if (updateHistory) history.pushState(null, null, location.href.replace(/\?show/, ""));
}

function toggleText() {
var elem = document.getElementById("long_desc");
if (elem.style && elem.style.display == "none") showText(true);
else hideText(true);
}

function manageText() {
if (location.href.match(/\?show/)) showText();
else hideText();
}
window.addEventListener("popstate", manageText, false);
window.addEventListener("DOMContentLoaded", function() {
document.getElementById("toggle").addEventListener("click", function(e) {
toggleText();
e.preventDefault();
return false;
}, false);
History | 223
manageText();
}, false);
</script>
</head>
<body>
<p>Here's a short description.</p>
<a id="toggle" href="#">toggle</a>
<p id="long_desc">Here's a longer description, which can be shown or hidden.</p>
</body>
</html>

If you run this demo, and click successively on the "toggle" link, you’ll see that the the longer text description paragraph is indeed toggled on and off. You’ll also notice that when the paragraph is visible, the URL has "?show" in it, and when it’s hidden, this parameter is removed. Finally, you will notice the forward/backward navigation cycles through these states, showing and hiding the paragraph as appropriate.

Try copying and pasting the URL while the "?show" is visible, and pasting that into a new browser tab, and you’ll see that indeed the paragraph is visible—the state really was preserved in the URL, as we wanted. The above example keeps track of the state changes in the forward/backward queue of the browser.

For some applications, this is desirable. For other applications, polluting the forward/backward queue with lots and lots of intermittent state changes is notappropriate. So, instead of using pushState(...), use replaceState(...), which, as the name implies, replaces the current state entry in the forward/backward navigation with the new desired state. If we do that for our example above, it looks like this.

snippet
function showText(updateHistory) {
document.getElementById("long_desc").style.display = "block";
if (updateHistory) history.replaceState(null, null, "?show");
}

function hideText(updateHistory) {
document.getElementById("long_desc").style.display = "none";
if (updateHistory) history.replaceState(null, null, location.href.replace(/\?show/, ""));
}

Running that demo, you’ll see that the toggle behavior and the URL all behave the same. The only difference is that there’s no forward/backward queue state to cycle through.

The above code example stores a simple state in the URL ("?show"). This is good for the copy/paste (or bookmarking) use case, as the entirety of the state is in the URL and thus restorable.

If you have a more complex set of states to manage, and copy/paste or bookmarking is not important, you can actually store a much richer and more complex set of states with each entry. This complex state is saved with an entry, and then retrieved and sent back to your application via the popstate event handler as the user navigates back with the back button.

The first parameter to pushState(...)/replaceState(...) is the state object, which can be any arbitrarily complex object that you need, as long as it’s serializable to a string value.
Example
snippet
window.addEventListener("popstate", function(e) {
alert("Current state data: " + JSON.stringify(e.state));
}, false);
window.pushState({
foo: "bar"
}, null, "?foobar");
window.pushState({
bar: "baz"
}, null, "?barbaz");
history.back(); // triggers `popstate` to go back to the "?foobar" page/state

Before the HTML5 History API enhancements were added to browsers, the only way to emulate the functionality described above was using the URL’s "hash" (the end of a URL that looks like "#some|stuff|here"). Older browsers don’t all support the hashchange event, which is very helpful in monitoring the state of the URL hash, in case a user copies and pastes a URL into the address bar. Without that event, you must poll the URL hash using a timer. Helper library like History.js (https://github.com/balupton/history.js), attempts to use the new HTML5 History API enhancements, and falls back to URL hash management automatically.

Notes
Browsers currently don’t support the second parameter, which is a "title" for the new state, so just pass null or an empty string for that parameter.