Jump to Table of Contents

History

JavaScript applications often involve client-side interactions that change the contents or state of the page without performing a full page refresh. Browsers don't record new history events for this kind of interaction, which means that the back and forward buttons can't be used to navigate between these states.

The YUI History Utility provides an API that JavaScript applications can use to programmatically add state information to the browser history, and to provide bookmarkable and shareable URLs that can be used to restore that state at a later time.

Note: Releases of YUI prior to 3.2.0 included the Browser History Manager, which is now deprecated. For information on the differences between the Browser History Manager and the new History Utility, and on how to migrate your code, see the Migrating from the Browser History Manager section below.

Getting Started

To include the source files for History and its dependencies, first load the YUI seed file if you haven't already loaded it.

<script src="http://yui.yahooapis.com/3.11.0/build/yui/yui-min.js"></script>

Next, create a new YUI instance for your application and populate it with the modules you need by specifying them as arguments to the YUI().use() method. YUI will automatically load any dependencies required by the modules you specify.

<script>
// Create a new YUI instance and populate it with the required modules.
YUI().use('history', function (Y) {
    // History is available and ready for use. Add implementation
    // code here.
});
</script>

For more information on creating YUI instances and on the use() method, see the documentation for the YUI Global Object.

How Browser History Works

Browsers keep track of the web pages a user visits and allow the user to easily jump back and forth between those pages using "back" and "forward" buttons or shortcuts.

Each time a new URL is loaded, the browser adds an entry to the browser history pointing to that URL. When the user clicks "back", the previous entry is loaded (if there is one). When they click "forward", the next entry is loaded. Any change to the URL, whether the user edits it manually in the address bar or just clicks a link, results in a new history entry being added.

The YUI History Utility provides the ability to create browser history entries without navigating to a new page, and to associate a "state" with each history entry. The state is an object of key/value string pairs that can contain data necessary to restore the client-side state of the page at the time the history entry was added, such as information about an XHR request or about which tab was selected in a JavaScript TabView widget.

Since this state information can be stored in the URL as well, the user can bookmark the URL or send it to a friend, and it will work exactly as they expect it to. This results in rich client-side interactions that feel like a seamless, natural part of the overall browsing experience.

History Adapters

Browsers have varying levels of support for history manipulation, so the History Utility provides several different adapters that provide specialized implementations and share a common API.

Adapter Description
Y.History

An alias that, by default, will automatically point to the best available history adapter that the current browser supports. If the browser supports the HTML5 History interface, then Y.History will be an alias for Y.HistoryHTML5. Otherwise Y.History will be an alias for Y.HistoryHash.

Y.HistoryHash

Creates history entries and stores state by modifying the hash fragment portion of the URL. The hash fragment is the part of the URL that begins with a # character, as in http://example.com/path#foo=bar.

This method of history manipulation is supported by most browsers, but is also the most limited. The state must be an object of key/value string pairs, and there are several other caveats described in the Known Limitations section below.

Y.HistoryHTML5

Uses the new HTML5 History interface, which is currently supported by Firefox 4, Safari 5+, and Google Chrome 5+.

Unlike hash-based history, HTML5 history supports non-string state values such as arrays and objects, and allows custom URLs to be associated with history entries and displayed in the browser's address bar without refreshing the page.

Using the History Utility

History Basics

Instantiating History

Create a new instance of the best available history adapter that's supported by the current browser:

var history = new Y.History();

Alternatively, instantiate a specific adapter if you'd rather not rely on History to select one automatically:

// Always use the HistoryHash adapter, no matter what.
var history = new Y.HistoryHash();

// Or, always use the HistoryHTML5 adapter, no matter what.
var history = new Y.HistoryHTML5();

Specifying an Initial State

To specify an initial or default state, pass a configuration object containing an initialState property to the history adapter's constructor.

var history = new Y.History({
  initialState: {
    kittens: 'fuzzy',
    puppies: 'cute'
  }
});

By default, the initial state for the HistoryHash adapter will be determined from the current URL, while the initial state for the HistoryHTML5 adapter will be empty.

If both the current URL and the initialState config property contain state information, then HistoryHash will give priority to the information in the URL, falling back to initialState for any items that aren't in the URL.

Adding, Replacing, and Getting State Values

Use the add() or addValue() methods to change the state and create a new browser history entry for the new state. The user can then navigate back to the previous state using the browser's back button, and forward again to the new state using the browser's forward button.

The add() method changes several state values at once. By default, the new state is merged into the existing state: new values will override any existing values with the same names, while unchanged values will remain the same.

// Current state:
// {kittens: 'fuzzy', puppies: 'cute'}

history.add({
  kittens: 'cute',
  ferrets: 'sneaky'
});

// New state:
// {kittens: 'cute', puppies: 'cute', ferrets: 'sneaky'}

The addValue() method changes a single state value.

// Current state:
// {kittens: 'cute', puppies: 'cute', ferrets: 'sneaky'}

history.addValue('kittens', 'soft');

// New state:
// {kittens: 'soft', puppies: 'cute', ferrets: 'sneaky'}

To override the default merge behavior and discard the previous state entirely when setting a new state, pass an options object to add() or addValue() and set the merge property to false.

// Current state:
// {kittens: 'soft', puppies: 'cute', ferrets: 'sneaky'}

history.addValue('sloths', 'slow', {merge: false});

// New state:
// {sloths: 'slow'}

The replace() and replaceValue() methods work just like add() and addValue(), except that they replace the current browser history entry instead of adding a new entry.

// Current state:
// {sloths: 'slow'}

history.replace({
  turtles: 'slower',
  snails : 'slowest'
});

// Current (not new) state:
// {sloths: 'slow', turtles: 'slower', snails: 'slowest'}

Use the get() method to get the current state, or the value of a single item in the current state.

history.get();          // => {sloths: 'slow', turtles: 'slower', snails: 'slowest'}
history.get('sloths');  // => 'slow'
history.get('monkeys'); // => undefined

Removing State Values

While it's not possible to remove an entry from the browser history, it is possible to create a new entry (or replace the current entry) and remove one or more state values that were previously set. To do this, add or replace one or more values with null or undefined.

// Current state:
// {sloths: 'slow', turtles: 'slower', snails: 'slowest'}

history.add({
  sloths: null,
  snails: null
});

// New state:
// {turtles: 'slower'}

History Events

The History Utility fires events when the history state changes. Changes can be triggered either by the History Utility's add/replace methods or by a browser navigation action, such as clicking the back or forward button. Subscribe to change events to be notified when the state of your application needs to be updated.

history:change Event

There are several ways to subscribe to History events. The most common is to subscribe to the global history:change event. This event fires whenever the history state changes for any reason, regardless of the source of the change, even if it came from a different History or YUI instance.

Y.on('history:change', function (e) {
  var changed = e.changed,
      removed = e.removed;

  if (changed.kittens) {
    // The "kittens" key was added or changed.
    console.log('kittens were ' + changed.kittens.prevVal);
    console.log('kittens are now ' + changed.kittens.newVal);
  } else if (removed.kittens) {
    // The "kittens" key previously existed, but was removed.
    console.log('kittens were ' + removed.kittens);
    console.log('kittens have escaped!');
  }
});

If you're only interested in changes that are made by one specific History instance and don't want to be notified about changes made by other instances, subscribe to the local change event on the instance.

history.on('change', function (e) {
  // ... handle only local changes ...
});

Property-specific Events

To be notified when a specific state property is added or changed, subscribe to the instance-level [key]Change event, where [key] is the name of the property. To be notified when a state property is removed, subscribe to [key]Remove.

history.on('kittensChange', function (e) {
  // The "kittens" key was added or changed.
  console.log('kittens were ' + e.prevVal);
  console.log('kittens are now ' + e.newVal);
});

history.on('kittensRemove', function (e) {
  // The "kittens" key previously existed, but was removed.
  console.log('kittens were ' + e.prevVal);
  console.log('kittens have escaped!');
});

See the API docs for more details.

Filtering by Event Source

All History event facades include a src property that indicates the source of the event. You can filter on this property to ignore events triggered by sources you don't care about, or to avoid handling duplicate events.

Source Description
Y.HistoryBase.SRC_ADD Event was triggered by a call to add() or addValue() on a history adapter.
Y.HistoryBase.SRC_REPLACE Event was triggered by a call to replace() or replaceValue() on a history adapter.
Y.HistoryHash.SRC_HASH Event was triggered by a change to the URL hash fragment.
Y.HistoryHTML5.SRC_POPSTATE Event was triggered by the HTML5 popstate event.

The following example demonstrates how to handle only events that were triggered by a change to the URL hash, while ignoring events from other sources:

Y.on('history:change', function (e) {
  if (e.src === Y.HistoryHash.SRC_HASH) {
    // ...
  }
});

Extra Functionality Provided by HistoryHTML5

In browsers that support the new HTML5 History Interface, the Y.HistoryHTML5 adapter provides additional functionality beyond what Y.HistoryHash offers.

When adding or replacing a history entry, you may also provide an options object as the second argument to add() and replace(), or as the third argument to addValue() and replaceValue(). It may contain zero or more of the following properties:

Property Description
title User-visible title associated with the history entry. Browsers will typically display this title in a detailed history view or a dropdown menu attached to the back/forward buttons.
url URL associated with the history entry. This will be displayed to the user in the browser's address bar, and will replace the current URL without causing a page refresh. If an absolute URL is specified, the protocol, hostname, and port of the new URL must be the same as the current URL or the browser will raise a security exception (the "same origin" policy applies here just as it does to Ajax requests).

This example demonstrates how to associate a custom title and URL with a history entry:

// Current URL: http://example.com/photos/

history.addValue('kittens', 'cute', {
  title: 'Photos of cute kittens',
  url  : '/photos/kittens?type=cute'
});

// New URL: http://example.com/photos/kittens?type=cute

Custom URLs can be used to allow server-side handling of history states when the user returns to a page, without requiring a page refresh when the history state is created. The HistoryHTML5 adapter doesn't provide any out-of-the-box URL parsing functionality, so additional server-side or client-side code may be necessary to handle custom URLs.

Supporting Google's Ajax Crawling Scheme

One problem with using the URL hash fragment to store history state, as the Y.HistoryHash adapter does, is that search engines typically don't distinguish between a URL with a hash fragment and one without. If your website displays different content depending on a hash-based history state, that content won't be indexed by search engines.

Google's Ajax Crawling Scheme specifies a way to make your hash-based history states crawlable by the GoogleBot with a bit of extra work, and the History Utility can help. Note that the technique described here applies only to the Y.HistoryHash adapter; if you're using Y.HistoryHTML5, you can use custom URLs to achieve the same thing more elegantly.

To indicate to Google's crawlers that your hash URLs are crawlable, the hash must be prefixed by #! instead of the usual #. The History Utility will take care of this automatically if you set the static Y.HistoryHash.hashPrefix property to "!", as in this example:

Y.HistoryHash.hashPrefix = '!';

var history = new Y.HistoryHash();
history.addValue('key', 'value');

// URL is now http://example.com/#!key=value

Next, read Google's getting started guide for a description of how the Ajax crawling scheme works and the additional changes you'll need to make to your application. Most of the work will need to happen on the server, which is out of YUI's hands.

Migrating from the Browser History Manager

Versions of YUI 3 prior to 3.2.0 included the Browser History Manager. In YUI 3.2.0, the Browser History Manager was deprecated and replaced with the new History Utility, which has a new API and differs in several important ways.

Key Differences

  • The Browser History Manager (BHM) requires both an iframe and a hidden form field to exist in the static page markup. The new History Utility doesn't have any markup requirements. The History Utility still uses an iframe for IE6 and IE7 support, but the iframe is created automatically as needed, and does not result in an extra HTTP request.
  • The BHM required history parameters (which it called "modules") to be registered before they could be used, and new modules could not be registered once the BHM was initialized. The new History Utility does not require parameters to be registered.
  • The BHM differentiated between "bookmarked" states (the state of a parameter in the URL hash at the beginning of the pageview) and "current" states (the state of a parameter now). The History Utility does not make this distinction, since the current state is what matters.

API Equivalency: Methods

The table below provides a quick reference to help you translate API methods from the deprecated Browser History Manager to the new History Utility. See the API documentation for details on the new API.

Browser History Manager (deprecated) History Utility (new)
multiNavigate() add()
navigate() addValue()
getBookmarkedState() or getCurrentState() get()
not supported replace()
not supported replaceValue()

API Equivalency: Events

The table below provides a quick reference to help you translate API events from the deprecated Browser History Manager to the new History Utility. See the API documentation for details on the new API.

Browser History Manager (deprecated) History Utility (new)
history:globalStateChange history:change
history:moduleStateChange [key]Change
history:moduleStateChange [key]Remove
history:ready n/a

Known Limitations

  • The HistoryHash adapter uses the hash fragment portion of the URL to store state information. Consequently, there is a limit to how much information can be stored, and this limit is browser-specific. For example, Internet Explorer limits URLs to 2,083 characters including the hash fragment.
  • The HistoryHash adapter can only store key/value string pairs. It can't store non-string values unless those values are first converted to a string format such as JSON. The HistoryHTML5 adapter does not have this limitation.
  • Web browsers never send the hash fragment portion of the URL to the server. As a result, when using HistoryHash some client-side processing is required in order to restore the initial state of a bookmarked URL. It's important to keep this amount of processing to a minimum in order to avoid degrading the user experience. The HistoryHTML5 adapter does not share this limitation, since it can construct full, server-visible URLs that will allow you to restore state on the server.
  • HistoryHash state names and values are case-sensitive in all browsers except Internet Explorer 8 and 9. In most browsers, changing a state item's name or value from "foo" to "Foo" will trigger a change event. However, in IE8 and IE9, this will not trigger a change event because IE ignores the case of the URL hash.
  • Internet Explorer 6 and 7 only retain the most recent history state from a previous pageview after navigating to another page and returning. However, history entries created within a single pageview will persist for the duration of that pageview, and bookmarked URLs will still work in all cases.
  • In Internet Explorer 6 and 7, the page titles displayed for history entries in the browser's history dropdown menu are not correct. Instead of showing the title of each document, it shows part of the URL of each page.
  • Internet Explorer (all versions) replaces the current history entry when the hash portion of the URL is manually edited in the URL bar instead of adding a new history entry as other browsers do. There's unfortunately nothing YUI can do to detect or work around this.