Bookmarkability in Flash

Using the external interface, and a simple JavaScript, you can make your Flash applications bookmarkable, and also accidental-reload-safe. In this article I’ll show you a simple and minimal way of doing it.

Theory

To achieve bookmarkability we must have a mechanism for encoding the application’s state, and then be able to save that state somehow.

We have some options for how to actually save the state, but a common variant in Ajax-applications is to use the hash-part of the current page’s URL (the bit after the “#”, a.k.a anchor, fragment, I’m using the JavaScript terms when talking about the different parts of an URL), since that will be saved if the user bookmarks the page, and it’s simple to get and set, it’s exactly what we want.

To save our application’s state in the URL we need to encode it to a string since URL:s are strings. One way of encoding an applications state is as pairs of keys and values, URL:s are used to pass key-value pairs all the time in the search-part (the bit after a “?”), it’s a good model, tried and tested, so we will use it.

In the search-part keys and values are separated by an equal sign, and key-value pairs are separated by an ampersand, we can do the same, but use the hash-part instead.

Google uses the search part to pass parameters to it’s search script:


http://www.google.com/search?hl=en&lr=&q=bookmarks+ajax

We can use the hash-part to store the state of our application:


http://www.example.com/application.html#color=red&user=steve

The difference between these two examples is that if you change the value of one of the parameters in Google’s URL the browser will load a new page, which is not desirable in Flash applications because they do their own page handling, so to speak.

Implementation

As you may know, there is no way of retrieving the current URL from ActionScript without actively inserting it from the page it’s embedded on (for example via FlashVars or JavaScript). So to get the stored state we need to recieve it from something outside the Flash application. We are going to use a simple JavaScript snipplet to do this.

I want to create a minimal script on the page and minimise the interface between it and and the Flash application because I want to keep as much logic as possible inside my application. This is in order to minimise maintenance issues, and also to be able to switch storage implementations. Low coupling should always be something to strive for.

Let’s jump straight to the code. This is all the JavaScript that we will use:

var siteContainerId = "siteContainer";
var siteContainer    = null;

/** * Store a new state in the hash-part of the current URL. * * Called from the Flash application when it's state changes. * * @param state a string containing the current state */ function stateChanged( state ) { window.location.hash = encodeURI(state); }

/** * Reads the state from the current URL and sends this to * the Flash application. */ function restoreState( ) { // remove the first char from the hash, since it's the "#" var state = decodeURI(window.location.hash.substring(1));

siteContainer.restoreState(state);

}

/** * Set up the Flash application reference and then calls restoreState. * * Called from the Flash application when it has finished loading and * is ready to recieve calls. */ function onFlashLoaded( ) { // get a reference to the Flash application, this line works // fine if you're using SWFObject to embed your application. // if you have both an object and an embed-tag you need to // do this differently, google for "ExternalInterface" for examples siteContainer = document.getElementById(siteContainerId);

restoreState();

}

What happens here is that the Flash application starts up, calls the onFlashLoaded function, which calls restoreState, which in turn calls restoreState inside the Flash application. At a later point the Flash application can call stateChanged to save a new state.

Notice that the state has been encoded to a string before it is passed to stateChanged to minimise the amount of logic outside the application. The downside is that the state saving mechanisms can only save a string, it can’t do any clever tricks that might become available in the future, and the application will have to decide the format the state is going to be saved in without knowing the mechanism that will save it. In a perfect world the application would pass it’s state unencoded as a JavaScript/ActionScript object and the script that saved the state would decide on how to encode it. However, the state handling inside the Flash application can be decoupled from the code that contains the state, and in that way you can achieve almost the same thing.

With all the code outside of the application done, what remains is to connect it all together in ActionScript. To communicate with the JavaScript environment on the page from ActionScript you use the ExternalInterface (flash.external.ExternalInterface) singleton. First you must register the callbacks you want to be able to recieve, in this case the JavaScript calls restoreState on the Flash application, so at least this callback needs to be registered.

Add this code to your main controller, keep in mind that calling onFlashLoaded will result in an immediate call to restoreState, so your application should be loaded and ready before this code is run.

if ( ExternalInterface.available ) {
    var callbackAdded = ExternalInterface.addCallback(
        "restoreState", 
        this, 
        this.restoreState
    );

if ( callbackAdded ) {
    ExternalInterface.call("onFlashLoaded");
}

}

Also note that your application should not depend on restoreState being called, it may be that the browser does not support the external interface (Opera doesn’t yet) and if you test in Flash or the standalone FlashPlayer there isn’t even any web page, let alone JavaScript involved.

This is an example of how the restoreState and stateChanged methods in the Flash application could be implemented. I leave the decodeState and encodeState methods up to the reader to implement, but a tip is that it involves a lot of string splitting and joining, respectively.

In this example, the state is stored as an associative array, a.k.a Object, but any implementation would do. I usually don’t even save the state explicitly, but set the appropriate parameters on the involved objects, and then assemble the state from them when I want to save the state.

public function restoreState( stateString : String ) : Void {
    var newState = decodeState(stateString);

for ( var key in newState ) {
    this.state[key] = newState[key];
}

}

private function stateChanged( ) : Void {
    var stateString = encodeState(this.state);

ExternalInterface.call("stateChanged", stateString);

}

You should register your controller as an observer on the objects involved in the state you want to save, and call stateChanged every time an observable sends you a change event.

A proper implementation

A proper implementation of this would wrap the external interface calls in a proxy object, in order to shield your code from changes in the actual external interface, and also to be able to switch implementations. Who knows, one day you may be able to do the same thing that the JavaScript code does, but from ActionScript, and if so, you would only need to change the proxy.

Note: you need to set the allowScriptAccess parameter in the object/embed tag to “sameDomain” or “always” in order for the external interface to work properly.

3 Responses to “Bookmarkability in Flash”

  1. Pixel Acres » Blog Archive » DeepLink: Flash Deep Linking Says:

    [...] Inspired by the work of Theo Hultberg and Asual, I have developed a simple Flash class that demonstrates the implementation of deep linking in a Flash movie. While I don’t propose that my DeepLink class should be used as a substitute for Ausual’s excellent SWFAddress, it might provide a useful building block for exploring what the ExternalInterface class is capable of. [...]

  2. larein Says:

    dun realli understand the “this,”

    in..

    if ( ExternalInterface.available ) { var callbackAdded = ExternalInterface.addCallback( “restoreState”, this, this.restoreState );

  3. Theo Says:

    As you may know, “this” refers to the current object, the object the currently executing method is defined on.

    If you look at the documentation for the ExternalInterface class you will see that the second parameter is the object that will respond to the callback, and the third is the function that is to be used as the callback.

    In this case I want the same object that is adding the callback to also respond to the callback.

    If that didn’t clear things up, I warmly recommend reading the documentation on ExternalInterface.

Leave a Reply