Wednesday, 28 May 2008

Flex File Upload using Grails Backend

This file upload application is based off the one by Nocturnal at Coding Cowboys, a Flex client with a PHP backend. The client has been stripped down to it's core functions and PHP replaced with a Grails backend.

The Grails backend is a file controller with an action defined:

def upload = {
if(request.method == 'POST') {
Iterator itr = request.getFileNames();

while(itr.hasNext()) {
MultipartFile file = request.getFile(itr.next());
File destination = new File(file.getOriginalFilename())

if (!file.isEmpty()) {
file.transferTo(destination)
// success
}
else
{
// failure
}
}

// Trigger an Event.COMPLETE event, notifying the Flex client
response.sendError(200,'Done');
}
}


This is capable of uploading multiple files at once and can be tested using a view gsp:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="main" />
<title>Test Upload</title>
</head>
<body>
<div class="body">
<h1>Upload File</h1>
<g:form method="post" action="upload" enctype="multipart/form-data">
<input type="file" name="file_1"/><br/>
<input type="file" name="file_2"/><br/>
<input type="file" name="file_3"/><br/>
<input type="file" name="file_4"/><br/>
<input type="file" name="file_5"/><br/>
<input type="submit"/>
</g:form>
</div>
</body>
</html>


The Flex client has been pared down to 3 main functions, adding files to the upload list, removing them from the list and finally uploading the files to the server.

First the imports, global variables and initializer:

import mx.controls.*;
import mx.managers.*;
import mx.events.*;
import flash.events.*;
import flash.net.*;

// Convention: Underscore to differentiate global and local variables
private var _refAddFiles:FileReferenceList;
private var _refUploadFile:FileReference;
private var _arrUploadFiles:Array;
private var _numCurrentUpload:Number;

private function initApp():void {
_refAddFiles = new FileReferenceList();
_refAddFiles.addEventListener(Event.SELECT, onSelectFile);
_arrUploadFiles = new Array();
_numCurrentUpload = 0;
}


The add-files function opens the browse window and enable the selection of one or more files to add to the upload list:

// Called to add file(s) for upload
// The fileList property is populated anew each time browse() is called on that FileReferenceList object.
private function addFiles():void {
_refAddFiles.browse();
}

// Called when file(s) are selected
private function onSelectFile(event:Event):void {
// Add files to _arrUploadFiles
if (_refAddFiles.fileList.length >= 1) {
for (var k:Number = 0; k < _refAddFiles.fileList.length; k++) {
_arrUploadFiles.push({
name:_refAddFiles.fileList[k].name,
size:formatFileSize(_refAddFiles.fileList[k].size),
file:_refAddFiles.fileList[k]});
}
listFiles.dataProvider = _arrUploadFiles;
listFiles.selectedIndex = _arrUploadFiles.length - 1;
}
}


The remove-files function:

private function removeFiles():void {
var arrSelected:Array = listFiles.selectedIndices;
if (arrSelected.length >= 1) {
// Null selected files
for (var i:Number = 0; i < arrSelected.length; i++) {
_arrUploadFiles[Number(arrSelected[i])] = null;
}

// Remove the null entries
for (var j:Number = 0; j < _arrUploadFiles.length; j++) {
if (_arrUploadFiles[j] == null) {
_arrUploadFiles.splice(j, 1);
j--;
}
}
listFiles.dataProvider = _arrUploadFiles;
listFiles.selectedIndex = 0;
}
}


The upload function is where the point of integration with Grails is, in the request calling up the controller. Although the Grails backend can handle multiple files, Flex can only initiate the upload one file at a time. The event listener and the return response in the Grails controller allow the upload function to loop:

private function uploadFiles():void {
if (_arrUploadFiles.length > 0) {
listFiles.selectedIndex = _numCurrentUpload;

var request:URLRequest = new URLRequest();
request.data = sendVars;
request.url = "http://localhost:8080/App/file/upload";
request.method = URLRequestMethod.POST;
_refUploadFile = new FileReference();
_refUploadFile = listFiles.selectedItem.file;
_refUploadFile.addEventListener(Event.COMPLETE, onUploadComplete);
_refUploadFile.upload(request, "file", false);
}
}

private function onUploadComplete(event:Event):void {
_numCurrentUpload++;
if (_numCurrentUpload < _arrUploadFiles.length) {
uploadFiles();
} else {
_numCurrentUpload = 0;
_arrUploadFiles = new Array();
listFiles.dataProvider = _arrUploadFiles;
listFiles.selectedIndex = 0;
Alert.show("Files have been uploaded", "Upload Complete");
}
}


And lastly the UI:

<mx:Panel title="Files Upload" width="100%" height="100%">
<mx:DataGrid id="listFiles" allowMultipleSelection="true" verticalScrollPolicy="on"
draggableColumns="false" resizableColumns="false" sortableColumns="false" width="100%" height="100%">
<mx:columns>
<mx:DataGridColumn headerText="File" dataField="name" wordWrap="true"/>
</mx:columns>
</mx:DataGrid>

<mx:ControlBar>
<mx:Button label="Add" click="addFiles()"/>
<mx:Button label="Remove" click="removeFiles()"/>
<mx:Button label="Upload" click="uploadFiles()"/>
</mx:ControlBar>
</mx:Panel>


The Flex mxml/swf files must be moved to the web-app folder of the Grails project and the embedded Jetty activated by 'grails run-app' in order to run this application.

The files will be uploaded to the project directory and thus are not accessible by the webapp. The line to amend is in the Grails controller:

File destination = new File("web-app/" + file.getOriginalFilename())

Note this only works while the project is in development mode. For war files, the base directory would depend on where the server is started, usually in the server home directory. So for a standalone Jetty server, the path to add would be "webapps/App/".

Using the war file in a Tomcat server has been problematic. Only one file can be successfully uploaded to the server, there is no looping.

Links:
Coding Cowboys: Flex upload component

Flex Client:
http://livedocs.adobe.com/flex/3/langref/flash/net/URLRequest.html
http://livedocs.adobe.com/flex/3/langref/flash/net/FileReference.html
http://livedocs.adobe.com/flex/3/langref/flash/net/FileReferenceList.html
http://livedocs.adobe.com/flex/3/html/help.html?content=17_Networking_and_communications_7.html

Grails Backend:
http://www.grails.org/Controllers+-+File+Uploads
http://docs.codehaus.org/display/GRAILS/File+Upload

2 comments:

Unknown said...

hi all,

i m very new to grails. so i am writing a small code for file uploading. i had seen the coding
Flex File Upload using Grails Backend but one thing i want to ask where to put the three main functions ???

Thanks

OggieOne said...

I would really like to get your sample to work. As far as I can tell it is missing 2 things (I think).

1. Error: Call to a possibly undefined method formatFileSize

2.request.data = sendVars;
Where did this variable sendVars come from?

Thanks in advance!