Google TV

Using Location Hash To Enable BACK/FORWARD Navigation

Written by Daniels Lee, Developer Relations
Published: June 2011
  1. Overview
  2. Context: Why is this important?
  3. Concept: Understanding app states
  4. Manipulating the Browser Location Hash
    1. Getting Location Hash
    2. Setting Location Hash
    3. Detecting Location Hash Changes
  5. Using Location Hash To Enable Persistent State
  6. Using Location Hash With HTML
    1. HTML Demo
  7. Using Location Hash With ActionScript3
    1. JavaScript Components
    2. AS3 and JavaScript Communication Bridge
    3. Putting It All Together
    4. Flash Demo

Overview

This article discusses how to tap into your browser's location history to enable backward navigation support and why it's important to do so. You'll learn how to enable use of the browser BACK event and protect your users from inadvertently being redirected away from your web app. We'll cover and provide techniques around how to do this within HTML and in ActionScript3.

Back button remote

Context: Why is this important?

Imagine the following scenario.

You're using Google TV to discover content to consume. You browse the Spotlight Gallery and select a web app. Your browser loads the web app and launches into a full-screen 10-foot user interface. To navigate the app, you use the D-Pad on the remote, moving the selection cursor around the interface, and highlighting various elements in search of interesting content. You see something you like, navigate the cursor to it, and hit "OK." The browser begins loading the content, but you quickly realize this isn't the content you're looking for. You want to go back to the previous screen to find something else and hit the "BACK" button on the remote. To your dismay, the browser brings you back to Spotlight Gallery instead of the previous App screen.

What happened? Take a look at the diagram below.

Bad state diagram

The browser creates new entries in its location history whenever certain actions are performed. One action is when you navigate from one website to another. In the scenario above, only two entries were created in the browser's location history. The first location record was created when browsing to Spotlight Gallery. The second record was created when navigating to one of the web apps listed in Spotlight. Within the app itself, no new location entries were created. As a result, whenever the browser BACK event is triggered, you're redirected back to Spotlight Gallery since it was the most recent location recorded in the browser history. As a user, if you drilled deep into the subsections within the app, all your progress gets lost when this happens. You'll have to reload the web app and work your way back through the app again just to restore your state.

This is a common pitfall whenever developing single-screen interfaces equipped with AJAX loaders in the background to asynchronously refresh data between states. There is no longer a 1-1 relationship between HTML pages and the many states which can exist in any given web app. Fortunately, browsers allow us to work around this problem by manually creating new entries in its location history. By doing so, you can create artificial app states throughout your web app and enable the use of browser BACK events to navigate backwards to a previous app state. Leveraging this ability can help you improve user experience by protecting users from losing their state when hitting the browser BACK key.

Keep in mind, creating too many app states for users to navigate backwards can also hurt the user experience. The key here is balance and following the wisdom of "less is more" in your design. Don't create a new app state for every user action. Do it only for major state changes.

Back to top

Concept: Understanding app states

In the previous section, we've covered what an undesirable scenario looks like. Let's shift focus to what a more optimal and desirable scenario would look like instead.

Optimal state diagram

Notice the additional state (State 3), which is created by adding a new location to the browser history from tv.app.com to tv.app.com#view. Now there are three location entries in the browser history. Triggering the browser BACK event from tv.app.com#view (State 3) takes you back to tv.app.com (State 2) whereas otherwise, you'd be redirected out of the app and back to Spotlight (State 1). The app knows when these location changes occur and restores the interface back to its previous state.

This is a better user model and experience, and this is what we want to help you achieve.

App states are nothing more than new location entries in the browser history. Therefore, you can theoretically create as many new states as you think is necessary. The updated diagram below depicts this concept, where N represents a number from 1 to infinity:

Infinite state diagram

Though it's possible to create an infinite number of app states via new location entries in the browser history, doing so may severely degrade the user experience as a result. Imagine you're browsing a photo album web app on Google TV. You select an album and enter a slideshow with hundreds of photos. A new app state is created as you flip through each photo. To get back to the top-level album view, you try hitting the BACK button on the browser only to see the app load the previous photo. You're now on photo 32 => 31 => 30 => 29 => ... Let the BACK button mashing begin.

The ability to create an infinite number of app states with new entries in your browser location history is a powerful tool. However, with great power comes greater responsibility. Take a moment and be smart about where and when it makes sense to create new states in your app. Your users will thank you!

Back to top

Manipulating the Browser Location Hash

Each time the browser location hash is updated, a new entry is created in its navigation history. You can try this now on this current webpage you're viewing. Try appending a hash character at the end of the URL, or simply click on this link. Hit the browser BACK button, and you'll see the browser returns to its previous location entry. In conclusion, each time the browser encounters a new hash tag at the end the URL, a new location entry is added to its history. This is how new states can be created in your app.

There are three main operations required to enable state persistence in your Google TV web apps.

  1. READ: Getting and parsing the location hash value
  2. WRITE: Updating the location hash with your own values
  3. LISTEN: Detecting when location hash value changes

All of these actions can be performed using JavaScript. For ActionScript3 developers, you'll be relying on ExternalInterface to call these JavaScript commands from your Flash app.

Getting Location Hash

To read the browser location hash, use JavaScript to read the window.location.hash property. The first character will always be the # character, which we can safely discard.

var hashValue = window.location.hash.substring(1);

For best practice, I recommend creating a JavaScript function to do this:

/**
 * Returns the value of the location hash.
 * @return {string} Hash value with '#' prefix discarded.
 */
function getLocationHash () {
  return window.location.hash.substring(1);
}

Setting Location Hash

To update the browser location hash programmatically from your app, use JavaScript to set the window.location.hash property like so:

window.location.hash = 'newstate';

Again, for best practice, I recommend creating a JavaScript function to do this:

/**
 * Updates the location hash with the specified string.
 * @param {string} str
 */
function setLocationHash(str) {
  window.location.hash = str;
}

Detecting Location Hash Changes

The browser conveniently fires off an event whenever the location hash has been updated or changed. Set up an event handler using JavaScript to listen for this event:

/**
 * Listen for location hash changes.
 * @param {Event} e HashChangeEvent object
 */
window.onhashchange = function(e) {
  // Do something whenever location hash value changes
  // [MORE CODE HERE]
  // ...
};

Back to top

Using Location Hash To Enable Persistent State

To configure your web app to enable persistent state tracking, you need to understand the two main sequences of actions which may occur. The diagram below illustrates these two sequences denoted by the red and green arrows.

Location hash flow diagram

Sequence A: Red

  1. User engages with app
  2. App updates location hash to create new state
  3. Hash update fires event and calls handler function
  4. Handler function does nothing

Sequence B: Green

  1. User updates URL or moves BACK/FORWARD in browser history
  2. Hash update fires event and calls handler function
  3. Handler function updates App
  4. App is updated and does nothing more

Caution: Notice the potential risk for a circular loop to occur from the hash change event handler. When location hash is updated, the hash change event handler updates the app. If your app was the source of the hash change, it's redundant for the handler to update the app again.

In the sections below, we'll show examples of how to protect your app from this using a simple boolean flag in JavaScript.

Back to top

Using Location Hash With HTML

To begin tracking app state in your HTML web app, start by defining functions to perform the three different actions required to read, write, and detect state. I've added some comments below to provide more context about each respective function.

/**
 * Called to change the state of my app based on specified value.
 */
function updateMyApp(value) {
  // Do something cool
  ...

  // Track state by updating location hash
  setLocationHash('state-N');
}

/**
 * Returns the value of the location hash.
 */
function getLocationHash () {
  return window.location.hash.substring(1);
}

/**
 * Called by my app to update location hash.
 */
function setLocationHash(str) {
  window.location.hash = str;
}

/**
 * Listen for hash changes.
 */
window.onhashchange = function(e) {
  updateMyApp(getLocationHash());
};

The function updateMyApp can be invoked in primarily two ways. One is by the user, as they interact with the app such as the selection of a button or item. The other is triggered by a browser event, when the location hash value changes and fires the onhashchange event. Whenever one of these actions occur, the app is updated. However, we only want to update the location hash to save state for the former case, where the action is invoked explicitly by the user. Otherwise, we could lead to a case where the browser event triggers the onhashchange event, proceeds to update the app, which in turn updates the location hash, and triggers the onhashchange event again.

The easiest way to protect ourselves from this is to add a single boolean flag to prevent this circular loop from occurring. It makes most sense to define this flag in the global namespace so it can be accessible from inside the onhashchange event handler function. Give it a descriptive name so you can easily remember what it does.

/**
 * Flag tells my onhashchange event handler whether my app should be updated
 * whenever hash value changes. Enabled by default. If hash change comes from
 * my app, use this flag to stop my onhashchange event handler from executing.
 * @type boolean
 */
var allowHashToUpdateApp = true;

Then add the following logic to your onhashchange handler function to only call your updateMyApp function if this flag has enabled. If the flag has been disabled, do nothing other than resetting the flag to its default state of true.

/**
 * Listen for hash changes. Use flag to determine whether hash changes should
 * propagate updates to my app.
 */
window.onhashchange = function(e) {
  if (allowHashToUpdateApp) {
    updateMyApp(getLocationHash());
  } else {
    allowHashToUpdateApp = true;
  }
};

Now we can easily enable or disable the event handler function from updating the app after the location hash is updated. The setLocationHash function is only called upon to update location hash in response to some user engagement with the app. We can thus safely assume that whenever this function is called, the update request came from the app itself and doesn't need to be updated again. We need to modify our setLocationHash function to always prevent the onhashchange event handler from propagating changes to the app.

We accomplish this by adding a line in our function to always set our handy flag to false before updating the location hash property.

/**
 * Called by my app to update location hash. Hash changes made through
 * this function should always be ignored by the hash change event handler.
 */
function setLocationHash(str) {
  // Tell the event handler to ignore this change since its manually updated.
  allowHashToUpdateApp = false;

  window.location.hash = str;
}

HTML Demo

We've put together this demo to demonstrate a simple web app, which pulls all these techniques into action and enables backward/forward browser navigation between app states. Be sure to view the source code in order to more closely inspect the logic underneath. I've included a screenshot below to entice you to check it out.

HTML demo screenshot

Back to top

Using Location Hash With ActionScript3

This section is for Flash developers who are writing full-screen web apps using AS3. You can leverage the same techniques described in the previous section with a small addition to enable your Flash app to communicate with JavaScript methods defined in its parent container. AS3 allows Flash apps to call functions defined in JavaScript functions and vice versa using a library called ExternalInterface. Since we have this capability, we can leverage the same set of JavaScript functions defined in the previous section and rely on them to interact with the browser location history.

JavaScript Components

Define the following JavaScript functions within the HTML container of your Flash app. These are responsible for executing the three main operations (READ, WRITE, LISTEN) required to enable state persistence.

/**
 * Flag tells my onhashchange event handler whether my app should be updated
 * whenever hash value changes. Enabled by default. If hash change comes from
 * my app, use this flag to stop my onhashchange event handler from executing.
 * @type boolean
 */
var allowHashToUpdateFlash = true;

/**
 * Returns the value of the location hash.
 */
function getLocationHash () {
  return window.location.hash.substring(1);
}

/**
 * Called by AS3 Flash app to update location hash. Hash changes made through
 * this function should be ignored by the event handler.
 */
function setLocationHash(str) {
  // Tell event handler to ignore this change since we manually updated it.
  allowHashToUpdateFlash = false;

  window.location.hash = str;
}

/**
 * Listen for hash changes. Use flag to determine whether hash changes should
 * propagate updates to my Flash App.
 */
window.onhashchange = function(e) {
  if (allowHashToUpdateFlash) {
    // Send hash value to my Flash app
    // [CODE TO UPDATE MY FLASH APP]
  } else {
    allowHashToUpdateFlash = true;
  }
};

Under the window.onhashchange function, note the bolded placeholder comment where we'll later on insert the command to call an AS3 function, which will update my Flash app.

AS3 and JavaScript Communication Bridge

Using ExternalInterface, it's easy to create a bridge between your Flash app and the JavaScript functions defined in its HTML container. Once set up, you'll be able to reliably call AS3 functions using JavaScript and vice versa. To enable this feature, you need to do two things.

First, include the following two lines at the head of your AS3 script:

import flash.external.ExternalInterface;

flash.system.Security.allowDomain('EXAMPLE.COM');

Make sure you replace EXAMPLE.COM with the domain where your Flash app is hosted.

Second, make sure you set the allowFullScreen parameter to true in the HTML container which embeds your SWF application.

<object>
  <param name="allowFullScreen" value="true" />
  <!-- [other params] -->

  <embed src="my_flash_app.swf"
    allowFullScreen="true"
    type="application/x-shockwave-flash"
    other_attributes="">
  </embed>
</object>

Once this is set, you can now call any JavaScript functions defined in the HTML container from within your Flash app via AS3 by using the ExternalInterface.call() function. Here's an AS3 example which uses this function to call my JavaScript function called getLocationHash(), which returns the value of my browser's location hash.

// Read location hash from browser by calling a JavaScript method.
var hash:String = ExternalInterface.call('getLocationHash');

To enable communication going the other way, use the ExternalInterface.addCallback() function to register any AS3 functions you want to allow being called via JavaScript. Here's an AS3 example which registers a function called updateApp, which is responsible for updating my Flash app based on the hash value passed in.

// Registered function exposed to HTML container via ExternalInterface
private function updateApp(hashValue:String):void {
  // Do something fantastic
}

// Register function to listen for hash state changes.
ExternalInterface.addCallback('updateApp', updateApp);

With this AS3 function registered, you can now call this function from JavaScript by calling it against the document's SWF object. Here's a corresponding JavaScript example, which calls the registered function from above.

/**
 * SWF object used to call methods defined in AS3 by ExternalInterface.
 * @type Object
 */
var swfObj = document['main'];

// Send the location hash to my AS3 function
swfObj.updateApp(getLocationHash());

Putting It All Together

Now, we have all the tools we need to interact with the browser location history from within my Flash app. I now have the ability to expose an AS3 function, which I can call from within my onhashchange event handler whenever the location hash value changes. I also have the ability to READ and WRITE to the location hash by calling the respective JavaScript functions, getLocationHash() and setLocationHash().

Here's the updated JavaScript snippet of the onhashchange event handler, which now sends these updates to my AS3 function to process.

/**
 * SWF object used to call methods defined in AS3 by ExternalInterface.
 * @type Object
 */
var swfObj = document['main'];

/**
 * Listen for hash changes. Use flag to determine whether hash changes should
 * propagate updates to my Flash App.
 */
window.onhashchange = function(e) {
  if (allowHashToUpdateFlash) {
    swfObj.updateApp(getLocationHash(), false);
  } else {
    allowHashToUpdateFlash = true;
  }
};

Back to top

Flash Demo

Take a look at this Flash demo. It's an exact replica of the HTML demo from the above section with the only difference being that the app update logic is executed in AS3 instead. I developed this example using Adobe Flex Builder 3 and included the source code as a Flex Builder project archive for you to download and inspect. I've included a screenshot below and encourage you to check it out.

Flash demo screenshot

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.