/customers/iconara.net/iconara.net/httpd.www/blog/wp-content/plugins/wp-super-cache/wp-cache-phase1.php Iconara » SWFAddress: Back, reload and bookmarks in Flash

SWFAddress: Back, reload and bookmarks in Flash

I gave you my home-grown solution on how to handle reload and bookmarks in Flash applications a few weeks back, and now its time to revise it. The reason is the newly released SWFAddress extension to SWFObject, which solves the same issues as my previous solution, but also handles the back button.

I don’t care very much for the example code given at the SWFAddress site, so I’m going to show you a proper object oriented solution for all your state handling needs.

Before we begin, I suggest you take a look at the examples on the SWFAddress site (look under “Solutions”), and you should familiarise yourself with SWFObject, if you haven’t already.

The SWFAddress state handling

While I think that SWFAddress as such is a great idea, the ActionScript component that does the state handling on the Flash side is not very good, but it’s quite easy to fix since the JavaScript interface is quite clean and simple.

My problem with the SWFAddress ActionScript component (on the website you can find it in AS1 form, and in the download it’s available as an AS2 class in the flex and mtasc directories) is that its essentially a singleton but really just a collection of static functions with some static variables, not exactly good OO. Moreover, it doesn’t guard against the ExternalInterface not being available, and it doesn’t keep the state in any structured form, it’s just a string so if you want to save structured data, you will have to do it yourself.

I also hate to litter my code with implementation specific components, what if I want to change implementations later? I would need to change all the code that uses the component, which is totally unnecessary.

A new state handling component

I propose a variant which is a true singleton (which it should be, since this is a resource which should have a single point of entry), which should work without the ExternalInterface being available and also allows for some form of structured state, not just a string. It will also be a clean state handling interface, which does not bind you to any specific implementation.

This is an example of how I would like to work with it:

var changeListener = function( ) {
    trace("State changed!");
}

var state = State.getInstance();

state.addChangeListener(changeListener);

state.initialize();

//...

state.setProperty("page", "intro"); state.setProperty("subsection", "3");

//...

var pageId = state.getProperty("page");

trace(pageId);

Actually, I have had a component for a long time that worked more or less exactly like this, but with another implementation behind the scenes (see my earlier post on the subject), this is just an update which uses SWFAddress instead. This is the power of proper object oriented code, I can change the details without having to rewrite everything. And the code above does not reveal any hints to the implementation, not even that the state persists across reloads, and nor should it, it’s just a stant handling component, and the client code shouldn’t be bothered with more than that.

I will let the code speak for itself, it is thoroughly documented. You may use it for non-commercial purposes without my explicit permission, but apart from that I would prefer if you asked me first. You can contact me using contact@iconara.net.

import flash.external.ExternalInterface;

/** * State is a basic state handling component which lets you save * properties in the form of key-value string pairs. If the * ExternalInterface is available, and SWFAddress is used it * will also maintain the state between page reloads, bookmarks * and back-forward clicks. * * You can register a listener that will be called when the state * changes using the addChangeListener method. * * @author Theo Hultberg / Iconara * @copyright 2006 Theo Hultberg */ class net.iconara.util.State {

private static var instance : State;

private var changeListeners : Array;

private var state : Object;


/**
 * Use getInstance to obtain an instance of this class.
 */
private function State( ) {
    this.state = { };

    this.changeListeners = [ ];

    this.addCallback("setSWFAddressValue", this, this.restoreState);
}

/**
 * Returns the shared instance of this class.
 */
public static function getInstance( ) : State {
    if ( instance == null ) {
        instance = new State();
    }

    return instance;
}

/**
 * Initializes the state handling by reading the state from the external 
 * state handling service. Should be called early, but only once.
 */
public function initialize( ) : Void {
    this.restoreState();
}

/**
 * Add a function which will be called when the state changes.
 */
public function addChangeListener( listener : Function ) {
    this.changeListeners.push(listener);
}

/**
 * Notifies all change listeners.
 */
private function onChange( ) {
    for ( var i = 0; i < this.changeListeners.length; i++ ) {
        this.changeListeners[i].call();
    }
}

/**
 * Sets the value of the named property and sends the new state
 * to the external state handling service.
 */
public function setProperty( key : String, value : String ) : Void {
    if ( key == null ) {
        throw new Error("Invalid argument: key cannot be null");
    }

    this.state[key] = value;

    this.updateState();
}

/**
 * Returns the value of the named property.
 */
public function getProperty( key : String ) : String {
    return this.state[key];
}

/**
 * Returns an encoded version of the state, suitable for sending to the
 * external state handling service.
 */
private function createStateString( ) : String {
    var stateString = "";

    var keys = [ ];

    for ( var key in this.state ) {
        keys.push(key);
    }

    // sort the keys, otherwise a different order of the keys may
    // look like a different state when the user clicks the back-button
    keys.sort();

    for ( var i = 0; i < keys.length; i++ ) {
        var key = keys[i];
        var value = this.state[key];

        // don't use properties with null values
        if ( value != null ) {
            stateString += "&" + key + "=" + value;
        }
    }

    if ( stateString.length > 1 ) {
        // remove the initial &
        stateString = stateString.substring(1); 
    }

    return stateString;
}

/**
 * If the ExternalInterface is available, reads the state from the
 * external state handling service.
 *
 * When the state has been restored, all change listeners are notified.
 */ 
private function restoreState( ) {
    if ( ExternalInterface.available ) {
        // not used, but available if needed
        //var id          = this.cleanExternalValue(
        //  ExternalInterface.call("SWFAddress.getId")
        //);

        var stateString = this.cleanExternalValue(
            ExternalInterface.call("SWFAddress.getValue")
        );

        var states = stateString.split("&");

        var restoredState = { };

        var size = states.length;

        for ( var i = 0; i < size; i++ ) {
            var kv = states[i].split("=");

            restoredState[kv[0]] = kv[1];
        }

        state = restoredState;

        this.onChange();
    }
}       

/**
 * If the ExternalInterface is available, sends the encoded state to the
 * external state handling service.
 */
private function updateState( ) {
    if ( ExternalInterface.available ) {
        var stateString = this.createStateString();

        ExternalInterface.call("SWFAddress.setValue", stateString);
    }
}

/**
 * Wrapper method for ExternalInterface.addCallback, guards agains the
 * ExternalInterface not being available.
 *
 * Throws an error if the ExternalInterface is available but a callback 
 * could not be added.
 */
private function addCallback( message : String, target : Object, handler : Function ) : Void {
    if ( ExternalInterface.available ) {
        var added = ExternalInterface.addCallback(message, target, handler);

        if ( ! added ) {
            throw new Error(
                "Could not add callback \"" + message + "\" to ExternalInterface"
            );
        }
    }
}

/**
 * If a value of null or undefined is sent, it is serialized as a string,
 * this method makes sure that a value of undefined or null becomes proper null.
 */
private function cleanExternalValue( v ) {
    if ( v == "undefined" || v == "null" ) {
        return null;
    } else {
        return v;
    }
}

/*
 * These two methods are available if you need them. For example,
 * Internet Explorer tends to stick the fragment part (after the #)
 * of the current URL in the title of the window. Using these
 * methods you can reset the title after each state change.
 *
public function getTitle( ) : String {
    var title = this.cleanExternalValue(
        ExternalInterface.call("SWFAddress.getTitle")
    );

    return title;
}
public function setTitle( title : String) : Void {
    ExternalInterface.call("SWFAddress.setTitle", title);
}
 */

}

Ideally there would be an interface called State which the class above would implement. In that case there could be a number of different implementations, one using SWFAddress, one without any persistance capability (if you didn’t need it for some reason) and so on, and when the next state handling component comes along you just write up a wrapper for it which implements the State interface, and plug it in.

8 Responses to “SWFAddress: Back, reload and bookmarks in Flash”

  1. stacey Says:

    Yer gonna wanna check the return on the getPropery – unless you forgot to import some as files, this.state.get(key) doesn’t work inherently in as2 Could be wrong but it was return nothing till i fixed that.

  2. Theo Says:

    You’re absolutely right. I have a simple dictionary/map implementation which I use sometimes instead of the built-in associative lists, and I missed to translate that line. Thanks for pointing it out.

  3. stacey Says:

    I’ve been modifying your implementation a bit , addding and changing, once thing i noticed si that I had to put an interval in the constructor to add the interface properly, otherwise it always returned false to me.

  4. Theo Says:

    I have not seen that problem, the callbacks are set up fine for me, using this code:

    this.state = State.getInstance();

    this.state.initialize();

    perhaps doing it in two lines is enough? (with any other programming language or environment I would have said that that would be completely irrelevant, but ActionScript surprises me on a regular basis).

  5. Pixel Acres » Blog Archive » DeepLink: Flash deep linking class 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. Table of contents: [...]

  6. Tony Warrens Says:

    Hi, i just wanted to share an article i found that should help some newbies. Search Engine Optimization And The Magic Fairy Dust

  7. Floroskop Says:

    Hello! I think this try.

  8. Flash Components Says:

    It’s one of the most effective and useful articles I’ve ever read. Thank you, helped a lot!

    Regards, Flash Scope.

Leave a Reply