Better use of SWFAddress deep-linking

Let’s talk SWFAddress. For those unfamiliar, SWFAddress is an open source library using Flash and Ajax to enable URL deep-linking within Flash. Or in less technical terms, it lets you hook a Flash movie into the web browser URL so that you can navigate through Flash using URL addresses (just the way you would if you were browsing an entirely HTML-based site). So, this is a great tool for Flash accessibility!

However, SWFAddress is really only as good as your implementation of it. Quite frankly, a sloppy implementation makes it more of a liability than a benefit. Unlike HTML, Flash doesn’t have a friendly 404 Error message to kindly tell users that there was a problem accessing the URL (or at least, not unless you specifically program the error handling). With Flash, you’ll get whatever ugly mess was left unrendered on stage before ActionScript failed to configure it. Why is this? Well, by nature you need to make SWFAddress a critical component within your master application controller in order for it to do its job. In essence, you need to program your Flash application to be navigable by a set of key values which are going to be incorporated into the URL string. When those values change, your application needs to update to match the new configuration. That said, it’s essential that those key values are handled with the utmost care, caution, and sanctity; otherwise you open the door for having your application fail when values leak in that the controller doesn’t know what to do with. So, I’ve walked onto a few suffering SWFAddress projects now and, in the process of tightening them up to make them bullet-proof, I’ve found some best practices to help avoid pitfalls.

First, what NOT to do:

For your own sanity, DO NOT start placing direct calls to SWFAddress on every button and content portal in your application. In doing so, you’re limiting your ability to validate input and creating lots of opportunity for output errors. If two buttons don’t agree on how to get and set URL values, then one button’s output may clash with another button’s input, which leads to unpredictable results. Caution, consistency, and control are essential factors here.

Instead, here’s what I’d suggest TO do:

  1. Have ONE single adapter class which acts as a proxy between SWFAddress and the entire rest of your application. Never import SWFAddress into anything but that proxy.
  2. Within the proxy class, create getters and setters for all controller values used by your application. Have the rest of your application interface with those getters / setters.
  3. Privately store the source values of the proxy’s getter and setter values as properties of the proxy object. DO NOT directly interface those source values with the SWFAddress URL. Covet and protect those valid source values at all costs!
  4. Create only one method for the proxy to get the SWFAddress URL with, and only one method for the proxy to set the SWFAddress URL with. This creates a funnel: only one object in your application talks to SWFAddress, and that object only has one input and one output with the URL. This funnel will make expansion, modification, and debugging much easier in the long run, given that all URL logic will be centralized in one spot.
  5. When setting the SWFAddress URL, create a single standardized format that organizes and writes all source proxy values out to the URL in a rigid and consistent manner. You should send values out to the URL whenever a controller property is changed.
  6. When getting the SWFAddress URL, extract the values in an organized fashion that directly counters your setter operation. Then, validate ALL input. Never accept a value into your proxy unless you’re sure that you want it. If you get garbage information, leave your valid source value protected and untouched. After getting the latest URL  information, dispatch an event that notifies your Flash app of any changes.
  7. After getting and validating the SWFAddress input, reset the SWFAddress URL with the final, resolved value set. This will replace any garbage that may have shown up in the URL with valid address information.

The goal in these guidelines is to build an armor-plated guard for your application that protects against unknown or unwanted data. This avoids the embarrassment of having your Flash site fail when a visitor makes a typo in the web address. While I advocate using SWFAddress to increase Flash’s accessibility, I also believe that it’s not worth integrating unless you’re prepared to put in the time and effort to implement an iron curtain around it.

Part II: An Implementation

After a full year away from SWFAddress, I finally have a new project that requires it. I’ve now dusted off some old code that I wrote for PBS’ Patchwork Nation map and was pleased to discover that it plugged straight into my new SWFAddress app (I love not having to rethink old problems!).

So, the following is my proxy class for managing traffic between SWFAddress and my main Flash application. This includes a fairly intricate set of switches for managing data coming in and going out, and preventing feedback loops. I have set up this example with two controller properties called “section” and “page” as a sample implementation. However, you may incorporate as many controller values as you need into this architecture. Just follow along with my implementation, and just write code into the “<your code>” blocks that I’ve included within comments. With a little luck, you’ll have the same out-of-the-box success that I’ve been having with this system. Just remember to include the SWFAddress JS file in your HTML page, and remember that you’ll probably need to view the page from a web server in order for the JS to work with the browser URL.

Good luck!

package com.gmacwilliam.utils
{
 import com.asual.swfaddress.*;
 import flash.events.EventDispatcher;
 import flash.events.Event;
 import flash.events.TimerEvent;
 import flash.utils.Timer;

 public class SWFAddressProxy extends EventDispatcher
 {
 /**
 * DEFINE SOURCE CONTROLLER VALUES HERE...
 * These are all private members that will be accessed through getters and setters.
 * <your code>
 */

 private var _section:String = "home";
 private var _page:int = 1;

 /*
 * </your code>
 */

 // The following are mechanisms used by the controller...
 private var _internalizing:Boolean = false;
 private var _updateWasExternal:Boolean = false;
 private var _postExternalTimer:Timer;
 private var _init:Boolean = false;

 public function SWFAddressProxy():void {
 super();
 _postExternalTimer = new Timer(1000, 1);
 }

 /**
 * This event notifies of updates within the controller.
 * Other components should subscribe to this event, then update themselves with the latest controller values upon catching this event.
 * NOTE: you may want to define your own event type for this rather than just using a generic CHANGE event.
 */
 public function update():void {
 dispatchEvent(new Event(Event.CHANGE));
 }

 /**
 * Loads the controller's configuration with optional parameters.
 * This should be called as the last step while starting up your application...
 * Make sure all necessary data and display is loaded, configured, and has subscribed to the controller's CHANGE event.
 * Finally, call this load method to kick SWFAddress control into gear.
 *
 * @param ParamsObject
 * This parameter is a handy way to seed values within the application controller.
 * I generally pass in the root movie's "loaderInfo.parameters" object, which includes all params that the SWF was embedded with.
 * During this load function, we can configure our controller with values from these startup params
 */
 public function load( $params:Object=null ):void
 {
 // first collect values from the params object (if provided).
 if ($params != null)
 {
 _internalizing = true;

 /*
 * DETECT AND SET STARTING PARAMS HERE...
 * Just check to see if your expected params were provided, then set their values if they were.
 * Make sure this process occurs while "_internalizing" has been disabled.
 * <your code>
 */

 if ($params.hasOwnProperty("section")) section = $params[ "section" ];
 if ($params.hasOwnProperty("page")) page = $params[ "page" ];

 /*
 * </your code>
 */

 _internalizing = false;
 }

 // Now kick-off SWFAddress. An INIT event should follow in a moment as a result.
 SWFAddress.addEventListener(SWFAddressEvent.INIT, this._onSWFAddressChange);
 SWFAddress.addEventListener(SWFAddressEvent.CHANGE, this._onSWFAddressChange);
 }

 //-------------------------------------------------
 //  Controller value getters / setters
 //-------------------------------------------------

 /*
 * WRITE YOUR CONTROLLER VALUE GETTERS AND SETTERS HERE...
 * Create a getter and setter for each source value that you are storing within the controller.
 * When setting a value, validate the new provided value, and only set the value if you're sure you want it.
 * Whenever a source value is changed, externalize the new values.
 * <your code>
 */

 public function get section():String {
 return _section;
 }
 public function set section($val:String):void
 {
 // Test that the value is not already set, and that the value is valid.
 // If the value passes validation, set the source value then externalize.
 if ($val != _section && ($val == "home" || $val == "work" || $val == "contact")) {
 _section = $val;
 _externalize();
 }
 }

 public function get page():Number {
 return _page;
 }
 public function set page($val:Number):void
 {
 // Test that the value is not already set, and that the value is valid.
 // You may also want to include additional validation specific to other controller values,
 // For example, you may want to validate the page number based on what the current section is.
 // If all tests pass, then again: set the validated value then externalize.
 if ($val != _page && !isNaN($val) && isFinite($val)) {
 _page = $val;
 _externalize();
 }
 }

 /**
 * You may encounter a situation when multiple values need to be set at once (ie: section and page changes at the same time).
 * It's not ideal to use the individual property setters in this case, becasue SWFAddress will need to externalize each one in repitition, and latency may cause unexpect results.
 * So, in this case it's best just to create a method that will validate and set multiple values, then call for a single externalization.
 */
 public function setView($section:String, $page:Number):void
 {
 var $changed:Boolean = false;

 // validate/set section
 if ($val != _section && ($val == "home" || $val == "work" || $val == "contact")) {
 _section = $val;
 $changed = true;
 }

 // validate/set page
 if ($val != _page && !isNaN($val) && isFinite($val)) {
 _page = $val;
 $changed = true;
 }

 // externalize if one or more of the values was changed.
 if ($changed) _externalize();
 }

 /*
 * </your code>
 */

 //-------------------------------------------------
 //  Read/Write to URL process.
 //-------------------------------------------------

 /**
 * Gets data from the URL and brings it into Flash.
 */
 private function _internalize():void
 {
 // do not allow internalizing within 1 second of having externalized.
 if (!_postExternalTimer.running)
 {
 // disable externalizing while collecting values.
 _internalizing = true;

 /*
 * COLLECT URL VALUES HERE...
 * You can collect data from the URL path ("/path/") or from URL variables ("/?var=value"). There is an example of each below.
 * When internalizing values, use your getter/setter properties that will validate the input. DO NOT set your source values directly from the URL.
 * <your code>
 */

 section = SWFAddress.getPath().split("/").join("");
 page = parseInt( SWFAddress.getParameter("page") );

 /*
 * </your code>
 */

 // re-enable externalizing, then push all validated fields back out to the URL.
 _internalizing = false;
 _externalize(true, true);
 }
 }

 /**
 * Pushes current Flash values out to the URL.
 */
 private function _externalize($update:Boolean=true, $externalSource:Boolean=false):void
 {
 // Restrict ability to externalize while internalizing is in progress.
 if (!_internalizing)
 {
 /*
 * WRITE URL STRING HERE...
 * You can write values as path components or as variables ("/path/?var=value&var2=value").
 * In the below example, we'll write a URL formatted as "/section/?page=1".
 * <your code>
 */

 SWFAddress.pushValue( '/'+ _section +'/?page='+ _page );

 /*
 * </your code>
 */

 _postExternalTimer.reset();
 _postExternalTimer.start();
 _updateWasExternal = $externalSource;
 if ($update) update();
 }
 }

 /**
 * Specifies if the last call to update came from outside of Flash (triggered by SWFAddress).
 * This is useful to know when trying to differentiate a user-submitted controller change from an internal application change.
 */
 public function get updateWasExternal():Boolean {
 return _updateWasExternal;
 }

 //-------------------------------------------------
 //  Event handlers (don't touch these)
 //-------------------------------------------------

 /** @private updates the application upon a change within SWFAddress. */
 private function _onSWFAddressChange($event:Event):void {
 _internalize();

 // if the application has not yet initialized:
 if (!_init) {
 // Listen to post-externalization timer for complete.
 // After first call, the timer will call one additional externalize process.
 // This builds in enough latency to set the browser URL after JS has been initialized.
 _postExternalTimer.addEventListener(TimerEvent.TIMER_COMPLETE, this._onSetStartupURL);
 // flag as initialized.
 _init = true;
 }
 }

 /** @private called after first run of the post-externalization timer.
 * This pushes the application URL outward with enough latency that the browser will register it. */
 private function _onSetStartupURL($event:Event):void {
 _postExternalTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, this._onSetStartupURL);
 _postExternalTimer.stop();
 _externalize(false);
 }
 }
}

*NOTE: I just remembered that the above class was written for SWFAddress v2.2 with some small event modifications. As such, it probably won’t plug directly into SFWAddress 2.4. We’ve had user Murdoch post a revised version of this class into comments which apparently corrects compatibility issues with SWFAddress 2.4 (although I have not yet tested it. Either way, thanks Murdoch!).

Part III: Check it out…

If you’re interested to see some Flash apps that I’ve implemented using this controller, see the following links. Feel free to ask questions, or post your own examples in comments.

PBS / Christian Science Monitor: Patchwork Nation
http://www.pbs.org/newshour/patchworknation/#/archive/?category=food-stamps&map=change-food-stamp-recipients-2007-09

Brookings Institute: State of Metro America
http://www.brookings.edu/metro/StateOfMetroAmerica/Map.aspx#/?subject=8&ind=75&dist=0_0&data=Number&year=2008&geo=metro&zoom=0&x=0&y=0

NPR Election Map, 2008
http://www.npr.org/news/specials/election2008/2008-election-map.html#/president?view=race08

Advertisements

20 comments so far

  1. Chris on

    Interesting post… I just heard about this recently and was intending to look into it, might have to pick your brain on this at some stage :)

  2. mike on

    Good post – you are entirely correct on this – I have made a mess of a site using swfaddress flippantly and I am much more careful now.

    • bigmac on

      Haha! Haven’t we all! You have to destroy at least one project with SWFAddress before realizing both its advantages and detriments.

  3. Cicit on

    Hello I think you are right, the samples given by default from SWFAddress is just for simple website. However can I have an example files / class of your best practice using this? I am still using AS2

    Thanks

    • bigmac on

      I don’t have any readily available working examples that I can post quickly, so I’ll just type up a quick example of the logic pattern’s flow. This is really quick ActionScript that I wrote in a text editor, so I can’t guarantee that it is syntactically accurate or fully functional. It’s just a demo. Also, my AS2 is a little rusty, so you’ll have to follow the logic flow as AS3.

      package
      {
      	import SWFAddress;
      	import flash.events.EventDispatcher;
      	import flash.events.Event;
      
      	public class SWFAddressProxyDemo extends EventDispatcher
      	{
      		private var _page:String = "main";
      
      		public function SWFAddressProxyDemo():void {
      			super();
      			SWFAddress.addEventListener(SWFAddressEvent.CHANGE, this._onSwfAddressChange);
      		}
      		
      		/** set the page value to the URL */
      		private function _externalize():void {
      			SWFAddress.setValue("/" + page +"/");
      		}
      
      		/** get the page value from the URL */
      		private function _internalize():void {
      			var $page:String = page;
      			page = SWFAddress.getValue().split("/").join("");
      			
      			// if value has changed while internalizing,
      			// forcibly push out all current valid values.
      			if ($page != page) _externalize();
      		}
      
      		/** page value */
      		public function get page():String {
      			return _page;
      		}
      		public function set page($page:String):void {
      			_page = _validatePage($page);
      			dispatchEvent(new Event(Event.CHANGE));
      		}
      
      		/** validate page */
      		private function _validatePage($page:String):String {
      			// test if the new page value is valid.
      			// if so, return the new value.
      			// otherwise, return the existing (valid) value.
      		}
      
      		/** called each time SWFAddress changes */
      		private function _onSwfAddressChange($event:Event):void {
      			_internalize();
      		}
      	}
      }
      
  4. Steffen on

    Hi!

    Seems like you got it figured out good! Im still a noob my self, and im working on implementing SWFAddress in a larger project. And its giving me a headache beyond compare..

    I have to ask, when you say “one proxy class”, could this then be the document class? I’ve tried to follow your rules, but not sure i got it right.

    It only remembers the previous two “pages” (its all as3 no timeline) and then it freezes? Could this be due to wrong garbage collection you recon?

    Cheers

    • bigmac on

      For the “proxy class”, I would suggest NOT using the document class… it’s safe to assume that the document class should already have a specific job to do that configures the whole application. There’s no reason to double the document up and have it do a second job. Just make the SWFAddress proxy its own object and instantiate it with the document.

      Normally I set up a master controller object for my application which acts as the SWFAddress funnel. All Flash navigation hooks listeners to that controller object to listen for changes. A singleton class instance makes sense here (for more information on that, read up on the “singleton” design pattern. I know I wrote an article about it once). Also, all navigation applies application state changes to that controller. That controller object implements the following process flow:

      1. A new navigation key is set to the controller from within Flash.
      2. Controller outputs new value to the URL.
      3. Controller detects change to URL through SWFAddress.
      4. Controller validates new URL input, and if the nav key has changed, it dispatches an event notifying all listeners of change. If an invalid key was pulled from the URL, the controller resets the URL with current app state configuration and does not notify the rest of the application of the change.
      5. All nav buttons update themselves in response to controller changes.

      The above process goes through the whole flow: all the was from a value being pushed from Flash, out to the URL, then the URL back into Flash. In the event that the browser URL is changed directly by the user, the above sequence flow would start from step #3.

  5. Alex on

    Nice (and very helpful) article indeed.
    Thanks for this!

    I’m (almost) a newbie (in AS3) though playing with flash for a couple years, so please, can you help me with such a question:
    I’m trying to develop correctSWFAddress-based micro-site and your approach is the best one (from what i could find), but…
    Ok. I made (actually, copied your code:) SWFAddressProxy class. Now how can I call, say changing page in some other classes (yes, I had imported SWFAddressProxy into them) but this system doesn’t work like this:
    SWFAddressProxy.page = “someotherpage”?
    or SWFAddressProxy.page(“someotherpage”)?
    What should I add to make it work?
    Thanks.

  6. Reece on

    bigmac,

    Awesome post, I must say. I am an AS3 novice and I am trying to implement deep linking in a flash website I have built. Pardon my ignorance, but how do I go about implementing the proxy class?

    My Document class sets up the fluid layout, but all of the action happens in a sub class(CustomClasses.Menu1)

    Any advice would be greatly appreciated, thanks.

    Regards,

    Reece

    • Reece on

      I copied and pasted the code into the SWFAddressProxy class. My Flash is not liking how $val is expressed as both an int and a string in some functions and not defined elsewhere. Any advice?

      • bigmac on

        Indeed, this is a common datatype discrepancy. Keep in mind that the input of a setter function and the output of the corresponding getter function must have a matching datatype, otherwise you get an error. Also, check out the top of the class where the source value variables are defined. Those values should also have a matching datatype to the getter and setter functions that mutate their values.

  7. Filipe on

    Hi,

    Excelent post. I think in this matter you will be the best person to help me.

    Well, i’m finishing a dynamic site, passing GET variables to a flash content.

    When i apply swfaddress it results in nothing. It simply open the page has it is only the GET variables in URL address.

    1. It is possible to apply swfaddress to a dynamic site with url variables?
    2. What are the best choices to make my flash content visible to google beside swfaddress anchors?

    Regards,

    Filipe

    • bigmac on

      Filipe,

      Your question about SWFAddress used in conjunction with URL variables is a tough one… technically it is possible, although my best advice would be to be careful! You’d need to make sure to acquire all variables that are present within the URL at the time your Flash app loads, and then make sure to always include those values in the URL. This wouldn’t be a big deal if you’re just using an anchor path for Flash (ie: /#/director/?var=value), however it could become tricky if you’re mixing Flash vars in with the URL vars. Again, there’s no reason why this isn’t possible… it would just require care and caution.

      As for the google question – I’m not a SEO expert, but I do know that XML content tends to be a really good way to have your content reviewed by search engines. They can detect the XML load and then transverse the document as markup rather than relying on Flash accessibility; which is better than it was, although I still don’t fully trust it. If you’re ever curios of what a bot or screen reader is seeing of your Flash site, just get a free demo of a screen reader and browser your website with it… it’s a pretty shocking experience if you’ve never tried it before.

  8. Ivo Boeykens on

    Hi bigmac,

    I’ve implemented your proxy method on the standard example delivered with the SWFaddress library (it’s not 100% working, but I’m getting close). I think it would be usefull for the comunity to have this example working and eventually included in the standard examples!

    You can check the current status if you click on my name.

    I’ve added the status and html title changes, but at this moment I’m stuck on implementing the flashvars: they work fine for some pages, but not for the subpages. If you’re interested, I can post my source, and maybe you’ll see what I’m doing wrong?

    I’ve also included some basic debugging which prints out all swfadress actions to the site, or sends them to MonsterDebugger…

    • mari on

      Ivo, is your code working now? Can you post it or email it? I would like to try it out?

      I’m not an actionscript programmer, but I can modify code well enough if I have a full working example.

      Is it just me, or is the class in the main post cut off on the right margin?

      Thanks! I definitely think this should be added to the examples at asual.com

      • Ivo Boeykens on

        Hi,

        I’ve discontinued working on it, because I actually needed the code to work on another project (I used the standard example to understand how it works). But here’s a link of what I had so far:
        http://www.altoalto.be/previews/swfaddress_proxy/swfaddress_proxy.zip

        All relevant files and source are in there, so if you can work further on it.

        But as Bigmac pointed out: it’s true, you should use the most recent version which has most of this new functionality build in. But I think it’s still a very good point to make a Proxy and send all navigation through this proxy…

  9. Murdoch on

    I found there was a few errors in the class. I have correct these. Code pasted below. Have yet to implement but atleast the class fires up now and I can start testing. Code below:

    package {
    import com.asual.swfaddress.*;
    import flash.events.EventDispatcher;
    import flash.events.Event;
    import flash.events.TimerEvent;
    import flash.utils.Timer;

    public class SWFAddressProxy extends EventDispatcher {
    /**
    * DEFINE SOURCE CONTROLLER VALUES HERE…
    * These are all private members that will be accessed through getters and setters.
    *
    */

    private var _section:String = “home”;
    private var _page:int = 1;

    /*
    *
    */

    // The following are mechanisms used by the controller…
    private var _internalizing:Boolean = false;
    private var _updateWasExternal:Boolean = false;
    private var _postExternalTimer:Timer;
    private var _init:Boolean = false;

    public function SWFAddressProxy():void {
    super();
    _postExternalTimer = new Timer(1000, 1);
    }

    /**
    * This event notifies of updates within the controller.
    * Other components should subscribe to this event, then update themselves with the latest controller values upon catching this event.
    * NOTE: you may want to define your own event type for this rather than just using a generic CHANGE event.
    */
    public function update():void {
    dispatchEvent(new Event(Event.CHANGE));
    }

    /**
    * Loads the controller’s configuration with optional parameters.
    * This should be called as the last step while starting up your application…
    * Make sure all necessary data and display is loaded, configured, and has subscribed to the controller’s CHANGE event.
    * Finally, call this load method to kick SWFAddress control into gear.
    *
    * @param ParamsObject
    * This parameter is a handy way to seed values within the application controller.
    * I generally pass in the root movie’s “loaderInfo.parameters” object, which includes all params that the SWF was embedded with.
    * During this load function, we can configure our controller with values from these startup params
    */
    public function load( obj_params:Object = null ):void {
    // first collect values from the params object (if provided).
    if (obj_params != null) {
    _internalizing = true;

    /*
    * DETECT AND SET STARTING PARAMS HERE…
    * Just check to see if your expected params were provided, then set their values if they were.
    * Make sure this process occurs while “_internalizing” has been disabled.
    *
    */

    if (obj_params.hasOwnProperty(“section”)) {
    section = obj_params[“section”];
    }
    if (obj_params.hasOwnProperty(“page”)) {
    page = obj_params[“page”];
    }

    /*
    *
    */

    _internalizing = false;
    }

    // Now kick-off SWFAddress. An INIT event should follow in a moment as a result.
    SWFAddress.addEventListener(SWFAddressEvent.INIT, this._onSWFAddressChange);
    SWFAddress.addEventListener(SWFAddressEvent.CHANGE, this._onSWFAddressChange);
    }

    //————————————————-
    // Controller value getters / setters
    //————————————————-

    /*
    * WRITE YOUR CONTROLLER VALUE GETTERS AND SETTERS HERE…
    * Create a getter and setter for each source value that you are storing within the controller.
    * When setting a value, validate the new provided value, and only set the value if you’re sure you want it.
    * Whenever a source value is changed, externalize the new values.
    *
    */

    public function get section():String {
    return _section;
    }
    public function set section(str_section:String):void {
    // Test that the value is not already set, and that the value is valid.
    // If the value passes validation, set the source value then externalize.
    if (str_section != _section && (str_section == “home” || str_section == “work” || str_section == “contact”)) {
    _section = str_section;
    _externalize();
    }
    }

    public function get page():Number {
    return _page;
    }
    public function set page(num_page:Number):void {
    // Test that the value is not already set, and that the value is valid.
    // You may also want to include additional validation specific to other controller values,
    // For example, you may want to validate the page number based on what the current section is.
    // If all tests pass, then again: set the validated value then externalize.
    if (num_page != _page && ! isNaN(num_page) && isFinite(num_page)) {
    _page = num_page;
    _externalize();
    }
    }

    /**
    * You may encounter a situation when multiple values need to be set at once (ie: section and page changes at the same time).
    * It’s not ideal to use the individual property setters in this case, becasue SWFAddress will need to externalize each one in repitition, and latency may cause unexpect results.
    * So, in this case it’s best just to create a method that will validate and set multiple values, then call for a single externalization.
    */
    public function setView(str_section:String, num_page:Number):void {
    var changed:Boolean = false;

    // validate/set section
    if (str_section != _section && (str_section == “home” || str_section == “work” || str_section == “contact”)) {
    _section = str_section;
    changed = true;
    }

    // validate/set page
    if (num_page != _page && ! isNaN(num_page) && isFinite(num_page)) {
    _page = num_page;
    changed = true;
    }

    // externalize if one or more of the values was changed.
    if (changed) {
    _externalize();
    }
    }

    /*
    *
    */

    //————————————————-
    // Read/Write to URL process.
    //————————————————-

    /**
    * Gets data from the URL and brings it into Flash.
    */
    private function _internalize():void {
    // do not allow internalizing within 1 second of having externalized.
    if (! _postExternalTimer.running) {
    // disable externalizing while collecting values.
    _internalizing = true;

    /*
    * COLLECT URL VALUES HERE…
    * You can collect data from the URL path (“/path/”) or from URL variables (“/?var=value”). There is an example of each below.
    * When internalizing values, use your getter/setter properties that will validate the input. DO NOT set your source values directly from the URL.
    *
    */

    _section = SWFAddress.getPath().split(“/”).join(“”);
    _page = new int(SWFAddress.getParameter(“page”));

    /*
    *
    */

    // re-enable externalizing, then push all validated fields back out to the URL.
    _internalizing = false;
    _externalize(true, true);
    }
    }

    /**
    * Pushes current Flash values out to the URL.
    */
    private function _externalize(bol_update:Boolean = true, bol_externalSource:Boolean = false):void {
    // Restrict ability to externalize while internalizing is in progress.
    if (! _internalizing) {
    /*
    * WRITE URL STRING HERE…
    * You can write values as path components or as variables (“/path/?var=value&var2=value”).
    * In the below example, we’ll write a URL formatted as “/section/?page=1”.
    *
    */

    // !! possibly deprecated !!
    //SWFAddress.pushValue( ‘/’+ _section +’/?page=’+ _page );
    // !! possibly deprecated !! now setValue
    SWFAddress.setValue( ‘/’+ _section +’/?page=’+ _page );

    /*
    *
    */

    _postExternalTimer.reset();
    _postExternalTimer.start();
    _updateWasExternal = bol_externalSource;
    if (bol_update) {
    update();
    }
    }
    }

    /**
    * Specifies if the last call to update came from outside of Flash (triggered by SWFAddress).
    * This is useful to know when trying to differentiate a user-submitted controller change from an internal application change.
    */
    public function get updateWasExternal():Boolean {
    return _updateWasExternal;
    }

    //————————————————-
    // Event handlers (don’t touch these)
    //————————————————-

    /** @private updates the application upon a change within SWFAddress. */
    private function _onSWFAddressChange(evt_swfaddress:Event):void {
    _internalize();

    // if the application has not yet initialized:
    if (!_init) {
    // Listen to post-externalization timer for complete.
    // After first call, the timer will call one additional externalize process.
    // This builds in enough latency to set the browser URL after JS has been initialized.
    _postExternalTimer.addEventListener(TimerEvent.TIMER_COMPLETE, this._onSetStartupURL);
    // flag as initialized.
    _init = true;
    }
    }

    /** @private called after first run of the post-externalization timer.
    * This pushes the application URL outward with enough latency that the browser will register it. */
    private function _onSetStartupURL(evt_swfaddress:Event):void {
    _postExternalTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, this._onSetStartupURL);
    _postExternalTimer.stop();
    _externalize(false);
    }
    }
    }

    • bigmac on

      Ah, I was reviewing one of my recent projects the other day and remembered that I wrote this class around a hacked version of SWFAddress 2.2 which I had gone in and added some events to. I remembered this when I tried to upgrade to SWFAddress 2.4 and got errors. Sorry about that – it had completely slipped my mind that I’d made some small improvements to SWFAddress to make this work. Looking over the latest build of SWFAddress, it looks like they’ve naively implemented pretty much everything that I had added in.

  10. Murdoch on

    Note: I am using this inside the Flash IDE in Strict Mode so might be fine in Flex Develop etc.

    Just little errors like…

    From:
    page = parseInt( SWFAddress.getParameter(“page”) );

    To:
    _page = new int(SWFAddress.getParameter(“page”));

    … and in one function I needed to go through and change $val to $section and $page depending on the if statement…

    Thank you very much for the class and I can’t wait to use it. Just posting this to help out people in a similar situation to me.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: