Developing Ajax Applications That Preserve Standard Browser Functionality

by Mark Schiefelbein
01/24/2006

The Basic Design of Supporting the Back Button in Ajax

In this section, I discuss the basic steps you need to take to support the back button in Ajax applications. I include a simple code sample that illustrates the necessary steps. More complete discussions of how to implement back-button support in a cross-browser-compatible way are already available. Two articles I particularly like were written by Mike Stenhouse (Content with Style) and more recently Brad Neuberg (OnJava).

My simple sample application, shown in Figure 1, will be a select box with two values: "Year 1" and "Year 2." For the purposes of this article, I will track history when the value of the select box is changed. This means that a user may first select "Year 2" and then click the back button to be taken back to the previous selection.

Figure 1. A simple sample application with the select box

The starting point for the sample application is a simple HTML form with a JavaScript getter and setter for the select box values:

<html>

<head>
<script language="JavaScript" type="text/JavaScript">
function reportOptionValue()
{
  var myForm = document.make_history;
  var mySelect = myForm.change_year;
  return mySelect.options[mySelect.selectedIndex].value;
}

function setOptionValue(value)
{
  var myForm = document.make_history;
  var mySelect = myForm.change_year;
  mySelect.options[value-1].selected = true;
}
</script>
</head>

<body>
<form name=make_history>
  <select name=change_year>
    <option value="year_1">Year 1</option>
    <option value="year_2">Year 2</option>
  </select>
</form>
</body>

</html>

I'll first tackle requirement 1: Create a history of states. As mentioned above, the requirement consists of the following three steps:

  1. Create history
    1. Save meaningful state
    2. Generate a corresponding URI
    3. Push the URI onto the browser stack
  2. The state I want to save is every change to the select box. Therefore I'll create a new URI that includes the select box state information.

    To remain compliant with Internet standards, I'll use the fragment identifier part of the URI. As stated in the IETF RFC 3986, " ...Fragment identifiers have a special role in information retrieval systems as the primary form of client-side indirect referencing, <...> the fragment identifier is separated from the rest of the URI prior to a dereference, and thus the identifying information within the fragment itself is dereferenced solely by the user agent, regardless of the URI scheme...."

    Using the fragment identifier, I can create an "Ajax-URI," composed of a client-side and a server-side part, separated by the hash ("#") sign.

    JavaScript provides the window.location() function to update the browser's history and address with a URI. In addition, you can directly access the fragment identifier with window.location.hash().

    In the following snippet, you can see how I have extended the code with an onchange event handler on the select box that updates the browser history and address bar with an "Ajax-URI":

    <html>
    
    <head>
    <script language="JavaScript" type="text/JavaScript">
                           
    function makeHistory(newHash)
    {
      window.location.hash = newHash;
    }
    
    function reportOptionValue()
    {
      var myForm = document.make_history;
      var mySelect = myForm.change_year;
      return mySelect.options[mySelect.selectedIndex].value;
    }
    
    function setOptionValue(value)
    {
      var myForm = document.make_history;
      var mySelect = myForm.change_year;
      mySelect.options[value-1].selected = true;
    }
    </script>
    </head>
    
    <body>
    <form name=make_history>
      <select name=change_year 
    
                          
    onchange=
          "return makeHistory(reportOptionValue())">
        <option value="year_1">Year 1</option>
        <option value="year_2">Year 2</option>
      </select>
    </form>
    </body>
    
    </html>

    As Figure 2 shows, the browser address is now updated with every change to the select box. Please note that there are some issues with Internet Explorer (IE) that require using a hidden frame to get the correct behavior. You are again referred to Mike Stenhouse's or Brad Neuberg's articles for complete details.

    Figure 2. The history stack is updated on state change

    You now have an event handler that creates a new URI when the value of the select box is changed. The new URI uses the fragment identifier to store the information to recreate the previous state. With this you can move on to the next requirement:

  3. Restore history
    1. Detect URI change
    2. Recreate the state from a URI

In Step 1, I updated the URI client side through the window.location.hash() function. The call does not result in a server roundtrip or a page refresh. Instead, I need to handle the URI change the Ajax way (client side).

I'll first add a polling function that will regularly check the URI in the browser history. I'll register pollHash() in the onload event of the page and then re-execute it every 1,000 milliseconds.

The polling function will call the function handleHistory(), which checks whether the URI has changed since the previous check. I do this using the global variable expectedHash.

The final piece is to determine whether the URI has changed because of the event handler on the select box or because the end user clicked the back button. I do this by setting expectedHash accordingly in the event handler of the select box.

<html>

<head>
<script language="JavaScript" type="text/JavaScript">
                         
var expectedHash = "";

function makeHistory(newHash)
{
  window.location.hash = newHash;

                       
expectedHash = window.location.hash;
  return true;
}

function reportOptionValue()
{
  var myForm = document.make_history;
  var mySelect = myForm.change_year;
  return mySelect.options[mySelect.selectedIndex].value;
}

function setOptionValue(value)
{
  var myForm = document.make_history;
  var mySelect = myForm.change_year;
  mySelect.options[value-1].selected = true;
  return true;
}

                         
function handleHistory()
{
  if ( window.location.hash != expectedHash )
  {
    expectedHash = window.location.hash;
    var newoption = expectedHash.substring(6);
    setOptionValue( newoption );
  }
  return true;
}

                         
function pollHash() {
  handleHistory();
  window.setInterval("handleHistory()", 1000);
  return true;
}
</script>

</head>

<body language="JavaScript"

                        
onload="return pollHash()">
<form name=make_history>
  <select name=change_year 
    onchange="return makeHistory(reportOptionValue())">
    <option value="year_1">Year 1</option>
    <option value="year_2">Year 2</option>
  </select>
</form>
</body>

</html>

This concludes my simple example that shows how to record state in a URI, push that URI to the browser history track, detect an address change from the back button, and finally recreate the required state.

The example is still lacking several features such as:

  • Support for IE by using a hidden iframe
  • More solid URIs (the sample only works for select boxes with fewer than 10 options)
  • Registration of the initial state on construct

Complete handling of all traditional Web usability features in a robust way that works across all browsers is not easy to implement. An alternative approach is to use an Ajax toolkit with built-in support for these features.

In the following section, I describe how the Backbase Ajax engine provides these features. I do so by looking at the implementation of the Ajax forum on the Backbase DevNet.

Oracle Chatbot
Disconnected