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.
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.18.1/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.
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.
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.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 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. |
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();
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.
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
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'}
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.
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 ... });
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.
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) { // ... } });
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.
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.
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.
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() |
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 |