YUI 3: History

Client-side changes to a web page's content and structure are not recorded by the browser's history engine. As a consequence, the navigation buttons (back/forward) we've learned to use as we traverse the World Wide Web of documents don't usually serve us well when we begin exploring the World Wide Web of applications. Bookmarking, too, is problematic in web applications, as the application can change state hundreds of times through the course of a session without any change to the original document's URL. These are significant problems in the current paradigm of web application development.

The History Utility is a utility designed to facilitate the creation of web applications in which the navigation buttons are fully functional and in which broad aspects of an application's state — what panels are open, what tabs are active, etc. — can be bookmarked.

Getting Started

Include Dependencies

The easiest way to include the source files for History and its dependencies is to add the YUI seed file to your page, using the following script tag, and allow the YUI instance to download any additional files which may be required:

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

The YUI instance will automatically pull down History's source files and any missing dependencies when the history module is used. This helps you avoid having to manually manage the list of files needed on your page to support multiple components while also optimizing your initial page weight by loading files only when they are required.

If you do want to include file dependencies manually on your page, the YUI Dependency Configurator can be used to determine the list of files you need to include in order to use History.

The YUI Instance

Once you have the YUI seed file on your page (yui-min.js), you can create a new YUI instance for your application to use and populate it with the modules you need, specified as the first set of arguments to the use method:

  1. // Create new YUI instance, and populate it with the required modules
  2. YUI().use('history', function(Y) {
  3.  
  4. // History available, and ready for use.
  5.  
  6. });
// Create new YUI instance, and populate it with the required modules
YUI().use('history', function(Y) {
 
    // History available, and ready for use.
 
});

The last argument passed to use is a callback function. The callback function will be invoked as soon as the YUI instance is done downloading any required files missing from your page. Once those files are loaded, your local YUI instance will be supplemented with the classes which make up the history module and any modules it depends on. A reference to the populated YUI instance (Y) is passed back to your callback function. Within your callback, then, you can start writing your application code based on your own custom instance of YUI.

For more information on creating instances of YUI and the use method, see the YUI Global object documentation.

Using the History Utility

This section describes how to use the History Utility in further detail. It contains these subsections:

Required markup

The History Utility requires the following in-page markup:

  1. <iframe id="yui-history-iframe" src="path-to-existing-asset"></iframe>
  2. <input id="yui-history-field" type="hidden">
  3.  
<iframe id="yui-history-iframe" src="path-to-existing-asset"></iframe>
<input id="yui-history-field" type="hidden">
 

Notes:

  1. The IFrame is only used to support Internet Explorer prior to version 8, and IE8 in compatibility mode. If the markup is dynamically generated on the server (as a result of running a PHP script for example), you may want to create the IFrame only for the browsers that need it (use server side user agent sniffing)
  2. The asset loaded in the IFrame must be retrieved from the same domain as your page (use a relative path for the src attribute to make sure of that)
  3. This markup should appear right after the opening <body tag.

The asset loaded in the IFrame must be an HTML document. A blank html document will do just fine:

  1. <html><body></body></html>
  2.  
<html><body></body></html>
 

Don't forget to hide the IFrame using the following CSS code:

  1. #yui-history-iframe {
  2. position: absolute;
  3. top: 0;
  4. left: 0;
  5. width: 1px;
  6. height: 1px;
  7. visibility: hidden;
  8. }
  9.  
#yui-history-iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 1px;
    height: 1px;
    visibility: hidden;
}
 

Module registration

A user interface module is simply a visible item on your page with which the user can interact. As such, modules are not immutable and their state is subject to change over time. If you wish to record the history of states visited by a specific module during a single browser session, to allow the user to navigate through that history using the browser's back/forward button, and to bookmark the page with your module in a specific state, you must first register it with the History Utility.

In order to register a module, use the register method of the History Utility, passing in the module identifier (a non-empty string that uniquely identifies your module) and the initial state of your module (i.e. the state of your module corresponding to the earliest history entry within the current browser session) as a string.

Getting the initial state of the module is the trickiest part of this process. Indeed, the user might have previously visited your page and bookmarked it with your module in a specific state. If the user later on comes back to your page using the same bookmark, you will likely wish to restore your module in the same state it was in when the page was bookmarked. To retrieve this "bookmarked state", simply call the getBookmarkedState method of the History Utility, passing in your module identifier:

  1. var bookmarkedState = Y.History.getBookmarkedState("myModule");
  2.  
var bookmarkedState = Y.History.getBookmarkedState("myModule");
 

Of course, you also need a "default state" for your module in case the user accessed the page without using a bookmark. Having determined whether there is a bookmarked state in the code example above (getBookmarkedState will have returned null if there was no bookmarked state), the initial state of your module can be assigned as follows:

  1. // If there is no bookmarked state, assign the default state:
  2. var initialState = bookmarkedState || "defaultState";
  3.  
// If there is no bookmarked state, assign the default state:
var initialState = bookmarkedState || "defaultState";
 

The next step in configuring your module to use with the History Utility is to define what action should be taken when the state of your module changes:

  1. function updateMyModule (state) {
  2. // Update the UI of your module according to the "state" parameter
  3. //...
  4. }
  5.  
function updateMyModule (state) {
    // Update the UI of your module according to the "state" parameter
    //...
}
 

Finally, complete your module registration by calling the register method of the History Utility. This method returns a history module object, which will fire the history:moduleStateChange event whenever its state changes. You should subscribe to this event using the previously defined handler.

  1. // Register "myModule" and subscribe to its "history:moduleStateChange" event
  2. Y.History.register("myModule", initialState).subscribe(
  3. "history:moduleStateChange", updateMyModule);
  4.  
// Register "myModule" and subscribe to its "history:moduleStateChange" event
Y.History.register("myModule", initialState).subscribe(
    "history:moduleStateChange", updateMyModule);
 

Note: You may register as many modules as you want, but you must register at least one module before initializing the History Utility. Modules cannot be registered once the History Utility has been initialized.

Initializing the History Utility

The History Utility initializes itself asynchronously and some of its methods cannot be called until it is fully initialized. You may want to know when this event occurs. In order to do that, subscribe to History Utility history:ready event.

One common task you'll find yourself executing in your History Utility history:ready event handler is retrieving the current state of your module and updating its UI accordingly. Why would the current state of your module be different from its initial state? Remember that the "initial" state of your module corresponds to the earliest history entry. The History Utility history:ready event will be fired every time the user visits the page. If the user comes back to the page using the browser's back button, the state of your module will correspond to the latest history entry — that is the last state of your module before the user left the page. You can retrieve the current state of your module using the getCurrentState method of the History Utility, passing in your module identifier.

The initialization of the History Utility will therefore look like so:

  1. Y.History.subscribe("history:ready", function () {
  2. //...
  3. var currentState = Y.History.getCurrentState("myModule");
  4. updateMyModule(currentState);
  5. //...
  6. });
  7.  
  8. Y.History.initialize('#yui-history-field', '#yui-history-iframe');
  9.  
Y.History.subscribe("history:ready", function () {
    //...
    var currentState = Y.History.getCurrentState("myModule");
    updateMyModule(currentState);
    //...
});
 
Y.History.initialize('#yui-history-field', '#yui-history-iframe');
 

Storing New History Entries

Use the navigate method of the History Utility to store a new state in the browser history, passing in the identifier of the module which state has changed, and a string representing the new state of the specified module (if you wish to modify the state of several modules in one single history entry, use the multiNavigate method instead)

  1. Y.History.navigate("myModule", "newState");
  2.  
Y.History.navigate("myModule", "newState");
 

Known Limitations

  • The History Utility uses the URL fragment identifier to store state information. This means that there is a limit to how much information can be stored. Indeed, each browser has a built-in limit to how long a URL can be, and this limit is browser dependent. For example, on Internet Explorer, this limit is 2083 characters. Also, keep in mind that this limit applies to the entire URL, not just the fragment identifier.
  • A string is a one-dimensional way of saving state information. It is possible to save multi-dimension state information (such as the collapsed/expanded state of each node of a treeview) into a one-dimensional string. However, the maximum length of a state identifier limits this to comparatively simple cases.
  • Web browsers never send the URL fragment identifier to the server. This means that some client-side processing is required in order to handle bookmarks. It is important to keep this amount of processing to a minimum in order not to degrade user experience. However, if an additional HTTP request at startup is not an issue, it is possible to send the URL fragment identifier back to the server and have the server do some processing instead (such as generating the appropriate markup)
  • All browsers will forget part or all of the history if the page gets forcibly refreshed. This will happen on Internet Explorer by pressing Ctrl+F5, and on Firefox by pressing Ctrl+F5 or Shift-Ctrl-R.
  • On Internet Explorer, the title of the documents added to the history, listed in the browser's history drop down menu, is not correct. Instead of showing the title of each document, it shows part of the URL of each page.

Support & Community

Forums & Blog

YUI 3 discussion forums are hosted on YUILibrary.com.

In addition, please visit the YUIBlog for updates and articles about the YUI Library written by the library's developers.

Filing Bugs & Feature Requests

The YUI Library's public bug tracking and feature request repositories are located on the YUILibrary.com site. Before filing new feature requests or bug reports, please review our reporting guidelines.

Copyright © 2010 Yahoo! Inc. All rights reserved. Copyright | Privacy Policy | Terms of Use | Job Openings