Archives for August 2007

Flex and Flash file uploading with return data

The addition of the FileReference class in AS2 made file uploading significantly easier in Actionscript. However, one issue that remained problematic was the ability to get detailed information back from the server once the upload was complete. The best you could do was to get a '200' response saying the file upload had completed, but there was no easy way to return additional data from the server with respect to the uploaded process .... until now.

Adobe has finally added the missing piece to accomplish a nice tight uploading process, the UPLOAD_COMPLETE_DATA event (Flash Player 9.0.28.0). This event fires after the COMPLETE event and contains an important piece of information. The data property of the DataEvent contains the raw data returned from the server after a successful file upload. Finally, a way to return from the server information about the file and/or process. This is especially useful if during the upload process you need to do additional things like create a database record and then return the new database id, or possibly put the file in a different path based on the user, and return the path where the file exists back to the client.

Here's a quick sample of implementing the new event in Flex and also a Coldfusion and PHP script to upload a file and then output and return data back to the client in a clean consistent fashion.

The Flex code. As you can see, this is a very simple example, you select a file and when the upload has completed the results from the server are output into a text area.

<?xml version="1.0" encoding="utf-8"?>
<!--
	Derrick Grigg
	derrick@dgrigg.com
	http://www.dgrigg.com
	created on August 2, 2007
	
	A simple file upload process with data returned from the server
	using the UPLOAD_COMPLETE_DATA event.
-->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
	<![CDATA[
	public var file:FileReference;
	
	public function selectFile():void 
	{
		file = new FileReference();
		file.addEventListener(Event.SELECT, fileSelected);
		file.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, uploadDataComplete);
		file.addEventListener(Event.COMPLETE, uploadComplete);
		file.addEventListener(IOErrorEvent.IO_ERROR, handleError);
		file.browse();
	}
	
	public function handleError(event:IOErrorEvent):void 
	{
		status_txt.text = 'ERROR: ' + event.text + '\n';
	}
	public function fileSelected(event:Event):void
	{
		file = FileReference(event.target);
		file_txt.text = file.name;
		status_txt.text = 'upload file: '+ file.name  + '\n';
		
		var request:URLRequest = new URLRequest();
    	request.url = "uploader.cfm";
		file.upload(request);			
	}
	
	public function uploadDataComplete(event:DataEvent):void 
	{
		var result:XML = new XML(event.data);
		status_txt.text += 'Upload Data Complete\n'
		status_txt.text += 'RESULT: ' + result.toString()  + '\n'
		status_txt.text += 'STATUS: ' + result.status + '\n';
		status_txt.text += 'MESSAGE: '+ result.message;
	}
	
	public function uploadComplete(event:Event):void 
	{
		status_txt.text += 'Upload complete\n';

	}
	]]>
</mx:Script>
<mx:VBox>
	<mx:TextInput id="file_txt"/>
	<mx:Button id="select_btn" label="select" click="selectFile();"/>
	<mx:TextArea id="status_txt" width="400" height="200"/>
</mx:VBox>
</mx:Application>

The Coldfusion and PHP scripts upload a file and then output a simple XML string stating if the upload worked. You could easily modify this to return back any additional data you needed. The scripts are very simplified for demonstration purposes, you would definitely want to make them more robust in a production environment.

The Coldfusion upload script.

<cfprocessingdirective  suppresswhitespace="true">
<cftry>
	<cffile action="upload" fileField="filedata" destination="c:\wamp\www\test\flex" nameconflict="overwrite">
	<cfxml variable="status"><result><status>OK</status><message><cfoutput>#filename#</cfoutput> uploaded successfully.</message></result></cfxml>
	<cfcatch>
		<cfxml variable="status"><result><status>Error</status><message><cfoutput>#cfcatch.Message#</cfoutput></message></result></cfxml>
	</cfcatch>
</cftry>
<cfoutput>#status#</cfoutput>
</cfprocessingdirective>

The PHP upload script

<?php
 
$upload_dir = $_SERVER['DOCUMENT_ROOT'] .  dirname($_SERVER['PHP_SELF']) . '/';
$upload_url = "http://".$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']) . '/';
$message ="";

$temp_name = $_FILES['Filedata']['tmp_name'];
$file_name = $_FILES['Filedata']['name']; 
$file_name = str_replace("\\","",$file_name);
$file_name = str_replace("'","",$file_name);
$file_path = $upload_dir.$file_name;

$result  =  move_uploaded_file($temp_name, $file_path);
if ($result)
{
	$message =  "<result><status>OK</status><message>$file_name uploaded successfully.</message></result>";
}
else 
{
	$message = "<result><status>Error</status><message>Somthing is wrong with uploading a file.</message></result>";
}
 
echo $message;
?> 

You can download the sample files here.

I'm glad to see Adobe finally added this, it makes life so much easier.

Datagrid runtime item renderers

My posts on the Flex 2 DataGrid renderers have received a fair amount of traffic and some questions on how to add and/or change the item renderers at runtime. I finally had a need to try this in a project so now I can share the 'how' with everyone else.

Data Grid adding and changing columns with item renderers at runtime.

Right click to view the source.

  • Step 1.

    Create the basic item renderer. One of the things I needed to accomplish with my item renderer was the ability to add it to different columns (ie the dataField was not always the same). This meant I needed a way from within the renderer to determine what column it was bound to so I could get and display the correct data. To do this the renderer needs to implement the IDropInListItemRenderer. This interface allows the renderer to have access to information about the list and column it is in via the BaseListData and DataGridListData classes. The DataGridListData gives you everything you need to get the data required to make a flexible, reusable renderer.

    Here is my basic image item renderer. Nothing to exciting, but it is very reusable. I can drop this into any datagrid to render an image column and it will work.

    <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" 
    	implements="mx.controls.listClasses.IDropInListItemRenderer">
    	
    	<mx:Script>
    		<![CDATA[
    		import mx.controls.dataGridClasses.DataGridListData;
    		import mx.controls.listClasses.BaseListData;
    			
    		// Make the listData property bindable.
        		[Bindable("dataChange")]
    		private var _listData : BaseListData;    
    			
    		
    		public function get listData() : BaseListData
    		{
    			return _listData;            
    		}                                        
    	
    		public function set listData( value : BaseListData ) : void
    		{
    			_listData = value;
    				
    		}
    		]]>
    	</mx:Script>
    	<mx:Image source="{data[(DataGridListData(listData).dataField)]}"/>
    </mx:Canvas>
    
  • Step 2.

    Modify the renderer so that it can be added at runtime. To do this the renderer needs to implement the IFactory interface. This will allow the renderer to be added at any point during runtime. Without implementing this interface it won't work, period. Below is the code to implement the interface.

    <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" 
    	implements="mx.controls.listClasses.IDropInListItemRenderer, mx.core.IFactory">
    
    public function newInstance():*
    {
       return new ImageRenderer();
    }
    

  • Step 3.

    The code to change a column at runtime from a basic column to an item renderer column. Get the columns from the datagrid, modify the column you are interested in and then reassign the columns to the datagrid. Notice that when you set the column's itemRenderer value you must create a new instance of the desired item renderer.

    private function switchStatusColumn(event:MouseEvent):void 
    {
    	var cols:Array = users_dg.columns;
    	var col:DataGridColumn = cols[1] as DataGridColumn;
    	col.itemRenderer = new CheckBoxRenderer();
    	users_dg.columns = cols;	
    }
    

  • Step 4.

    The code to add a new column with an item renderer at runtime. This is almost identical to modifying an existing column. The only difference is that we push the new column into the columns array.

    private function addImageColumn(event:MouseEvent):void 
    {
    	var cols:Array = users_dg.columns;
    	var col:DataGridColumn = new DataGridColumn();
    	col.itemRenderer = new ImageRenderer();
    	col.dataField = 'emoticon';
    	col.headerText = 'Image';
    	cols.push(col);
    	users_dg.columns = cols;	
    }
    

Hope this helps all those who were asking how to do this.