This came up on the PhoneGap Forums today so I thought I'd take a quick look at how PhoneGap handles file uploads. Turns out there is really nice support for it built-in, but you can quickly run into an issue with ColdFusion if you don't know one little tip.
My demo application will make use of PhoneGap's FileTransfer object. What's nice is that the PhoneGap team includes a full demo that makes use of your device's photo library. I decided I'd use this demo to post a file to ColdFusion and perform a few quick image manipulations to it. Let's begin with the PhoneGap portion of the code. My HTML is rather simple. I've got a button and some elements that will end up storing results later on.
<!DOCTYPE HTML>
<html>
<head>
<meta name="viewport" content="width=320; user-scalable=no" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Image Upload Example</title>
<link rel="stylesheet" href="master.css" type="text/css" media="screen" title="no title" charset="utf-8">
<script type="text/javascript" charset="utf-8" src="phonegap-1.1.0.js"></script>
<script type="text/javascript" charset="utf-8" src="xui-2.3.0.js"></script>
<script type="text/javascript" charset="utf-8" src="main.js"></script>
</head> <body onload="init();"> <button id="picSelect">Select Picture</button> <div id="status"></div> <img id="resultpic"> </body>
</html>
As you can guess, the main logic is in main.js. Let's take a look over there.
function init() {
document.addEventListener("deviceready", deviceReady, true);
} function errorHandler(e) {
/*
FileTransferError.FILE_NOT_FOUND_ERR = 1;
FileTransferError.INVALID_URL_ERR = 2;
FileTransferError.CONNECTION_ERR = 3;
*/
alert("Error: "+JSON.stringify(e));
} function picDone(loc) {
x$("#status").html("after","About to upload your picture..."); var options = new FileUploadOptions();
options.fileKey="file";
options.fileName=loc.substr(loc.lastIndexOf('/')+1);
options.mimeType="image/jpeg";
//Thank you Steve Rittler! http://www.countermarch.com/blog/index.cfm/2011/10/27/PhoneGap-FileTransfer-and-ColdFusion
options.chunkedMode=false; var ft = new FileTransfer();
ft.upload(loc, "http://192.168.1.105/test3a.cfm", fileUploaded, errorHandler, options);
} function fileUploaded(r) {
x$("#status").html("And we're done!");
x$("#resultpic").attr("src", r.response);
} function deviceReady() { x$("#picSelect").touchstart(function(e) {
navigator.camera.getPicture(picDone,errorHandler,{sourceType:Camera.PictureSourceType.PHOTOLIBRARY, destinationType:Camera.DestinationType.FILE_URI,quality:50});
}); }
Let's walk through this, starting with the deviceReady function. That's run because I added a listener to it in my init function and is a way to ensure I can do "cool device stuff" with the PhoneGap APIs. In case you're curious about the x$ stuff - that's just me playing with xui.js, a replacement for jQuery. I'm not sure how I feel about it yet - ask me next week.
Any way, you can see where we bind to the button element's touch event. When run, we ask the device to get a picture. PhoneGap allows you to go the camera or to the storage for the picture. In this case I went to my storage. Once the picture is taken, we then begin the file upload process. This is in the function picDone. The code here is pretty much ripped right from the PhoneGap docs, with one crucial difference. Notice the call out to a blog post by Steve Rittler. Apparently the upload is using chunked form data. ColdFusion can't handle this. For the life of me though I thought it was an Apache issue. I got a 411 error in Apache, but nothing in ColdFusion. I'm still not convinced it is a ColdFusion, but at the end of the day, Steve's change worked fine. By the way, "fileKey" is simply the name of the form field. You will need to remember this when we get over to the server side.
Finally, our file upload handler fileUploaded() assumes we are getting a URL back. It then simply takes that URL and assigns it to the image. Here's a few screen shots. First, the application as it begins:
Next - the image picker....
and finally, the result:
The server side code is rather trivial:
<cfif structKeyExists(form, "file")>
<cfset destination = getTempDirectory()> <cffile action="upload" filefield="file" destination="#destination#" nameconflict="makeunique" result="result"> <cfif result.fileWasSaved>
<cfset theFile = result.serverDirectory & "/" & result.serverFile>
<cfif isImageFile(theFile)>
<!--- copy to web root with new name --->
<cfset newName = expandPath("./") & createUUID() & ".jpg">
<cfset fileMove(theFile, newName)>
<!--- resize to a thumbnail and grayscale for the hell of it --->
<cfset img = imageRead(newName)>
<cfset imageScaleToFit(img, 200,200)>
<cfset imageGrayScale(img)>
<cfset imageWrite(img)>
<cfoutput>http://192.168.1.105/#getFileFromPath(newName)#</cfoutput>
<cfelse>
<cfset fileDelete(theFile)>
</cfif> </cfif> </cfif>
<cfsetting enablecfoutputonly="true">
You can see I handle the file upload, do some basic checking, and if it is an image, I scale the size and gray scale the color. I then simply output the URL. I could have written this as a CFC of course and normally would. Outside of the darn chunked error, this is a rather simple process. I'm not sure why this chunked option isn't documented (I posted as such to the forums), but now that I'm past it, I'm pretty pleased with how easy PhoneGap makes this.