Archive for October, 2008|Monthly archive page

Just making it work isn’t good enough…

This post’s title says it all, and pretty well sums up my feelings towards my world of projects right now. I’ve got two hot projects. One is an interactive U.S. election map for a major news network, the other is my game-editor project: Lassie Shepherd. In both cases, I’ve come to the pulling-teeth end of being SO close, but still so far from their respective finish lines. What is it about the end of a project that makes it so tedious? I suppose it’s the fact that the primary development cycle aims to just get everything working; and like it or not, that cuts corners on the smaller details. And of course, at some point you’ve got to go back in and clean up all those rough edges. It’s a tedious job, and a serious time suck. However, the best part comes when you can finally take a step back at look at the mountain that you’ve been climbing, and realize that if it were a house, then even the broom closets would have mahogany floors and brass fixtures.

So, the end is in sight on these projects, and news about Shepherd will be forth coming. It’s become a very powerful game layout tool, despite the fact that it currently has no tie-in player application to run a game based on its compiled output. However, it publishes clean and legible XML, so my hope is that third parties may adapt the Shepherd editor for creating XML structures to feed into other front-end applications.

Advertisements

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

Rendering an image outline stroke

While Flash 8 image filters added some excellent (and easy to use) graphics capabilities to the Flash display arsenal, I feel like there was a blatant omission from the filter set… what about a simple outline filter that will render a border around an image? Of course, you can fake the effect with a glow filter set to 1000% strength, but that still has a soft edge and doesn’t accommodate aliased graphics. So, I’ve been working on a script to render an outlined image based on the source image’s alpha channel. Here’s the code thus far:

Save as: com/gmacwilliam/graphics/ImageOutline.as

package com.gmacwilliam.graphics
{
    import flash.display.IBitmapDrawable;
    import flash.display.BitmapData;
    import flash.display.Bitmap;
    import flash.display.DisplayObject;
    import flash.geom.Rectangle;
    import flash.geom.Point;

    public class ImageOutline
    {
        private static var _color:uint;
        private static var _hex:String = "";
        private static var _alpha:Number = 1;
        private static var _weight:Number = 2;
        private static var _brush:Number = 4;

        /**
        * Renders a Bitmap display of any DisplayObject with an outline drawn around it.
        * @note: see param descriptions on "outline" method below.
        */
        public static function renderImage(src:IBitmapDrawable, weight:int, color:uint, alpha:Number=1, antialias:Boolean=false, threshold:int=150):Bitmap
        {
            var w:int = 0;
            var h:int = 0;

            // extract dimensions from actual object type.
            // (unfortunately, IBitmapDrawable does not include width and height getters.)
            if (src is DisplayObject)
            {
                var dsp:DisplayObject = src as DisplayObject;
                w = dsp.width;
                h = dsp.height;
            }
            else if (src is BitmapData)
            {
                var bmp:BitmapData = src as BitmapData;
                w = bmp.width;
                h = bmp.height;
            }

            var render:BitmapData = new BitmapData(w, h, true, 0x000000);
            render.draw(src);

            return new Bitmap(ImageOutline.outline(render, weight, color, alpha, antialias, threshold));
        }

        /**
        * Renders an outline around a BitmapData image.
        * Outline is rendered based on image's alpha channel.
        * @param: src = source BitmapData image to outline.
        * @param: weight = stroke thickness (in pixels) of outline.
        * @param: color = color of outline.
        * @param: alpha = opacity of outline (range of 0 to 1).
        * @param: antialias = smooth edge (true), or jagged edge (false).
        * @param: threshold = Alpha sensativity to source image (0 - 255). Used when drawing a jagged edge based on an antialiased source image.
        * @return: BitmapData of rendered outline image.
        */
        public static function outline(src:BitmapData, weight:int, color:uint, alpha:Number=1, antialias:Boolean=false, threshold:int=150):BitmapData
        {
            _color = color;
            _hex = _toHexString(color);
            _alpha = alpha;
            _weight = weight;
            _brush = (weight * 2) + 1;

            var copy:BitmapData = new BitmapData(src.width+_brush, src.height+_brush, true, 0x00000000);

            for (var iy:int=0; iy <= src.height; iy++)
            {
                for (var ix:int=0; ix <= src.width; ix++)
                {
                    // get current pixel's alpha component.
                    var a:Number = (src.getPixel32(ix, iy) >> 24 & 0xFF);

                    if (antialias)
                    {
                        // if antialiasing,
                        // draw anti-aliased edge.
                        _antialias(copy, ix, iy, a);
                    }
                    else if (a > threshold)
                    {
                        // if aliasing and pixel alpha is above draw threshold,
                        // draw aliased edge.
                        _alias(copy, ix, iy);
                    }
                }
            }

            // merge source image display into the outline shape's canvas.
            copy.copyPixels(src, new Rectangle(0, 0, copy.width, copy.height), new Point(_weight, _weight), null, null, true);
            return copy;
        }

        /**
        * Renders an antialiased pixel block.
        */
        private static function _antialias(copy:BitmapData, x:int, y:int, a:int):BitmapData
        {
            if (a > 0)
            {
                for (var iy:int = y; iy < y+_brush; iy++)
                {
                    for (var ix:int = x; ix < x+_brush; ix++)
                    {
                        // get current pixel's alpha component.
                        var px:Number = (copy.getPixel32(ix, iy) >> 24 & 0xFF);

                        // set pixel if it's target adjusted alpha is greater than the current value.
                        if (px < (a * _alpha)) copy.setPixel32(ix, iy, _parseARGB(a * _alpha));
                    }
                }
            }
            return copy;
        }

        /**
        * Renders an aliased pixel block.
        */
        private static function _alias(copy:BitmapData, x:int, y:int):BitmapData
        {
            copy.fillRect(new Rectangle(x, y, _brush, _brush), _parseARGB(_alpha * 255));
            return copy;
        }

        /**
        * Utility to parse an ARGB value from the current hex value
        * Hex string is cached on the class so that it does not need to be recalculated for every pixel.
        */
        private static function _parseARGB(a:int):uint
        {
            return parseInt("0x"+ a.toString(16) + _hex);
        }

        /**
        * Utility to parse a hex string from a hex number.
        */
        private static function _toHexString(hex:uint):String
        {
            var r:int = (hex >> 16);
            var g:int = (hex >> 8 ^ r << 8);
            var b:int = (hex ^ (r << 16 | g << 8));

            var red:String = r.toString(16);
            var green:String = g.toString(16);
            var blue:String = b.toString(16);

            red = (red.length < 2) ? "0" + red : red;
            green = (green.length < 2) ? "0" + green : green;
            blue = (blue.length < 2) ? "0" + blue : blue;
            return (red + green + blue).toUpperCase();
        }
    }
}

The class has two public render methods: “outline()” receives and returns raw BitmapData data, so requires some pre and post methodology to be used in a display pattern. However, the “renderImage()” method is written to be display-ready. Feed it a reference to any DisplayObject and some render parameters, and it will return a Bitmap object that can be added directly to the stage. All render parameters are documented within the class comments, but for quick reference, the basic params include: [thickness, color, alpha, antialias]. A simple implementation would look like this:

import com.gmacwilliam.graphics.ImageOutline;
var outline:Bitmap = ImageOutline.renderImage(myMovieClipRef, 2, 0xFF0000, 1, false);
addChild(outline);

It’s a work in progress. It’ll render a pixel-perfect outline on aliased graphics, which is a huge plus if you need to work with jagged edges. The anti-aliasing methodology has been quite a challenge and still plateaus along some curves, in which case the 1000% glow filter may still look better. If anyone has experience in rendering anti-aliased pixels, please feel free to comment with tech notes!

AS3 Countdown Timer

Chapter three of Tucker Bowen’s Lassie game Something Amiss is on the horizon, so it’s time to get the countdown going on the Lassie website! What can is say? we love game releases!

So as I was putting the countdown banner together I recalled that this is a subject that I’ve been asked about several times in the past and have answered on several web forums: how do you make a countdown timer in Flash? My previous responses to this question have always been in AS2, so lets do something fresh and write it in AS3. Here’s a count down/up timer display composed as a class…

Save as: com/gmacwilliam/display/Countdown.as

package com.gmacwilliam.display
{
    import flash.display.Sprite;
    import flash.utils.Timer;
    import flash.events.TimerEvent;
    import flash.events.Event;
    import flash.text.*;

    public class Countdown extends Sprite
    {
        private var _countUp:TextField;
        private var _countDown:TextField;
        private var _timer:Timer;
        private var _startTime:Number;
        private var _expireTime:Number;
        private var _range:Number;

        public function Countdown(refreshSecs:Number, year:int, month:int=1, day:int=1, hour:int=0, minute:int=0, second:int=0, ms:int=0):void
        {
            super();

            // create TextField displays
            _countUp = new TextField();
            _countDown = new TextField();
            _countUp.defaultTextFormat = _countDown.defaultTextFormat = new TextFormat("_sans", 24);
            _countUp.antiAliasType = _countDown.antiAliasType = AntiAliasType.ADVANCED;
            _countUp.autoSize = _countDown.autoSize = TextFieldAutoSize.LEFT;
            _countUp.selectable = _countDown.selectable = true;
            _countUp.y = 0;
            _countDown.y = 50;
            addChild(_countUp);
            addChild(_countDown);

            // configure start, end, and duration values.
            _startTime = new Date().getTime();
            _expireTime = new Date(year, month-1, day, hour, minute, second, ms).getTime();
            _range = _expireTime - _startTime;

            // configure timer interval.
            _timer = new Timer(refreshSecs * 1000);
            _timer.addEventListener(TimerEvent.TIMER, this._onRefresh);
            _timer.start();
            refresh();
        }

        /**
        * Refreshes countdown display.
        */
        public function refresh(evt:Event=null):void
        {
            var elapsed:Number = new Date().getTime()-_startTime;
            _countUp.htmlText = _small("count up: ") + _getTimeDisplay(elapsed).toUpperCase();
            _countDown.htmlText = _small("count down: ") + _getTimeDisplay(_range-elapsed).toUpperCase();
        }

        /**
        * Disposes of countdown display so that it is eligible for garbage collection.
        */
        public function dispose():void
        {
            _timer.stop();
            _timer.removeEventListener(TimerEvent.TIMER, this._onRefresh);
            _timer = null;
        }

        /**
        * Generates countdown string from remaining time value.
        */
        private function _getTimeDisplay(remainder:Number):String
        {
            // days
            var numDays:Number = Math.floor(remainder/86400000);
            var days:String = numDays.toString();
            remainder = remainder - (numDays*86400000);

            // hours
            var numHours:Number = Math.floor(remainder/3600000);
            var hours:String = (numHours < 10 ? "0" : "") + numHours.toString();
            remainder = remainder - (numHours*3600000);

            // minutes
            var numMinutes:Number = Math.floor(remainder/60000);
            var minutes:String = (numMinutes < 10 ? "0" : "") + numMinutes.toString();
            remainder = remainder - (numMinutes*60000);

            // seconds
            var numSeconds:Number = Math.floor(remainder/1000);
            var seconds:String = (numSeconds < 10 ? "0" : "") + numSeconds.toString();
            remainder = remainder - (numSeconds*1000);

            // milliseconds
            //var numMilliseconds:Number = Math.floor(remainder/10);
            //var milliseconds:String = (numMilliseconds < 10 ? "0" : "") + numMilliseconds.toString();

            return "<FONT SIZE='24'>"+ days + _small("days") + hours + _small("hrs") + minutes + _small("mins") + seconds + _small("secs") + "</FONT>";
        }

        /**
        * Utility for wrapping value labels in a tag with smaller text.
        */
        private function _small(label:String):String
        {
            return "<FONT SIZE='12'> "+label+"</FONT> ";
        }

        /**
        * Timer event handler to call refresh.
        */
        private function _onRefresh(evt:Event):void
        {
            refresh();
        }
    }
}

Implementation of that example class is pretty simple. Just import the class into a FLA document, create an instance of the Countdown class and add it to your display. When creating a Countdown object, constructor parameters are as follows:

new Countdown(refreshIntervalSeconds:Number, year:int, [month:int=1, day:int=1, hour:int=0, minute:int=0, second:int=0, millisecond:int=0]);

The class will take care of creating all TextField displays, so you don’t need any supporting library items. The full instantiation of this countdown display looks like this:

import com.gmacwilliam.display.Countdown;
// Countdown displaying the time until Halloween 2008. Refreshes once a second.
addChild(new Countdown(1, 2008, 10, 31));

You’ll notice that this example includes timers that are counting both up and down to the target time. You probably won’t ever need to display both values at the same time, but this should illustrate how to arrive at either value. Feel free to adapt and adjust the class to your needs!