Archive for the ‘actionscript’ Category

JSFLInterface

Saturday, January 10th, 2009

It won't be long after you start making your first windowSWF (or "Flash Panel") that you'll find yourself needing it to talk to JSFL. After all, JSFL is what's really doing the heavy lifting behind the scenes of that beautiful Flash interface you've created. This communication is accomplished with the MMExecute function. MMExecute accepts a string of (escaped) JavaScript and executes it. Simple right? Well, if you've made your way here, you probably already know that this can get to be a pain pretty quickly. Adding backslashes, changing double quotes to single quotes, concatenating strings, serializing objects… it gets to be a mess. Several people have come up with solutions to avoid the hassle but, being the egotist I am, I think mine is the simplest.

You can get the JSFLInterface class from my subversion repository.

If you've ever used Adobe's ExternalInterface, you'll be instantly at home with the JSFLInterface. Simply import the class and use its call method to make JSFL calls. Like ExternalInterface.call, the first argument is the function name and subsequent arguments are passed to your JavaScript function. Your arguments will automatically be serialized. Your JSFL function can even return values (including Objects, Arrays, Dates, and the "primitive" types), which will be automatically converted into ActionScript objects, using some of the same code as ExternalInterface.

I think it's pretty self-explanatory but here's an example anyway:

import com.exanimo.external.JSFLInterface;
JSFLInterface.call('fl.trace', 'Hello World'); // traces "Hello World"
if (JSFLInterface.call('fl.fileExists', 'file:///C|/example.fla') == true)
{
	JSFLInterface.call('fl.trace', 'file exists!');
}
else
{
	JSFLInterface.call('fl.trace', 'file not found!');
}

As usual, this class is available under the MIT License, with the additional request that any improvements you make be returned here so we can all benefit from them.

XML Makes A Bad Dictionary Key

Tuesday, April 29th, 2008

If you've ever tried to use the result of an E4X expression as a Dictionary key, you're probably no stranger to frustration. Mine started when I traced a problem I was having back to the following code:

import flash.utils.Dictionary;
var myXML:XML = <a><b /></a>;
var myDictionary:Dictionary = new Dictionary();
myDictionary[myXML.b[0]] = 'hello world';
trace(myDictionary[myXML.b[0]]);

If you're like I was, you're probably expecting this to trace "hello world". Instead, I got "undefined". So what's going on here? A glance at the documentation for the Dictionary class tells us that Dictionary "uses strict equality (===) for key comparison on non-primitive object keys." XML is a non-primitive, so logic dictates that myXML.b[0] !== myXML.b[0]! I guess this isn't completely preposterous: perhaps the dot operator returns a copy of the node? It's simple enough to check. I typed in the following.

var myXML:XML = <a><b /></a>;
trace(myXML.b[0] === myXML.b[0]);

true. Clearly, something strange is going on here. If Dictionary is indeed using strict equality, as it claims, then my initial code should've traced "hello world." On the other hand, maybe the strict equality operator is lying?

A Solution?

Since the strict equality operator returns the value we expect, we can find the value that corresponds to our key simply by iterating through the Dictionary with a function like the following:

function getDictionaryValue(dict:Dictionary, key:Object):Object
{
	var value:Object;
	
	if ((key is XML) && (dict[key] == undefined))
	{
		// Iterate through the dictionary to find a matching key.
		for (var k:Object in dict)
		{
			if (k === key)
			{
				value = dict[k];
				break;
			}
		}
	}
	else
	{
		value = dict[key];
	}
	return value;
}

Passing your Dictionary and key to the getDictionaryValue function will return the value you expected from the beginning.

Flash CS3 and _exclude.xml

Wednesday, March 26th, 2008

While working on a modest Flash framework at HZDG, I ran into a problem that everybody who works on a modest Flash framework runs into: there's no easy way to prevent classes from being compiled into a SWF in Flash CS3. Back in the days of AS2, classes could be excluded from compilation by creating an xml file like the following:

<?xml version="1.0" encoding="utf-8"?>
<excludeAssets>
	<asset name="MyClass" />
</excludeAssets>

Assuming that you had an FLA named "filename.fla," and saved the above xml as "filename_exclude.xml" in the same directory, the class MyClass would be excluded from your compiled SWF. This allowed you to create an application that used multiple SWF files without bloating the filesize and increasing the user's download time by including every class in every SWF. For some reason, however, this functionality was dropped in Flash CS3. Luckily, with the help of a tricky JSFL file, we can get it back.

Download the example and source files as a zip file or get them from my AS3 JSFL subversion repository. Then keep reading — I'll tell you what's going on.

Example

The example contains two FLA files: "library.fla" and "example.fla." The "library.fla" file contains only the following code on its first frame:

import PointlessThing;
var p:PointlessThing;

In practice, this FLA would be much more complicated, but I've simplified it for demonstration purposes. The only thing that matters is that one or more classes is imported and will be compiled into a swf. (The class I import — PointlessThing — is just that: it serves no real purpose other than to bloat the size of the SWF that it's compiled into so that we can be sure our _exclude file is working.) When we publish "library.fla," the resulting SWF is about 18k.

Our other FLA, "example.fla," is also very simple. It loads "library.swf" and, when the process is complete, creates a new PointlessThing and tells you how large the SWF file is:

import PointlessThing;
import flash.display.Loader;
import flash.system.LoaderContext;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;

var ldr:Loader = new Loader();
var request:URLRequest = new URLRequest('library.swf');
ldr.contentLoaderInfo.addEventListener('complete', completeHandler);
ldr.load(request, new LoaderContext(false, ApplicationDomain.currentDomain));

function completeHandler(e):void
{
	var p:PointlessThing = new PointlessThing();
	textField.text = 'This SWF is ' + this.root.loaderInfo.bytesLoaded + ' bytes.';
}

Compiling this FLA results in a SWF that's approximately 21k. Most of that is because of our PointlessThing class (which of course is only a stand in for the many classes that these two FLAs might share in a real-world application). This added bloat, however, would be completely unnecessary if we could simply use the PointlessThing definition that's already been compiled into the "library.swf" file. In other words, we need to prevent PointlessThing from being compiled into "example.swf." We could use the bridge pattern, but then we'd have to create new interfaces and we still wouldn't be completely rid of those extra bytes.

Instead, we can use the "Publish (obey _exclude.xml).jsfl" file included in the download above. This allows us to actually prevent classes from being compiled into our SWF using a system that many Flash developers are already familiar with. The files you've downloaded already include the _exclude.xml file for our example:

<?xml version="1.0" encoding="utf-8"?>
<excludeAssets>
	<asset name="PointlessThing" />
</excludeAssets>

In order for your _exclude.xml file to work, it must be named filename_exclude.xml (where filename is the name of your FLA) and saved in the same directory as the FLA.

This time, instead of publishing "example.fla" normally, run the jsfl file (Commands » Run Command…). Then open up the resulting SWF file — it should be about 3k, or less than 20% of the original!

Applications

This trick isn't just applicable to people creating Flash frameworks: virtually any project that loads external SWFs can benefit from eliminating redundant bytecode. And it's easy to fashion "Flash DLLs" that contain shared class definitions for runtime loading.

Though I chose to follow the _exclude.xml paradigm pioneered by previous versions of Flash, it doesn't really matter how the JSFL file determines which classes to exclude. In fact, having an _exclude.xml file for each FLA is almost certainly not an ideal situation. Because this technique is familiar to more developers than any other method of excluding classes, I figured it would provide a good starting point and allow for quick adaptation by users familiar with the process in AS2. Advanced developers, however, will probably benefit from picking apart the JSFL file and adapting it to suit their build process. All of my code is released under the MIT License so you can do whatever you want with it, but if you do wind up using it in Your Next Big Project, I'd appreciate a h/t.

ScrollPane

Tuesday, March 25th, 2008

I know a lot of work has gone into the Flash CS3 components and skinning them is much easier than it was with earlier versions, but doesn't it seem like they're still just too finicky? Well, it does to me. My simple ScrollPane class does everything you'd expect, but (unlike the component) leave the layout up to you. The result is a ScrollPane that's much more flexible and easily adapted to your pushing-the-boundaries-of-what-a-scroll-pane-can-look-like design. Don't want an up arrow? Don't include it. No thumb? No problem. Best of all, com.exanimo.containers.ScrollPane and com.exanimo.controls.ScrollBarare modeled after fl.containers.ScrollPane and fl.controls.ScrollBar, so you don't have to waste time learning new APIs. Better than best of all, I've put together six examples that demonstrate all the different ways to use the ScrollPane, and how easy it is to extend it.

Download the source code and example files as a ZIP or get them from my subversion repository.

Features

  1. Simple to skin
  2. API based on fl.containers.ScrollPane
  3. Doesn't muck with your layout
  4. Optional Thumb scaling
  5. Support for disabled ScrollBars
  6. Easy-to-implement easing
  7. Mouse wheel support (Windows only, for now)
  8. Thumb and arrow buttons are optional
  9. Accessors for setting the size of a line and page
  10. Accessors for setting the scrolling repeat interval and delay
  11. Uses interfaces in case you want to create your own implementation from scratch

Never Ever Use Tween or URLLoader or Loader or Timer or…

Wednesday, August 1st, 2007

The Situation

Your Tweens aren't finishing. Or your URLLoaders won't load. Or your Timers stop ticking.

The Problem

Consider the following function:

function doStuff():void
{
	var x:Object = {};
}

When this method is called, it creates an object and stores a reference to it in the variable x. After the function finishes executing, there are no remaining references to the object, so the Flash Player clears it from memory (as soon as it gets around to it). Of course, this happens no matter what type of object we create.

Unfortunately, there are some situations where this behavior can cause major problems. Objects like Tween, Loader, and Timer are associated with asynchronous processes (animating, loading, etc..) that we expect to complete regardless of whether the objects that originated them are still in the player's memory.

Sometimes, we're lucky. Depending on the how many things your application is trying to do at once, the garbage collector might not perform a sweep while our Tweens are playing (or our URLLoaders are loading). Still, it's impossible to anticipate exactly when the garbage collector will do its thing, and we certainly can't allow it to decide whether our applications will function correctly or not. Therefore, we can never ever use these classes without storing a reference to each instance we create.

However, when the number of objects is dynamic (or you're simply making a lot of them), this can get to be a hassle pretty quickly. This problem is illustrated by the following (admittedly outlandish) function, which creates 1000 Sprites and uses 1000 Tweens to fade them out.

function doSomething():void
{
	var mySprite:Sprite;
	for (var i:uint = 0; i < 1000; i++)
	{
		mySprite = new ItemInTheLibrary();
		this.addChild(mySprite);
		mySprite.x = Math.random() * this.stage.stageWidth;
		mySprite.y = Math.random() * this.stage.stageHeight;
		new Tween(mySprite, 'alpha', None.easeNone, 1, 0, 1, true);
	}
}

In its current state, this function just won't work. (At least not on a MacBook Pro with 2GB of RAM.) All of the Sprites will be created and added to the stage, but few (if any) of the Tweens will play. By not storing any references to our Tweens, we authorize the garbage collector to dispose of them as soon as the Flash Player needs the memory for something else. Pushing each Tween instance into an Array would solve our problem (try it), but then we'd have to worry about removing them from the Array when the animation finished (or else leave 1000 unused Tweens hanging around in our computer's memory).

The Fix

In almost every conceivable use of Loader, URLLoader, and Timer, there should be no difficulty in maintaining a reference to your instance. Because you'll rarely need to make many instances of each class at once, storing them in a private property will usually suffice. (Don't forget to null the reference when you're done with it!)

private var _timer:Timer;

private function _startTimer():void
{
	this._myTimer = new Timer(1000, 1);
	this._myTimer.addEventListener(TimerEvent.TIMER, this._timerHandler);
	this._myTimer.start();
}

private function _timerHandler(e:TimerEvent):void
{
	// Remove the listener
	e.currentTarget.removeEventListener(e.type, arguments.callee);
	
	// We're done with the timer. Let it die.
	this._myTimer = null;
}

Tweens are a slightly different beast. Luckily, there are many fine tweening libraries available. It's no coincidence that these libraries usually allow you to create tweens using static methods (for example, Tweener.addTween()); after all, there's no danger in Flash garbage-collecting your instances if you're not dealing with any instances.

If, however, you've got a big project that's already using Adobe's classes (without maintaining references to your instance), you've probably come here in search of The Quick Fix. For that, I'm making available a set of classes (GCSafeAnimator, GCSafeLoader, GCSafeTimer, GCSafeTween, and GCSafeURLLoader) that prevent themselves from being garbage collected while their respective processes are underway. Each class has the exact same API as its Adobe counterpart, so to create the Tween in the above function, for example, you would type:

new GCSafeTween(mySprite, 'alpha', None.easeNone, 1, 0, 1 true);

Similarly, loading a url would look like this:

var ldr:GCSafeURLLoader = new GCSafeURLLoader();
ldr.load(new URLRequest(‘http://exanimo.com'));

To further facilitate search-and-replacing, the packaging is also based on that of the Adobe classes (so GCSafeURLLoader is located at com.exanimo.net.GCSafeURLLoader and GCSafeTween is located at com.exanimo.transitions.GCSafeTween). When an object is no longer performing its task (be it tweening, loading, or whatever), it again becomes eligible for garbage collection.

Download the GCSafe examples and source as a zip file or from my as3 subversion repository (AS3 only). They're available under the MIT License.

Ultimately, having objects that manage their own memory is not the best idea. While these classes fulfill the expectations that you probably had about Adobe's counterparts, I would not recommend using them on new projects. Instead, it's better to understand why your objects were being garbage collected in the first place and take steps to avoid it.

Automatic Bandwidth Detection for SMIL + FLVPlayback + Progressive Downloads

Tuesday, June 5th, 2007

This technique makes use of the URLLoader's close method. Therefore, the code is only available for AS3.

You know what would be nice? If we could serve every user the same 1024×768, 512 kilobits per second video. Unfortunately, some selfish people still refuse to pay us developers the simple courtesy of buying that T1 line (I'm looking at you, Mom). To make matters even worse, our bosses want these selfish losers to be able to watch our videos. Luckily, Flash's FLVPlayback component supports a subset of SMIL that allows us to serve different videos to users with different bandwidths.* For those who don't know, a SMIL file looks something like this:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE smil PUBLIC "-//W3C//DTD SMIL 2.0//EN"
    "http://www.w3.org/2001/SMIL20/SMIL20.dtd">
<smil xmlns="http://www.w3.org/2001/SMIL20/Language">
    <body>
        <switch>
            <video src="video2.flv" system-bitrate="512000" />
            <video src="video1.flv" system-bitrate="256000" />
            <video src="video0.flv" />
        </switch>
    </body>
</smil>

Given the above SMIL file, users with connections transferring at a rate of 512kbps or higher should see "video2.flv," users with transfer speeds between 256kbps and 512kbps should see "video1.flv," and everybody else should get "video0.flv." But they won't — unless you let the FLVPlayback component know what their transfer rate actually is. Generally, this means having to download a file of known size and estimate the user's bandwidth based on how long the download takes to complete. However, with the advent of AS3 (and a little clever actionscripting), we can automate this process entirely.

See the NCManagerAuto Example and then download the NCManagerAuto Source and Example Files to try it out for yourself. (Or grab it from my as3 subversion repository.)

Most of the work of the FLVPlayback component is actually done by the VideoPlayer object that it wraps. The VideoPlayer class, in turn, uses a helper class called NCManager to (of all things) manage NetConnections. Therefore, we could make some pretty significant changes to how our FLVPlayback components operate if we could only substitute our own versions of NCManager for the default. If you haven't already guessed, Adobe provides us with a simple method to do exactly that. By setting the static iNCManagerClass property of the VideoPlayer class, we can change how VideoPlayers (and by extension FLVPlayback components) handle NetConnections. By setting the property to the xa NCManagerAuto class, we can enable automatic (and completely transparent) bandwidth detection for our FLVPlayback components. But enough babbling, here's how you do it:

import fl.video.FLVPlayback;
import fl.video.VideoPlayer
import com.exanimo.video.NCManagerAuto;

VideoPlayer.iNCManagerClass = NCManagerAuto;
var myFLVPlayback:FLVPlayback = new FLVPlayback();
myFLVPlayback.source = 'example.smil';
this.addChild(myFLVPlayback);

Except for the addition of one line (okay, one line and two import statements), everything here is business as usual. You can create your FLVPlayback component with ActionScript on a frame, in a document class, or even drag it onto the stage. Just add VideoPlayer.iNCManagerClass = NCManagerAuto; and the FLVPlayback component will be able to parse your SMIL file and play the video appropriate for your user's internet connection.

So How Does it Work?

The NCManagerAuto class uses a variation of the same trick people have always used to estimate a user's bandwidth. Historically, this required a small file of known size on your server. This file would then be downloaded and, based on the time the download took, the bandwidth could then be estimated. However, NCManagerAuto doesn't require any extra files on your server. Instead, it creates an instance of the xa BandwidthChecker, which it uses to download the first 35K of one of the FLVs in your SMIL file — an uncached version of course. It then sets the bitrate of your component using the data returned by the BandwidthChecker.

Of course, this method of determining bandwidth isn't entirely foolproof. Its drawbacks have been documented by almost everybody who's written on the subject so I won't go into it. However, since the actual detection is encapsulated in the BandwidthChecker, it'll be a simple switch if a more accurate method arises. Both the NCManagerAuto and the BandwidthChecker are available under the MIT License. Enjoy the free code.

*There appears to be a bug in how Adobe's FLVPlayback component parses SMIL files: the SMILManager 's parseVideo method throws away the system-bitrate attributes of your video nodes. Therefore, the component will think that the first node is your default video, and use it every time. The source code that Adobe includes with Flash 9 (located in the "Component Source" directory) does not exhibit this problem, so I can only guess that the FLVPlayback component was compiled with an earlier version of the SMILManager class.

This technique addresses the problem transparently by using a different SMILManager class. Even if you do not want to use automatic bandwidth checking (and instead want to detect bandwidth using your own method), you can still use the NCManagerAuto class to work around this bug. Simply set the bitrate property of your FLVPlayback instance and the component will use that value instead of trying to determine the user's bandwidth itself.

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 (!!!).