I had a request from someone about binding a control to the flash.util.Timer's running property. They wanted a control to enable/disable each time the Timer started or stopped. Initially it sounded very simple, extend the Timer class and mark the running property as bindable. Not so fast though, the running property is read-only. If you mark a read-only property in AS3 as bindable you get a compile warning,
[Bindable] on read-only getter is unnecessary and will be ignored.
Ok, no worries, add an equivalent setter for the getter and then everything should be a go. Nope, in the Timer class there is no public or protected method for setting the running property.
I vaguely remembered a presentation from a conference where they were attempting to optimize Flex performance in an application with a lot of databinding. They defined custom events to notify the compiler what to dispatch when a value had changed for data binding.
After a little digging I found an excellent post by Josh Tynjala that outlined exactly what I wanted to do. By defining a custom event in the [Bindable] tag you can dictate where and when to dispatch an event to make the data binding for a specific property update. In the case of the Timer class I added this bindable tag [Bindable("timerChange")] above the running getter method.
Here is what the new Timer class looks like to make the running property bindable. Since the property changes on starts/stops I override those two methods and add a line to dispatch the new timerChange event. Works like a charm.
package com.dgrigg.utils
{
import flash.events.Event;
import flash.utils.Timer;
public class BindableTimer extends Timer
{
public function BindableTimer(delay:Number, repeatCount:int=0)
{
super(delay, repeatCount);
}
[Bindable(event="timerChange")]
override public function get running():Boolean
{
return super.running;
}
override public function stop():void
{
super.stop();
dispatchEvent(new Event("timerChange"));
}
override public function start():void
{
super.start();
dispatchEvent(new Event("timerChange"));
}
}
}
In case my infrequent blog posts don't whet your appetite for any words or wisdom, or drivel, that comes from my keyboard I'm now on Twitter. Hopefully I will tweet more often than I post to my blog. As with my blog, the tweets will be development related (for the most part). I won't be tweeting about the grocery store, the show I'm watching or the fact I'm stuck in traffic (oh yeah I work from home that doesn't happen). I'm sure coming up with 140 witty or intelligent characters should be much easier than a few paragraphs.
At least once a week I get an email or contact request along the lines of "I have a great idea and I would love to have you build it for me .... I can't pay much ... but I'll offer sweat equity". And that's about the time when I zone out. Keith Peters posted a great response to a similar message he came across. He caught the exact essence of what I think everytime it happens to me.
A few of my favourite lines from his response:
Just because you have a good idea? Most developers have good ideas too, and if they are going to risk not making anything for their hard work, they might as well balance that by doing their own work and making 100% of the profits.
I think most developers, when they look at working on someone else’s project, are not at all interested in investing in an idea. They are looking to do some work and get paid for it.
I agree with Keith, it's not always a bad arrangement. I have had a few very good opportunities come across my desk structured that way, but most are far from good. If you are one of those people considering getting a developer on board for sweat equity do your research first, make sure you have a really good business plan that makes sense to more than just yourself, and realize that no matter how great the idea, not to many people will work for free, especially when there are lots of paying opportunities available.
One of the great new features in the Flash 10 plugin is the option to read and write files locally between the browser and OS without involving a server side solution. This can come in extremely handy when you are trying to take processing load off the server and leverage the client's computer.
A perfect example is creating thumbnails. There are many instances where a site allows a user to upload an image and then save that along with some additional data on the site's server. When the image gets saved often a thumbnail and medium size version of the image get created. This means the server takes a few big hits: having the file upload to the server and then writing it and also creating and writing the smaller versions of the image. A jpeg from a decent digital camera can easily be 3, 4, 5+ megabytes. For most people there's going to be a wait in getting that file uploaded to the server and in most cases the site will likely never server that full size image up again. What if you could drastically cut the upload time and the amount of work the server needed to do to create thumbnails?
With Flash 10 it's now possible. Using the FileReference.load() method you can allow a user to select a file locally and load it directly into the Flash plugin without having to upload it to a server. Once the file has been loaded into Flash you can use the Bitmap and BitmapData classes to manipulate the file into a more manageable and usable size. For instance, I loaded a 2.9MB jpeg and converted it to a 125x93 jpeg and a 600x400 jpeg. The small size ended up being 8KB and the medium size was 60KB. Now I'm uploading a total of 68KB to the server instead of 2.9MB ... wow.
Here's the sample Flex code used to make this work. Nothing to special. The FileReference.browse() and FileReference.load() methods are used to select and load the image into the browser. Once the image has been loaded and you need to save it a few things need to happen. First the image data needs to get put into BitmapData and then encoded by the JPEGEncoder so it can be saved as a jpeg on the server. The final step is to base64 encode the image data before sending it down to the server. There is a way you can do this without doing the base 64 encoding but you can't send any additional parameters in the POST if you do. I think this is a much cleaner solution.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" horizontalAlign="left"
height="250" width="250"
creationComplete="init();">
<mx:Script>
<![CDATA[
import flash.utils.setTimeout;
import com.dgrigg.utils.Logger;
import mx.graphics.codec.JPEGEncoder;
import mx.utils.Base64Encoder;
import flash.net.FileReference;
import flash.net.FileFilter;
import mx.controls.Alert;
private var file:FileReference;
private function init():void
{
file = new FileReference();
file.addEventListener(Event.SELECT, handleFileSelect);
file.addEventListener(Event.COMPLETE, handleFileComplete);
}
private function selectFile(event:Event):void
{
var filter:Array = [new FileFilter("Images", "*.jpg;*.jpeg;*.png")];
file.browse(filter);
}
private function handleFileSelect(event:Event):void
{
file.load();
}
private function handleFileComplete(event:Event):void
{
image.source = file.data;
saveBtn.enabled = true;
}
private function saveFile(event:Event):void
{
//prepare the thumb image data for saving
var bmpData:BitmapData = new BitmapData(image.contentWidth, image.contentHeight);
bmpData.draw(image);
var jpg:JPEGEncoder = new JPEGEncoder(80);
var ba:ByteArray = jpg.encode(bmpData);
var base64:Base64Encoder = new Base64Encoder();
base64.encodeBytes(ba);
//save it
var loader:URLLoader = new URLLoader();
var request:URLRequest = new URLRequest("http://local.test.com/saveThumbnail.php");
request.method = URLRequestMethod.POST;
var data:URLVariables = new URLVariables();
data.fileData = base64;
data.fileName = "thumb_" + file.name;
request.data = data;
loader.addEventListener(Event.COMPLETE, handleImageSaved);
loader.load(request);
}
private function handleImageSaved(event:Event):void
{
var loader:URLLoader = event.target as URLLoader;
Alert.show("Thumbnail saved");
}
]]>
</mx:Script>
<mx:Button id="selectBtn" x="5" y="5"
label="Select Image" click="selectFile(event);"/>
<mx:Canvas x="5" y="35" width="129" height="129"
borderStyle="solid" borderColor="0x000000">
<mx:Image id="image" x="1" y="1" width="125" height="125"/>
</mx:Canvas>
<mx:Button id="saveBtn" x="5" y="175"
label="Save Thumbnail" click="saveFile(event);" enabled="false"/>
</mx:Application>
And here is the PHP code, super easy just read the POST variables to get the file data and file name, since the data was already base64 encoded you can put the file directly on the server without having to do a file open and file write.
<?
$file = base64_decode($_POST['fileData']);
file_put_contents($_POST['fileName'], $file);
echo "saved";
?> And if you are using Coldfusion, here's the snippet you need.
<cffile action="write"
file="#GetDirectoryFromPath(GetCurrentTemplatePath())#/cf_#FORM.filename#"
output="#ToBinary(FORM.filedata)#"/> Enjoy, your server will thank me.
Derrick Grigg is a Rich Internet Application (RIA) freelance contractor based in Toronto, Canada. He specializes in architecting and developing applications using a variety of technologies, most notably Flash, Flex and Coldfusion.