Managing GWT History and Hyperlinks Tutorial

Posted by

Introduction

When you are building GWT apps, that run in the context of a web browser, what should happen when the user of your app presses the Back or Forward button in their browser? GWT provides a way for your apps to hook into the browser’s history mechanism, so that you can control what happens when a user hits Back or Forward in their browser. You can also programmatically manipulate the browser’s history, and even create hyperlinks in your apps that can hook into the browser’s history mechanism. You can even intercept these hyperlinks when a user clicks on them, instead of having the browser handle it, or both. This tutorial will show you how to leverage GWT’s history mechanism and do some creative things with histories and hyperlinks that will be useful in your applications.

If you are unfamiliar with GWT basics, please refer to our GWT Intro, Anatomy of a GWT Project, and Hello World tutorials. To learn more about GWT, I recommend reading these good books – GWT Solutions, GWT Applications, AJAX Security.

Background

We are going to focus on two classes in this tutorial: the History class and Hyperlink class. GWT makes it very easy to integrate with the browser’s history. You can do one of two things:

  1. Respond to the user pressing the Back or Forward button, by changing the state of your app or do nothing in response.
  2. Manipulate the browser’s history by programmatically invoking the Back and Forward buttons, in addition to adding new items to the browser’s history.

The history stack

GWT maintains browser history as a stack of tokens (identifiers that you define). These tokens allow you to map a specific state of your application to what’s in the browser’s history stack. When your app starts up, the stack is empty. When the user interacts with your app, and clicks on something, you can add tokens to this stack (either via hyperlinks or calls to History.newItem(String token)). This lets you change the history. You can also attach a HistoryListener to your app which will allow your app to respond to this change of history. Why do it this way? I mean, if your app generates the tokens themselves, why not just change the state instead of dealing with receiving a history change notification (that you just initiated) and then respond to it? A good reason for taking the trouble to do this, is to allow people to bookmark certain states of your application. If you don’t want them to do this, then you can just plug into the history mechanism, and then ignore all the history state change events, and make it so that the Back or Forward button does not mess your app up.

Back, Forward and the stack

If there are tokens in the history stack, and the user presses the Back button, or you invoke the History.back() method, then the following steps occur:

  1. a token is retrieved from the top of the stack
  2. this token (which is just a String) is then passed to your HistoryListener implementation, and this is how you know that the Back button is pressed. You then have to parse that token and change the state of your application to match what this token represents to your app.

Note that If there are many tokens on the stack, then the user will be able to keep hitting Back until there are no more previous tokens on this stack. In the case where the user has pressed Back a few times, they are now able to press Forward just as many times in order to up the stack of tokens.

You can also add items to this history stack, by using a Hyperlink that you create with a token as a parameter. When the user clicks on this Hyperlink object, it will add this token to the top of the browser’s stack, and your HistoryListener will be invoked with this new token. Your app will not be reloaded in the browser, it will just continue processing from where it was before the HistoryListener’s onHistoryChanged(String token) was called.

Confused?

It seems complicated to think about all the different ways in which a user can change the browser’s history stack, etc. but the important thing to note is no matter what happens, all these Back/Forward actions (whether initiated programmatically by your app, or by a user) ends up calling your HistoryListener’s onHistoryChanged(String token) method, which can act as a traffic cop of your state machine. You are in control of defining what a token is, and how it should map to various states in your application. You can encode parameters in the token and then parse them out later, to understand what your app is supposed to do now.

Hyperlinks

When you create a hyperlink that simply adds a new token on to the history stack, you can create it by doing this:

Hyperlink internalLink = new Hyperlink(“link2”, “link2token”);

In this example “link2” is displayed as a hyperlink in the browser, and “link2token” is the browser history token that’s passed to your HistoryListener when that link is clicked by the user. Note that when a user presses this hyperlink, the browser’s displayed URL will change, and there will be a “#link2token” attached at the end of it. Also, once the link has been clicked, and your app is in the new state, further clicks on this hyperlink will not result in any new actions! Your app will already be in “link2token” state, so there’s no need for GWT to fire any more onHistoryChanged() methods on your HistoryListener.

Browser URL – #token

If you noticed the change in the browser’s displayed URL, when a hyperlink with a history token is clicked, then can see how the user is able to bookmark a specific state in your app… the token is simply added to the URL in the browser prefixed with a “#”. So, is it possible to add your own history token by typing in the URL for your app and just appending “#mynewtoken” to it? Yes! This is how GWT handles people bookmarking specific states of your application. One thing to keep in mind is that if you end the browser session, and then come into the app, then your app will have to process the initial history state, and it will have to check to see if there is “#mynewtoken” passed in the URL to the browser, and then adjust it’s state accordingly, you will see this in the code example below.

One more thing to note is that if you just type in a new token in the URL and tell your browser to load it, then it will load a new instance of your application. This is different than pressing a hyperlink, which re-uses the same instance of your app in the browser. In the example below, try this and note when the app loading screen shows up, vs when it doesn’t, and it will give you more clues to the nuances of how all of this works in browsers.

What can I do with these features?

Since you are building a dynamic web application, that can manipulate your browser’s DOM without having to reload a page, what should happen when the user presses the Back/Forward button, or when they bookmark a page and want to come back to it? Well, the answer is whatever you want it to do! You could choose to ignore whatever is passed in the #token as part of the URL, and just maintain some other kind of state management in your app, so that regardless of Back/Forward, or whatever token is passed as part of the URL, your app does it’s own thing. Alternatively, you can hook into this mechanism and make it possible for the app to go to a particular state or load a particular resource or perform a function based on what is passed as a token to the URL.

You can also use this feature as a way to pass parameters, instead of using key value pairs as part of the URL passed to the browser. I don’t recommend doing both. If you embed a GWT app that uses browser history in an existing web page, you will see the #token show up in the browser URL when you add a token to the history stack (programmatically or via Hyperlink click). You can use pass parameters to your app via the URL (here’s a coding quickie that shows you how), and you can use it along with tokens, but it’s bit confusing to use both.

Example

We can finally get to the code example that will show you how to get started using History. I have built a very simple shell of an application that you can extend to make it do what you want. I will use a minimalist example so that I can stay focused on showing you how History and Hyperlinks work.

This example will create a simple Panel to which we will add a few hyperlink objects. A debug message will also be displayed when the history changes, and that’s pretty much all there is to it. Let’s get to it.

The first thing that’s different about creating a GWT app that uses history is that you have to insert an iframe in your HTML host page:

This is used by the GWT implementation to do it’s magic when you are switching states. You have to include this HTML as is in your host page, otherwise, History will not work. You can place it anywhere on the host page, but just make sure that it’s there. Here’s the HTML host page for our example:

<html>
<head>
</head>

<body>

<script type="text/javascript" language="JavaScript" src="Sample.Client.nocache.js"></script>

<!-- include this if you want history support -->
<iframe id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe>

<p class="apptitle">Browser History Demo</p>

<!--this is where the loading message goes-->
<div id="loading">
  <img src="loading.gif">Loading app...
</div>

<!--this is where the app will go-->
<div id="appdiv"></div>

</body>
</html>

The entry point class for this example just adds a MainPanel widget to the DOM and removes the loading div (which shows the splash screen, while your app is loading):

package Sample.client;

import Sample.client.support.*;
import Sample.client.ui.*;

/** Entry point classes define <code>onModuleLoad()</code>. */
public class EntryPoint implements com.google.gwt.core.client.EntryPoint {

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// data
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
private MainPanel panel;

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// main method
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

/** This is the entry point method. */
public void onModuleLoad() {

  // create the main panel
  panel = new MainPanel();

  // wire up it's ability to deal with browser history
  panel.initHistorySupport();

  // add the panel to the browser dom
  GWTUtils.addWidgetToDOM("appdiv", panel.getWidget());

  // finally, remove the splash screen/loading msg
  GWTUtils.removeElementFromDOM("loading");
}


}// end EntryPoint

Note that there is a call to panel.initHistorySupport(). This call registers the MainPanel’s HistoryChangeListener with GWT, so that it’s notified of any token/state changes in the future. Also, as part of this initialization, the code checks to see if any token has been passed to this URL already! This is a critical check to perform, if you want to allow people to bookmark the URL of your app and then have them come back to where they left off, when they load this bookmark at a later time. Here’s the code to make all of this happen (in MainPanel.java):

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// history mgmt
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

public void initHistorySupport() {
  // add the MainPanel as a history listener
  History.addHistoryListener(this);

  // check to see if there are any tokens passed at startup via the browser's URI
  String token = History.getToken();
  if (token.length() == 0) {
    onHistoryChanged(INIT_STATE);
  }
  else {
    onHistoryChanged(token);
  }
}

/**
 * this method is called when the MainPanel's contents must be changed due to a state change.
 * in the context of this app, a state change represents a new URL being loaded for the RSS reader.
 */
public void updateState(String token) {
  if (token.equals(INIT_STATE)) {
    lbl.setText("There is no token passed in the browser URL");
  }
  else {
    lbl.setText("Got a new browser history change token:" + token);
  }
}

/**
 * this method is called when the fwd/back buttons are invoked on the browser.
 * this is also invoked when hyperlinks (generated by the app) are clicked.
 */
public void onHistoryChanged(String historyToken) {
  // todo - when the token is passed, do something to update the state of your app
  updateState(historyToken);
}

Note that when the browser state is changed, the updateState(String token) is called on MainPanel – this is where you would put code in your app to actually do something to change the state. In this trivial example, it just displays a message to the browser.

Here’s the rest of the code in MainPanel, showing you the simple UI elements that are added to the browser:

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// constants
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
public static final String INIT_STATE="initstate";

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// data
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
private Label lbl;
private VerticalPanel panel;
private Hyperlink link1, link2;

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// constructor
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

public MainPanel() {
  // create UI components and lay them out
  lbl = new Label("boo");

  panel = new VerticalPanel();
  panel.setStyleName("panelstyle");

  link1 = new Hyperlink("link1", "http://devlife.com/?rss=sfkj");
  link2 = new Hyperlink("link2", "link2token");

  panel.add(lbl);
  panel.add(link1);
  panel.add(link2);
}

public Widget getWidget() {
  return panel;
}

Note that the Label lbl is initialized with “boo”. You should not be able to see this when the app loads, since it’s immediately replaced in the upateStatus() method with the name of the token that’s passed as part of the browser URL (if any).

Click here to see this demo in action.

When you are playing with the demo, try the following:

  • Type in some random string at the end of the URL like “#whatever”, and load the page to see what happens.
  • Click on the hyperlink, see the message in the label change, and then click on the same hyperlink again, nothing should happen.
  • Bookmark the URL after you’ve clicked on a hyperlink, close the browser, then load the bookmark to see what happens.

Download the example

You can download the example covered in this tutorial here.

Mixing URL params and history tokens

This section describes what would happen if you set a history token/pass it in the URL and also set key/value pairs in URL params. By the way, there is code in the GWTUtils.java class in the example that will extract the URL params for you and give you access to them in a HashMap:

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// native js helpers
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
public static native String getParamString() /*-{
        return $wnd.location.search;
}-*/;

public static HashMap parseParamString(String string) {
  String[] ray = string.substring(1, string.length()).split("&");
  HashMap map = new HashMap();

  for (int i = 0; i < ray.length; i++) {
    GWT.log("ray[" + i + "]=" + ray[i], null);
    String[] substrRay = ray[i].split("=");
    map.put(substrRay[0], substrRay[1]);
  }

  return map;
}

Anyway, let’s use this URL as an example: http://localhost:8888/Sample.Client/index.html#link2token?p1=v1&p2=v2

When you run the app with this URL, you will get the following string as the token: “link2token?p1=v1&p2=v2“. So as you can see it’s not a great idea to mix the two. This is the kind of URL that’s generated by the GWT Hyperlink class, so you can’t really mix and match query strings with history tokens.

Manually create a URL

If you want to create the URL yourself, then you can pass history tokens and query strings. In order to do this, you have to make sure to place the query string (?p1=v1&p2=v2) before the “#” in the URL and it will work.

If you use this URL as an example: http://localhost:8888/Sample.Client/index.html?p1=v1&p2=v2#link2token

When you run the app with this URL, you will get the following string as the history token: “link2token“. And a call to GWTUtils.getParamString() will return “?p1=v1&p2=v2“.

Feedback and comments

If you would like to share your thoughts with me on this tutorial, click here.