Archive for the ‘ActionScript 3’ Category

Embedding fonts in an ActionScript Project

Thursday, May 15th, 2008
Working with ActionScript projects can offer some rather interesting challenges with some of the most basic parts of Flash development. Things like preloading your application and using linked MovieClips from a library, not to mention using embedded fonts in your TextFields. This really becomes interesting when you want to use more than one font in a single TextField. And sure you can embed font files right into the AS class, but that doesn't always work. I've had numerous issues with otf fonts.

The method that I have had the best success with is using the same technique as embedding symbols from Flash AS2 swfs. Here's a step-by-step on how to do this.

1) Create a new FLA and set to publish to Flash 9 / AS2.

2) Create a New Font in the Library.

3) Set the Linkage to export for ActionScript on the first frame.

4) Publish the swf to a directory within your project.

5) Set non-embeded files to not copy to bin

6) Associate a standard Embed meta tag with a property of the class.
7) Create a new StyleSheet.
8) Create a style with fontFamily set to the name of the embedded symbol.
8) Assign the StyleSheet
package {
    import flash.display.Sprite;
    import flash.text.AntiAliasType;
    import flash.text.StyleSheet;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    
    [SWF(width="450", height="60", backgroundColor="#CCCCCC", frameRate="30")]
    public class EmbeddedFonts extends Sprite {
        private var _style:StyleSheet;
        private var _copy:TextField;
        
        [Embed(source="/embed/fonts.swf",symbol="Myriad Pro")]
        private var MyriadPro:Class;
        
        [Embed(source="/embed/fonts.swf",symbol="Myriad Pro Bold")]
        private var MyriadProBold:Class;
        
        public function EmbeddedFonts() {
            var main:Object = new Object();
            main.fontSize = 30;
            main.letterSpacing = 8;
            main.textAlign = "center";
            main.color = "#FFFFFF";
            main.fontFamily = "Myriad Pro";
            
            var main_bold:Object = new Object();
            main_bold.fontSize = 30;
            main_bold.letterSpacing = 8;
            main_bold.textAlign = "center";
            main_bold.color = "#000000";
            main_bold.fontFamily = "Myriad Pro Bold";
            
            _style = new StyleSheet();
            _style.setStyle(".main", main);
            _style.setStyle(".main_bold", main_bold);
            
            _copy = new TextField();
            addChild(_copy);
            _copy.autoSize = TextFieldAutoSize.LEFT;
            _copy.embedFonts = true;
            _copy.selectable = false;
            _copy.mouseWheelEnabled = false;
            _copy.styleSheet = _style;
            _copy.htmlText = "<span class='main'>I </span><span class='main_bold'>LIKE </span><span class='main'>MONKEYS!</span>";
            _copy.x = 450/2 - _copy.textWidth/2;
            _copy.y = 60/2 - _copy.height/2;
        }
    }
}
9) And voila.

The dropTarget conspiracy

Wednesday, May 7th, 2008
While working on an application that has some drag and drop functionality, I noticed something very interesting about the dropTarget property of the Sprite class. It seems that the dropTarget returns the DisplayObject that is at the bottom of the display list which may not yield the result you are expecting. I added a MouseEvent.MOUSE_MOVE event listener to the Sprite being dragged and traced out the dropTarget to see what kind of result I would get.

Instead of seeing the names of Sprite instances that were set-up on the timeline, the trace output was nothing but generic names such as instance42. The reason for this is that the object at the very bottom of the display list is not the Sprite that was created, but the contents of that Sprite such as a Shape or Bitmap. It seems that the Flash Player automatically creates a DisplayObject for these internal Sprite elements and assigns them a unique instance name. Tracing out the dropTarget.parent then reveals the DisplayObject that we're looking for. One thing to note is that a Sprite that only has visual elements that were created with the drawing API will not exhibit this behavior.

There are a couple of other things to be aware of when using the dropTarget. First, setting the mouseEnabled or mouseChildren properties to false will not keep a DisplayObject from being assigned to the dropTarget property. Second, depth has no effect on the assignment of a DisplayObject to the dropTarget propery. Objects at a higher depth than the object being dragged will react the same way as those that are under it. Third and probably the most important is that the assignment of the value for the dropTarget property is that of the DisplayObject that is directly under the mouse and has nothing to do with the bounding box of the DisplayObject being dragged. Lastly, there are definitely situations where the dropTarget will be null, so be sure to check for that before trying to access properties of the dropTarget or you'll get everyone's favorite error - TypeError: Error #1009: Cannot access a property or method of a null object reference.

example:
import flash.events.MouseEvent;
import flash.display.Sprite;

dragee.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
dragee.addEventListener(MouseEvent.MOUSE_UP,mouseUpHandler);
addEventListener(MouseEvent.MOUSE_MOVE,mouseMoveHandler);

function mouseDownHandler(evt:MouseEvent):void {
    dragee.startDrag();
}

function mouseUpHandler(evt:MouseEvent):void {
    dragee.stopDrag();
}

function mouseMoveHandler(evt:MouseEvent):void {
    if (dragee.dropTarget!=null) trace(dragee.dropTarget.name);
}
May the Null Object be your Friend.

Dispatching events between AS2 and AS3

Wednesday, February 6th, 2008
Even though AS3 has been out for some time now, there are still situations when you will need to load an AS2 compiled SWF into an AS3 compiled SWF and communicate with it. A good example would be if you need to use the ActionSource component for Omniture tracking or some other AS2 based Flash component in a Flash 9 or Flex 2 project. Well, what if you could simply dispatch an event and listen for it on the other side? That's what ASBridge does.

ASBridge consists of 4 classes, ASBridge and ASBridgeEvent classes for both AS2 (cookiejar package) and AS3 (sugarcookie package). The core idea of ASBridge comes from the Adobe LiveDocs LocalConnection entry, check out the second paragraph where it says that AS 1,2, and 3 can communicate with each other using a LocalConnection, that's where the magic is. Only thing to really be aware of is the 40kb limit on the amount of data that can be passed as arguments to the send() method.

Displayed below is the source code for the ASBridge_Example class which is the main class for the AS3 Project and the AS2_ASBridge class used in the Flash 8 Fla with brief descriptions. To get all the source code including the ASBridge classes for both AS2 and AS3, download all the source as an Eclipse ActionScript 3 Project.

To use the ActionScript 3 Project zip:
- unzip file anywhere
- open Eclipse
- File -> Import, choose Existing Projects into Workspace and click next
- browse for the root of the extracted directory
- click Finish

ASBridge_Example
Demonstrates loading a AS2 SWF into an ActionScript 3 Project generated SWF and using ASBridge to send and receive events with data between AS3 and AS2.

package {
    import com.sugarcookie.adapter.ASBridge;
    import com.sugarcookie.adapter.events.ASBridgeEvent;
    
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.net.URLRequest;

    public class ASBridge_Example extends Sprite {
        private var _adapter:ASBridge;
        private var _loader:Loader;
        
        public function ASBridge_Example() {
            _adapter = ASBridge.getInst();
            _adapter.addEventListener("onAdapterReady",onAdapterReady);
            
            _loader = new Loader();
            _loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
             var request:URLRequest = new URLRequest("AS2_ASBridge.swf");
            _loader.load(request);

            addChild(_loader);
        }
        
        private function onLoadComplete(evt:Event):void {
            trace("onLoadComplete: "+evt.toString());
        }
        
        private function onAdapterReady(evt:ASBridgeEvent):void {
            trace("successfully received event with data from AS2");
            trace("onAdapterReady: "+evt.toString());
            _adapter.sendEvent("onBridgeComm",{id:"test data"});
        }
    }
}

AS2_ASBridge
Demonstrates sending and receiving events with data between AS2 and AS3 implemented as a class that is instantiated on the timeline of a Flash 8 Fla.
import com.cookiejar.adapter.ASBridge;
import com.cookiejar.adapter.events.ASBridgeEvent;

class AS2_ASBridge {
    private var _adapter:ASBridge;
    
    public function AS2_ASBridge() {
        _adapter = ASBridge.getInst();
        _adapter.addEventListener("onBridgeComm",this);
        _adapter.sendEvent("onAdapterReady",{status:true});
    }
    
    private function onBridgeComm(evt:ASBridgeEvent):Void {
        trace("successfully received event with data from AS3");
    }
}

Getting rid of the Flex 2 preloader

Thursday, December 20th, 2007
At some point in your Flex career you have probably wanted to get rid of the generic Flex 2 preloader that you always see when your application initially loads. If your application is fairly light weight there really is no need for it and why have an application that is easily recognizable as Flex. Luckily the answer is simple. All you have to do is set the usePreloader attribute to false.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" usePreloader="false">
</mx:Application>

Flex 2 and Flash 9 play nice
with DisplayObjectWrapper

Wednesday, October 17th, 2007
If you're a Flash developer making the switch to start using Flex, then this class from Grant Skinner is for you. DisplayObjectWrapper makes life much easier by enabling developers to easily get anything that inherits from DisplayObject (Sprite, Loader and Bitmap to name a few) to become a display child of a Flex container (Canvas, ViewStack, Accordion, etc). Awesome, now here are some code examples of how to work with SWFs in Flex 2.

This mxml file shows how to:
- load a Flash 9 SWF
- call a method within the document class of the SWF
- receive an event from the loaded SWF

One thing to keep in mind is the Flex security model. When you compile a Flex application it is by default set to be in the local-with-networking sandbox and the default for Flash 9 is local-with-filesystem which means the Flex application will not load the SWF when run locally. You will have to change the Local playback security setting in the Publish Setting window to Access Network Only.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" horizontalAlign="center" verticalAlign="middle" applicationComplete="init();">
    <mx:Script>
        <![CDATA[
            import com.gskinner.ui.DisplayObjectWrapper;
            import flash.display.Loader;
            import flash.events.*;
            import flash.net.URLRequest;
            import flash.system.ApplicationDomain;
            import flash.system.LoaderContext;
            
            private var loader:Loader;
            private var loadedSWF:DisplayObject;
            
            private function init():void {
                stage.scaleMode = StageScaleMode.NO_SCALE;
                stage.align = StageAlign.TOP_LEFT;
                
                loader = new Loader();
                configureListeners(loader.contentLoaderInfo);
                
                var context:LoaderContext = new LoaderContext();
                context.applicationDomain = ApplicationDomain.currentDomain;
                var request:URLRequest = new URLRequest("fnine.swf");
                loader.load(request,context);
            }
            
            private function configureListeners(dispatcher:IEventDispatcher):void {
                dispatcher.addEventListener(Event.COMPLETE, completeHandler);
                dispatcher.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
                dispatcher.addEventListener(Event.INIT, initHandler);
                dispatcher.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
                dispatcher.addEventListener(Event.OPEN, openHandler);
                dispatcher.addEventListener(ProgressEvent.PROGRESS, progressHandler);
                dispatcher.addEventListener(Event.UNLOAD, unLoadHandler);
            }
            
            private function completeHandler(event:Event):void {
                trace("completeHandler: " + event);
                //add the loaded swf to the display list
                loadedSWF = loader.content;
                displayArea.addChild(new DisplayObjectWrapper(loadedSWF));
                
                //listen for an event from the loaded SWF
                loadedSWF.addEventListener("fnineEvent", onFnineEvent);
                //call a method on a loaded SWF
                loadedSWF["testCall"]();
            }
            
            private function onFnineEvent(evt:Event):void {
                trace("onFnineEvent: " + evt);
            }
    
            private function httpStatusHandler(evt:HTTPStatusEvent):void {
                trace("httpStatusHandler: " + evt);
            }
    
            private function initHandler(evt:Event):void {
                trace("initHandler: " + evt);
            }
    
            private function ioErrorHandler(evt:IOErrorEvent):void {
                trace("ioErrorHandler: " + evt);
            }
    
            private function openHandler(evt:Event):void {
                trace("openHandler: " + evt);
            }
    
            private function progressHandler(evt:ProgressEvent):void {
                trace("progressHandler: bytesLoaded=" + evt.bytesLoaded + " bytesTotal=" + evt.bytesTotal);
            }
    
            private function unLoadHandler(evt:Event):void {
                trace("unLoadHandler: " + evt);
            }
        ]]>
    </mx:Script>
    <mx:Canvas id="displayArea" width="500" height="300" backgroundColor="0xFFFFFF" />
</mx:Application>

Also noteworthy is that the DisplayObjectWrapper is actually the parent of the loaded SWF. In very simplistic terms the display hierarchy is Flex Application > Container > DisplayObjectWrapper > SWF.

This class is the Document class for the SWF.
package {
    import flash.display.Sprite;
    import flash.events.Event;
    
    public class fnineApp extends Sprite {
        public function fnineApp() {
            trace("fnineApp.swf constructor");
        }
        
        public function testCall():void {
            trace("testCall() method called on fnineApp.swf");
            dispatchEvent(new Event("fnineEvent",true));
        }
    }
}
This basic idea will allow you to do all kinds of fun stuff. Enjoy.

Recording and streaming video with FM2 and AS3

Friday, October 12th, 2007
For some reason all of the code examples for using FM2 are in AS2. Converting the examples to AS3 can be a bit challenging, especially since there is one really key piece of information that is left out that is new to AS3 and specific to the use of FM2. The problem is the that FM2 only supports AMF0, but in AS3 the default setting for the NetConnection class' defaultObjectEncoding property is AMF3. Without changing this setting to AMF0, your code will seem to be correct, but will fail to connect without really telling you why. Check out the class that I created from the Adobe example and the MXML to use it.
package
{
    import flash.events.*;
    import flash.media.Microphone;
    import flash.media.Camera;
    import flash.media.Video;
    import flash.net.NetConnection;
    import flash.net.NetStream;
    import mx.controls.VideoDisplay;

    public class StreamingFLV extends EventDispatcher {
        private var cam:Camera;
        private var mic:Microphone;
        private var rtmp:String;
        private var nc:NetConnection;
        private var ns:NetStream;
        private var local_video:VideoDisplay;
        private var _rtmp:String;
        private var _source:String;

        public function StreamingFLV(_lv:VideoDisplay,_r:String,_s:String) {
            _rtmp = _r;
            _source = _s;

            cam = Camera.getCamera();
            cam.addEventListener(ActivityEvent.ACTIVITY, activityHandler);
            cam.addEventListener(StatusEvent.STATUS, handleCameraStatus);

            mic = Microphone.getMicrophone();
            mic.addEventListener(ActivityEvent.ACTIVITY, activityHandler);
            mic.addEventListener(StatusEvent.STATUS, handleCameraStatus);

            local_video = _lv;

            NetConnection.defaultObjectEncoding = flash.net.ObjectEncoding.AMF0;
            nc = new NetConnection();
            nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
            nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
            nc.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
            nc.connect(_rtmp);
        }

        public function startRecordFLV():void {
            local_video.attachCamera(cam);
            ns.attachAudio(mic);
            ns.attachCamera(cam);
            ns.publish(_source, "record");
        }

        public function stopRecordFLV():void {
            ns.close();
            local_video.close();
        }

        public function startPlayback():void {
            local_video.source = _rtmp +"/"+ _source + ".flv";
        }

        public function stopPlayback():void {
            local_video.close();
        }

        private function connectStream():void {
            ns = new NetStream(nc);
            ns.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
            ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
            ns.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
            ns.client = this;

            dispatchEvent(new Event("onVideoReady"));
        }

        private function handleCameraStatus(event:StatusEvent):void {
            trace("handleCameraStatus: " + event);
        }

        private function activityHandler(event:ActivityEvent):void {
            trace("activityHandler: " + event);
        }

        private function netStatusHandler(event:NetStatusEvent):void {
            trace(event.info.code);
            switch (event.info.code) {
                case "NetConnection.Connect.Success":
                    connectStream();
                    trace("Connected");
                    break;
                case "NetStream.Play.StreamNotFound":
                    trace("No Connection!");
                    break;
            }
        }
        private function securityErrorHandler(event:SecurityErrorEvent):void {
            trace("securityErrorHandler: " + event);
        }
        private function ioErrorHandler(event:AsyncErrorEvent):void {
            trace("ioErrorHandler: " + event);
        }
        private function asyncErrorHandler(event:AsyncErrorEvent):void {
            trace("asyncErrorHandler: " + event);
        }
        private function onMetaData(info:Object):void {
            trace("metadata: duration=" + info.duration + " width=" + info.width + " height=" + info.height + " framerate=" + info.framerate);
        }
        private function onCuePoint(info:Object):void {
            trace("cuepoint: time=" + info.time + " name=" + info.name + " type=" + info.type);
        }
        private function onPlayStatus(info:Object):void {
            trace(info.toString());
        }

    }
}
And now the MXML:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    paddingTop="0"
    paddingLeft="0"
    paddingBottom="0"
    paddingRight="0"
    horizontalAlign="center"
    verticalAlign="middle"
    backgroundGradientAlphas="[0,0]"
    backgroundColor="#CCCCCC"
    applicationComplete="init();">
    <mx:Script>
        <![CDATA[
            import StreamingFLV;
            import flash.media.Camera;
            import flash.display.StageAlign;
            import flash.display.StageScaleMode;

            private var rFLV:StreamingFLV;
            private var pFLV:StreamingFLV;
            private var isPlaying:Boolean = false;
            private var isRecording:Boolean = false;

            private function init():void {
                stage.scaleMode = StageScaleMode.NO_SCALE;
                stage.align = StageAlign.TOP_LEFT;
            }

            private function initRecordStream():void {
                rFLV = new StreamingFLV(video_record,"rtmp://los1aps-flash/zflv/test","recordSample");
                rFLV.addEventListener("onVideoReady",initUI);
            }

            private function initPlayStream():void {
                pFLV = new StreamingFLV(video_play,"rtmp://los1aps-flash/zflv/test","recordSample");
                pFLV.addEventListener("onVideoReady",initUI);
            }

            private function recordToggle():void {
                if (!isPlaying) {
                    record.label = "RECORDING";
                    rFLV.startRecordFLV();
                } else {
                    record.label = "RECORD";
                    rFLV.stopRecordFLV();
                }
                isPlaying = !isPlaying;
            }

            private function playToggle():void {
                if (!isPlaying) {
                    play.label = "PLAYING";
                    pFLV.startPlayback();
                } else {
                    play.label = "PLAY";
                    pFLV.stopPlayback();
                }
                isPlaying = !isPlaying;
            }

            private function initUI(evt:Event):void {
                record.enabled = true;
                play.enabled = true;
            }
        ]]>
    </mx:Script>
    <mx:VBox horizontalAlign="center">
        <mx:HBox>
            <mx:VBox horizontalAlign="right">
                <mx:VideoDisplay id="video_record" width="320" height="240" creationComplete="initRecordStream();" />
                <mx:Button id="record" label="record" enabled="false" click="recordToggle();" />
            </mx:VBox>
            <mx:VBox horizontalAlign="left">
                <mx:VideoDisplay id="video_play" width="320" height="240" creationComplete="initPlayStream();" />
                <mx:Button id="play" label="play" enabled="false" click="playToggle();" />
            </mx:VBox>
        </mx:HBox>
    </mx:VBox>
</mx:Application>
Enjoy!

Working with Flex 2/AS3 will influence your AS2 development

Wednesday, June 13th, 2007
There are a lot of really great features within Flex 2 that Flash developers dream of having, but few know they already exist. When I moved to Flex 2 from Flash based AS2 development it was nice to see some native classes that had similar functionality to those that I had developed for my AS2 class library. In fact it gave me some really great ideas on how to improve some of my AS2 classes and even develop others from scratch. It's not too hard to emulate portions of the Flex 2 framework within AS2.

Once you start to use Flex 2 classes like the ArrayCollection, ObjectUtil and Logger it will be easy to see why you should take the time to make this functionality available to you in AS2. Be sure to peruse the Flex 2 Language Reference for some good ideas. You'll also probably want to start using Eclipse for your AS2 development, if so you should check out ASDT or FDT.

The reality for most Flash developers that are getting into Flex 2 or AS3, is that you will continue to have to do both AS2 and AS3 for some time to come. It would be great if the adoption rate of new Flash versions by clients would be as fast as the plug-in. But don't forget, there are still poor soles that are required to develop in Flash 6. Let's give them a moment of silence...

Sharing Links Is Good Karma

Tuesday, May 22nd, 2007
A few good links is all you need to learn something new off the interweb. Thought I would share a few of the ones I've come across so far in my path to Flex2/AS3 enlightenment. enjoy.

Adobe's ActionScript 3.0 Language and Components Reference
Adobe's Flex 2 Language Reference
Flex and ActionScript Error Codes Wiki
Good, Fast, Cheap
Oscar Trelles
jwopitz - flex/flash exploration

If you have some good links and want to improve your karma, please post a comment and share.

Remoting with Flex 2 and WebOrb

Thursday, May 17th, 2007
Remoting with Flex 2 and WebOrbRemoting is one of the coolest ways to get data from your server-side services or destinations as they are know in ActionScript. WebOrb was unbelievably easy to set-up and works great. In comparison to AMFPHP, it was considerably easier to set-up, required no modification to my PHP classes and has a very similar set-up to that of FDS. I found the documentation somewhat light, although the Quick Start guide was very helpful.

The only issue was an error in the WebOrb PHP code that is part of the install which generated this error message in the Eclipse console, "ReferenceError: Error #1056: Cannot create property isError on mx.messaging.AcknowledgeMessage." This seemed not to cause any problems when remoting except for the error message that kept showing up. Like any other anal developer I just couldn't have this, luckily the fix for this was fairly easy and I found it here. It was such a simple bug, I found it very surprising that Midnight Coders would release software with this error. Once I got past this everything has been perfect since.

So, what really made me choose WebOrb over AMFPHP? There are several reasons, but mainly the fact that it supports multiple languages and is upgradeable to include FDS comparable features for languages other than JAVA. Also they offer professional paid support, which can make a huge difference when deploying a large application for an important client and offer security features and a handy management console with the professional version. The final factor was that I found some good recommendations for its usage on sites that I respect and trust.

Preparing for ActionScript 3

Thursday, May 3rd, 2007
We're about to start some pretty big projects for some great clients and best of all they are going to be Flex 2/Flash 9. It's nice to have clients that want to use the latest and greatest just because they are. They scary part is that we have a lot to learn in short period of time, so I thought I would share what we're doing to ramp up for the challenge. Mainly there's a lot of reading. Here's a few titles that are already available at your book store that are always worth the cover price.
  • ActionScript 3.0 Cookbook:
    Solutions for Flash Platform and Flex Application Developers
  • Foundation Actionscript 3.0 Animation: Making Things Move!
As usual not all the classics are out yet, mainly Colin Moock's book. But there is a ray of hope and a way to get it before it comes out. O'Reilly has this new feature called 'Rough Cuts' that allows you to get PDF versions of upcoming releases well before they come out. Go to www.oreilly.com, search for the following titles and buy the Rough Cuts. It costs $9.99 to start which gets you the PDF of the book in it's current iteration. When it comes out you get charged the remaining price of the book and it ships to you. The best part is that you can give feedback to the authors and potentially help shape the final outcome of the book. Here's the one's that I got:
  • ActionScript 3.0 Design Patterns
  • Essential ActionScript 3.0
  • Programming Flex 2.0
There are also a fair amount of sites that have great information, but here is one that I found particularly interesting that a co-worker sent to me. http://actionscriptcheatsheet.com/blog/quick-referencecheatsheet -for-actionscript-20/

This is also a good resource by Adobe:
http://livedocs.adobe.com/flex/2/langref/migration.html

As we get more into the development of our projects I'll post more about our experiences, sample code and eventually the sites themselves.