Friday, October 26, 2007

Flex and FileReference.Upload using Firefox and SSL

Either the SSL handshake doesn't complete because firefox creates a new session when using file protection sandboxing, or something more horrible is going on with the flash player - for whatever reason, file uploads in firefox over SSL don't work.

This post is a workaround until Adobe gets its act together and fixes this problem.
We will be using javascript and normal html forms to accomplish our upload.

First things first: Trap the case that the user is using firefox, and call a javascript function.

private function addFiles(s:String):void {
//if not IE:
if(flash.system.Capabilities.playerType != "ActiveX")
{
var imagename:String = "yourimagename";
flash.external.ExternalInterface.call("showUploadComponent",imagename);
}
else
{ //do normal image upload within flex
_refAddFiles = new FileReferenceList();
_typeUploadFile = s;

_refAddFiles.addEventListener(Event.SELECT, onSelectFile);
var imageTypes:FileFilter = new FileFilter("Images (*.jpg, *.jpeg, *.gif, *.png)", "*.jpg; *.jpeg; *.gif; *.png");
_refAddFiles.browse([imageTypes]);
}
}



Ok, now on the html file that hosts the swf, we need to make this javascript function available, and have it do the work.

<script language="JavaScript" type="text/javascript">
function showUploadComponent(imageName)
{
var w = window.open("","ARESUploadWindow","height=400, width=600,scrollbars=no,status=no,titlebar=no,toolbar=no");
var s = "<html><body><form method='POST' action='<?=$flexapi?>' ENCTYPE='multipart/form-data'><input type='hidden' name='request' value='imageUpload'/>Upload Image:<input type='file' name='"+imageName+"'/><br/><input type='submit' value='Upload'/></form></body></html>";
w.document.open();
w.document.write(s);
w.document.close();
}
function imageUploaded()
{
getFlexApp("<?=$swfname?>").updateImages();
}
function getFlexApp(appName) {
if (navigator.appName.indexOf ("Microsoft") !=-1) return window[appName];
else return document[appName];
}
</script>

Just after this script should be this part of the file that flex autogenerated:
<!-- BEGIN DeepLinking required section -->
<link rel="stylesheet" type="text/css" ...

Ok. So whats goign to happen here?
You perform an action within the flex app (clicking a button or something) which calls addFiles. In firefox, flex then calls the javascript function showUploadComponent. This method then creates a popup window, writes html to it to upload the image. Some of my code here will need to be changed for you - I'm using php and have some variable use in there to point it at the php file that accepts the upload. You will need to write this code yourself (it can be the same place fileReference.upload is posting to). The important thing is that this page after accepting the upload, then writes some html on success.
The way I do it in php is like so:

if($params['request']=="imageUpload")
{
if(UploadImages())
{
echo "<ok><script language='JavaScript' type='text/javascript'>window.close(); window.opener.imageUploaded();</script></ok>";
}
else
{
echo "<error type='imageUpload' description='Call to UploadImages() failed.'>";
print_r($results);
echo "</error>";
}

}


Basically, you'll want to print out that script that closes the window, and then calls the callback in window.opener. Lets revisit what that function does.
It calls getFlexApp and then calls a method updateImages inside the flex app. In my case, the user was uploading an image. I then wanted them to see their image inside the flex app after the upload completed. However, to accomplish javascript calling a flex method, you need to do something back in your flex app:
First you write the updateImages function (mine updates an image with id thumbImage):

public function updateDealImages():void {
this.thumbImage.source = "put where you saved the file to on the server here";
}

Then you need to register it as callable in your creationComplete function for the <Application creationComplete="onCreationComplete();">

public function onCreationComplete():void {
if (flash.external.ExternalInterface.available)
flash.external.ExternalInterface.addCallback("updateImages", updateImages);
}


Now you're good to go. This solution isn't out of the box ready, but should walk you through all the steps you need to write a javascript/html forms workaround for file upload in firefox under SSL.

Thursday, July 26, 2007

I'm back on the flex bandwagon!

Now I'm working for a company in San Diego, and I've made a very beautiful reporting engine for them. As I'm going through my code wanting to make it even easier to work with and make future reports, I came across the DataGridColumn itemRenderer - which imo was implemented incorrectly by adobe.
The problem: You want to pass the field to render in and reuse the same renderer many times, but theres no way to do that!

So, I found a few solutions online, but modified them slightly to require no actionscript coding within the UI component itself (a practice of mine I'm trying my damnedest to stick to, as it forces me to write simple reusable classes, though not always clean and easy to understand the internals of).

The result I aimed for was being able to do this:

<itemRenderers:MoneyRenderer field="roomRevenue" datagridcolumn="{this.roomRevenueDataGridColumn}"/>

roomRevenueDataGridColumn is the id of a datagridcolumn in some datagrid in the report, and "roomRevenue" is the XML field we want to be given a dollar sign and comma separators with two decimal spaces.

And here is MoneyRenderer.as file that enables this:

package com.somecompany.reports.itemRenderers
{
import mx.core.IFactory;
import mx.controls.dataGridClasses.DataGridColumn;
/*
Class used for converting a number to a dollar preceded, comma delimited, 2 decimal formatted string.
Sample Usage:
<itemrenderers:moneyrenderer id="theMoneyRenderer" field="roomRevenue" datagridcolumn="{this.roomRevenueDataGridColumn}"/>
*/
public class MoneyRenderer implements mx.core.IFactory
{
public var field:String;
public function set dataGridColumn(dgc:DataGridColumn):void {
dgc.itemRenderer=this;
}
public function newInstance():* {
return new MoneyClass(field);
}
}
}
import mx.controls.Text;
import mx.formatters.CurrencyFormatter;

class MoneyClass extends mx.controls.Text
{
public var field:String;
public function MoneyClass(f:String):void {
field=f;
}
override public function set data(value:Object):void {
super.data = value;

var cf:CurrencyFormatter = new CurrencyFormatter;
cf.currencySymbol = "$";
cf.precision = 2;

if (value != null) {
this.text = cf.format(value[field]);
}
super.invalidateDisplayList();
}
}

Tuesday, January 16, 2007

nullpointerexception in flex style compiling

So far my experience with flex builder's compiler and debugger has been amazing.
However I ran into my first encounter with bad error reporting today.

I would get java.exception.NullPointerException on the tag <mx:Style source="somestyle.css"/>. Hrm. Had to go through commenting out the stylesheet until it compiled to figure out that the swf I generated from an .fla where I didn't have fonts wouldn't coerce the system fonts into the expected names, so trying to access them in that swf generated the exception.
Annoying that the part of the css that caused the error wasn't pointed to - the compiler I guess calls some other java css compiler and can't bubble the details into its context.

Oh well, still a pretty easy thing to fix, but I felt like I was back in javascript or php land, commenting out partial pieces of code to find the culprit breaking the build =)

Monday, January 8, 2007

Progress Event in Flex's URLStream

The low down is that the progress event does not work like I wanted it to. In case anyone else is attempting to use it and running into problems, I feel for you.

I have a .net aspx page which stores the Response stream in the application state and just idles, so that other page activations can write to it when needed. I keep track of client ID's and all is good: I can send the right stuff to the right clients.

However, I needed to set up polling on URLStream.bytesAvailable because ProgressEvent wouldn't fire if only a few bytes had been received. I also have to send at least 300 bytes to get Response.Flush() to actually flush! Kind of annoying. I try to avoid setInterval like the plague. Another odd thing, before any bytesAvailable is shown, I have to send about 2000 bytes. Then I can detect changes within 3-400 bytes as they come in, even if separated by 10 seconds or 10 minutes.

Now I have a framework accomplishing server push where clients can interact very quickly and be aware of eachother's actions immediately.

Say a user posts a reply in a forum you're currently looking at. Well, his page activation to save his reply will trigger a server-side event listener you established on that forum, causing you to get the updated data BEFORE it's even written to the database. Your view updates the forum live as if it were some kind of chat application. No need to refresh constantly while at work and tabbing back into that forum you're interested in, just leave it up and see the replies already there when you tab back in.

This is a HUGE step forward imo. All this talk about web 2.0 and I'm excited to be part of it.. and to be doing it right; not with some polling ajax hack.

Flex rocks.

I haven't been posting a lot of my work lately because I got a talk from the boss about keeping it under wraps. So I likely won't be posting code here anymore.