Archive for November, 2006

Using the StateManager in AJAX Apps

Monday, November 6th, 2006

Due to the success (and quality) of Asual's SWFAddress, I've decided to stop work on this project. SWFAddress is now capable of all the things that StateManager is, so it doesn't seem very beneficial to the open source community to continue development on StateManager. The source code and examples will remain available but no longer be updated, but keep checking xa for other open source code!

Within days after posting the above message, Rostislav Hristov (the creator of SWFAddress) sent me a very flattering email asking me to join the SWFAddress team. While I probably won't be able to contribute too much time to the project, Rostislav has informed me that he has incorporated my Safari findings into the SWFAddress code base.

After some input from Geoff Stearns, I decided to tweak the JavaScript file that powers the ActionScript StateManager class so that it can also be used to manage your AJAX states. If you've read the article that introduced the StateManager, you should be more than comfortable using it in an AJAX environment. The StateManager source and example files zip file includes two AJAX examples: the first shows how to create an AJAX application that utilizes state management, and the second shows how to implement state management into an already-existing project. However, since the usage is nearly identical to the ActionScript StateManager, I'll just give a brief walkthrough here. (In fact, the only real difference is how the event handlers are defined.)

Download the StateManager source and examples as a zip file or from my as3 subversion repository for all you need to start managing states in both your Flash and AJAX web applications or check out the AJAX StateManager in action.

Step 1: Import the Script

This time, all you need is in the file "statemanager.js". This is the exact same JavaScript file that the AS2 StateManager uses, so don't worry about getting them mixed up or anything. Add it to your html file with a <script> tag:

<script type="text/javascript" src="statemanager.js"></script>

You may also want to make a reference to the "class", so you don't have to keep typing the namespace out:

var StateManager = EXANIMO.managers.StateManager;

Step 2: Initializing the StateManager

Once the DOM is ready to be written to, you'll need to initialize the StateManager. In other words, call the initialize method in the window's onLoad handler:

window.onload = function()
{
    StateManager.initialize();
}

Since the StateManager uses an iframe to keep track of states in Internet Explorer, it's important that you not call initialize until the DOM can be written to.

In previous versions of the StateManager, the class would initialize itself as soon as the body tag was present. However, in large HTML files, the body tag is sometimes present before it becomes writable. Though the new version requires the user to manually initialize the StateManager, it avoids this problem without making assumptions about the user's load handling.

Step 3: Setting a State

Set a state by passing the ID of the state you'd like to set to the setState function. The following illustrates how to set the state "about" when the user clicks on the hyperlink with id "about-link".

window.onload = function()
{
    StateManager.initialize();

    document.getElementById('about-link').onclick = function()
    {
        StateManager.setState('about');
        return false;
    }
}

As in the ActionScript version of the StateManager, there is one special state: the default state of your application. This state is represented by an empty hash in the browser's address bar. The ID of the default state is stored in the property StateManager.defaultStateID. So, if you want a button to return your application to the default state (as we do in "ajax-1.fla", you would use the following code:

window.onload = function()
{
    StateManager.initialize();

    document.getElementById('home-link').onclick = function()
    {
        StateManager.setState(StateManager.defaultStateID);
        return false;
    }

    document.getElementById('about-link').onclick = function()
    {
        StateManager.setState('about');
        return false;
    }
}

In previous versions of the StateManager, the ID of the default state was always "home". In the new version, it defaults to "defaultState", but you can chose anything you want. To change it, simply set the defaultStateID property before you initialize the StateManager. But remember — the ID you chose will never show up in the URL; the default state is always represented with an empty hash value.

Step 4: Reacting to a State Change

By setting the StateManager's stateChange handler, we can determine how our application will react to a change of state. The handler recieves an object with an id property that holds the ID of the new state. In the code below, the contents of a heading tag are updated to reflect our state. We can also update the browser's title bar with the StateManager.setTitle function.

StateManager.onstatechange = function(e)
{
    document.getElementsByTagName('h1')[0].innerHTML =
        'The id of the current state is: ' + e.id;
    StateManager.setTitle('My Website :: ' + e.id);
}

Users familiar with prior versions of the StateManager should note that its event handler names have been changed to reflect JavaScript conventions. The "stateChangeHandler", "stateSetHandler", and "stateRevisitedHandler" functions have been renamed to "onstatechange", "onstateset", and "onstaterevisit", respectively.

Deja Vu?

That's it! Hopefully you're well on your way to managing states in both AJAX and Flash applications. If you have any ideas for making things simpler and more intuitive, leave a comment! And be sure to check out the ajax-2 folder in the StateManager source and example files to see how the StateManager can be integrated into already-existing projects.

StateManager

Sunday, November 5th, 2006

Due to the success (and quality) of Asual's SWFAddress, I've decided to stop work on this project. SWFAddress is now capable of all the things that StateManager is, so it doesn't seem very beneficial to the open source community to continue development on StateManager. The source code and examples will remain available but no longer be updated, but keep checking xa for other open source code!

Within days after posting the above message, Rostislav Hristov (the creator of SWFAddress) sent me a very flattering email asking me to join the SWFAddress team. While I probably won't be able to contribute too much time to the project, Rostislav has informed me that he has incorporated my Safari findings into the SWFAddress code base.

The flash community has been talking about, and proposing solutions for, "fixing the back button," for much longer than I've been developing in Flash, but when a sunKING client asked for a working back button in their flash site, I decided to have a go at it. Like most of these things, the xa StateManager fixes the back button and provides deeplinking. Unlike all of the other solutions, the ex animo StateManager is the first method to be fully compatible with Safari. Since the class uses ExternalInterface, you'll need to publish to at least Flash 8. It's been tested successfully in IE5.5, IE6, FF 1.5.06, and Safari 2.0.4 but will degrade gracefully (i.e. behave like a normal flash page) in unsupported browsers.

Check out this simple example to see it in action, then download the StateManager example and source as a zip file or from my as3 subversion repository (last updated 2007.04.30) to see how it's done. The zip file includes the AS2 and AS3 versions.

The StateManager also works for AJAX applications. For more information, read Using the StateManager in AJAX Apps.

In the following tutorial, I'll walk you through two methods for adding state management to your ActionScript 2 Flash applications. (It's almost exactly the same for AS3: just update your event handling.) In the first part, I'll show you how to build a Flash application that includes state management. Then, in the second part, I'll go through the steps for adding state management to an already existing project. The FLAs are in the source folder of the StateManager example and source files zip. Note that, in order for the published SWFs to work, you'll have to move them to a directory that also contains a suitable html file and statemanager.js.

Part I

Step One: Setting Up the HTML

First things first. The StateManager makes heavy use of JavaScript and most of the heavy lifting is done by the "statemanager.js" file. That means you'll need to import the file with a script tag in the head of your html document (see flash-1.html):

<script type="text/javascript" src="statemanager.js">

You're also going to have to make sure that your SWF is allowed to communicate with the browser. At sunKING, we're in the habit of using Geoff Stearns's SWFObject but you can use any embedding technique you want as long as you allow script access. The following code in the body of your html file should do the trick:

<div id="example">Download Flash!</div>

<script type="text/javascript">
/*<![CDATA[*/
    var mySWFObject = new SWFObject('example.swf', 'mySWF', 400, 300, 8, '#fff');
    mySWFObject.addParam('swLiveConnect', 'true');
    mySWFObject.addParam('allowScriptAccess', 'always');
    mySWFObject.write('example');
/*]]>*/
</script>

Of course, if you do choose to use SWFObject, don't forget to import "swfobject.js" with another <script> tag.

Step 2: Importing the Class

In order for your Flash Application to use the AS2 StateManager class, you'll have to import it. As you can see in "flash-1.fla" (you have downloaded the StateManager example and source files, right?), this is done with a simple import directive in the first frame of our movie:

import com.exanimo.managers.StateManager;

In order for this to work, you'll have to copy the "com" folder you downloaded to your ActionScript directory. Note that there are now two different versions included in the StateManager example and source files: one for AS2 and one for AS3. Make sure you grab the right one! For more information on importing classes, read the LiveDocs on the subject.

Step 3: Initializing the StateManager

Having successfully imported the StateManager, you'll now have to initialize it:

StateManager.initialize();

When this line is executed, the StateManager will run its initialization routine and, if the user has followed a deep-link, will attempt to load the specified state. Therefore, it's important that you not call initialize until your application is ready to change states.

In previous versions of the StateManager, the class would be initialized automatically when you imported it. However, this caused some problems for some preloading techniques on deep-linked pages. Though it requires the user to type an extra line, this new method provides more flexibility: you can now control exactly when a deep-link will cause a state change.

Step 4: Setting a State

Now that you've imported everything, you're ready to start setting states. To do so, simply pass the ID of the state you wish to set to the setState function. The browser url will then change to reflect the new state, and an entry will be added to the browser history. In the following code (from "flash-1.fla"), the "portfolio" state is loaded every time the user clicks portfolioButton.

portfolioButton.onRelease = function():Void
{
    StateManager.setState('portfolio');
}

You can set as many states as you want and state IDs can be any url-encoded string ("portfolio," "/portfolio," "/portfolio/art," etc.). There is also one special state: the default state of your application. This state is represented by an empty hash in the browser's address bar (i.e. "index.html" or "index.html#"). If you think about it, this makes perfect sense: when a user follows a link without a hash portion, they should get the default state of the Flash application. The ID of the default state is stored in the property StateManager.defaultStateID. So, if you want a button to return your application to the default state (as we do in "flash-1.fla", you would use the following code:

homeButton.onRelease = function():Void
{
    StateManager.setState(StateManager.defaultStateID);
}

In previous versions of the StateManager, the ID of the default state was always "home". In the new version, it defaults to "defaultState", but you can chose anything you want. To change it, simply set the defaultStateID property before you initialize the StateManager. But remember — the ID you chose will never show up in the URL; the default state is always represented with an empty hash value.

Step 5: Reacting to a Change of State

Of course, setting states doesn't mean anything unless something happens when you do it. To make sure something does, listen for the StateManager's "stateChange" event. In our example movie, our frame labels contain the registered state IDs so we can simply pass the IDs to the gotoAndPlay function. But be careful — frame labels can't be the identical to the state IDs! (That's why each of the frame labels in our example starts with "#".) We also use the setTitle function to change the browser window's title bar value.

function stateChangeHandler(e:Object):Void
{
	// Your navigation code here.
	gotoAndPlay('#' + e.id);

	// Set the title.
	StateManager.setTitle('My Website :: ' + e.id);
}
StateManager.addEventListener('stateChange', stateChangeHandler);

That's it!

The above code is all it takes to build a flash app with state management. Simply call setState when you want to.. well, set a state.. and listen for the stateChange event to handle your.. well, state changing. And, of course, there's no reason why your states need to be as rudimentary as those in the example; you can do whatever you want in the stateChange handler.

Part 2

Of course, chances are that you aren't going to want to build your application around the StateManager. You've got your own system of navigation and want something that'll integrate with it as simply and smartly as possible. Don't worry — the StateManager has got you covered. Part two of this tutorial covers adding state management to existing projects. It assumes that you've got a central navigation function which, in our example ("flash-2.fla" from the StateManager example and source files), is called "myOldNavigate". Steps one through three are exactly the same for this method so we'll jump right in to..

Step 4: Setting States

Clearly, if we want to add state management to our pre-existing project, we're going to have to add some code to our navigate function. That way, each time it's called, the user will get a new URL for deep-linking, and an item will be added to their browser history so they can use the back button. As it turns out, our setState function comes to the rescue again. This time, we provide it with a second argument — the title we wish to appear in our browser's title bar.

function myOldNavigate(id:String):Void
{
    // Your navigation code here.
    gotoAndPlay('#' + id);

    // Add this..
    StateManager.setState(id, 'My Website :: ' + id);
}

Step 5: Reacting to Changes of State

We've only added this one line to our already-existing myOldNavigate function and already, items are being added to our browser history and our browser title is updating. However, at this point, nothing happens when we click "back." Luckily, the StateManager provides us with the "stateRevisit" event. Any time the user revisits a state — whether it's by clicking the back button or forward button, or following a deep-link — the "stateRevisit" event will be dispatched. So, to update our application accordingly, we need to call our myOldNavigate function whenever this happens.

function stateRevisitHandler(e:Object):Void
{
    myOldNavigate(e.id);
}

StateManager.addEventListener('stateRevisit', stateRevisitHandler);

Now our navigate function sets the state and a change of state (that occurs as a result of a back button click, etc.) calls our navigate function! Those of you paying attention may be thinking that we've got a nasty infinite loop on our hands here, but the StateManager has an internal mechanism for preventing that, so we're alright.

What's next?

Nothing. You're done! Hopefully, after this quick walkthrough, you're on your way to creating state-aware Flash applications — or adding state management to a couple of old projects!

The StateManager is available under the MIT License with the additional request that any substantial work done on it be returned here so we can all benefit. In exchange, you could find your name next to an @author tag in the next release (!!!).