History and the Back Button

A common ailment in AJAX applications is support for the back button, history, and bookmarks. The user selects an item in a list, expands a treelist, or switches to a different tab in a notebook, but when they navigate away and return, they're back where they were when the page first loaded. This can be highly disconcerting to the user, depending on how rich of a UI has been presented. Similarly, forwarding links to others or adding them to bookmarks yields surprising results.

The problem is that state is generated in the browser that is not retained on the server, so page navigations lose it. Jitsu's solution to this problem is first to clearly delineate between state that is data, and state that is view-specific, or view state. We then provide a system service for view state management that allows views to save and retrieve this significantly smaller state across page navigations.

For most users of controls, this service is completely transparent. Simply using the controls will give you automatic back button support. If you are writing a control, and it retains some state independent from the data retrieved from the server, you will want to make use of a simple api to enable automatic view state management.

How It Works

Jitsu's view state is serialized, and then embedded in the hash component of the page's URL. So when you select an item from a list, or switch to a new page on a carddeck, you'll notice that the URL appends a '#' followed by some somewhat cryptic values. Here's an example:

    http://localhost:8080/jitsu/quicktours/binding_active.html#10-2!

On this page, there is a single control, the list, that retains view state (its selection). The text following the '#' contains the single view state reference:

    10-2!

The 10 is a unique identifier referring to the list. (For the curious, it consists of a dotted path to the control from the root, where each numeric value represents the current path element's position in its parent. This path is guaranteed to be consistent across page loads at the appropriate times.) The '-' is a separator that delineates the path from the actual view state value. The 2 represents the actual view state, in the form of a URL encoded string. In the case of a list, this is the selected index. The '!' is a separator between view state elements.

Since the amount of view state retained is typically very small (ignoring bitmap and rich text editors for now), and the maximum length of a URL is approximately 2K, we can encode the view state for a large number of controls for a given page. And all of this happens automatically.

An additional feature of Jitsu's view state management is that it works on nested controls, even those that are created lazily from templates. So for example if you are showing a card from a card deck, and that card has a current selection in it, the view state will be properly manged.

Using the View State APIs

Most control writers using this will need to implement two methods, and make a single call when they are changing their view state. Here is how the ListControl saves its viewState:

#method ListControl.onSetValue(propertyId, oldVal, newVal, notificationSource) {
    ...
    
    switch (propertyId) {
        case ID_selectedIndex:
        {
            ...
            if (newVal != null) {
                this.setViewState('' + newVal);
            }
        }
     }
     ...
 }

And here is how it retrieves it:

#method ListControl.onFirstRender() {
    this.setValuesFromViewState();
    this.createChildrenFromItems(this.getValue(ID_items));
    #call ContainerControl.onFirstRender();
}

#method ListControl.onViewStateChanged() {
    this.setValuesFromViewState();
    #call ContainerControl.onViewStateChanged();
}

#method ListControl.setValuesFromViewState() {
    var previousSelection = this.getViewState();
    if (previousSelection != null) {
        this.setValue(ID_selectedIndex, parseInt(previousSelection));
    }
}

Saving state consists of using setViewState(), and retrieving it requires getViewState(). The retrieval happens at two different times: when the page is first loaded (onFirstRender()), and when navigations occur using the back button (onViewStateChanged()). Be sure to call the parent class's methods for both of these.

Notice that the state itself must be stored as a string. This string may contain any text - it will be encoded for you - but remember that you are placing it in the URL of the page, and other controls will be using this resource as well, so keep it as small as possible.

The Nitty Gritty

In properly behaved browsers, Jitsu uses window.location.hash to store its data, since this allows one to change the page's URL without causing a page reload. Unfortunately, there are few well behaved browsers. Internet Explorer does not register a change to window.location.hash in history. For this reason, we employ the trick of writing to a hidden iframe, which IE does register. Safari does not even recognize this, so we currently have no back button support for Safari. We'll continue investigating this for other options.