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.
Archived Comments
I looked into this a while back, and the other way its to save the image binary that you get back from the camera into a text field and submit that to the server, some toBase64 functions later, and you are done. although I do like the fileUpload functionality!
The issue with the Base64 string though is that it can be huge. I've seen PG apps crawl to a halt when using Base64. You can ask for lower quality/size pictures of course.
Ray,
Get rid of the beard. Really.
Eh? You mean in the one above? That's an old picture. The one you see here in comments is more recent. And my wife likes it. She trumps all comments. ;)
Hi Ray,
Have you experience multiple file upload? want to upload video,audio,image in one request..http multipart...or any other alternatives?
thx
As far as I know, you can't do it using this API.
hi Ray,
ok..thought that..mhhh...what are the alternatives..REST ?
I'd do it in N async calls. Using jQuery Deferreds, it should be a bit easier to manage.
Hi Ray,
thx for the hint with deferred, - this means using ft.upload in combination with deferred?! I was also thinking about kind of pack all files together and POST it in one request ;) ?! any creative ideas ;)
In theory, stress, in theory, you could make a zip using JavaScript and then post that.
http://gildas-lormeau.githu...
ok thx.
Thanks Ray, This post saved me enough time so I could run out and pick up a bottle of Blanche De Chambly!
I keep thinking as I mature, I will eventually like wine. Still hasn't happened. ;)
Ray - I have used your site for a plethora of coding solutions. Thanks! ...Anyway, I am using PhoneGap 2.1 and have my emulator posting to a .cfm file but I get an http_status 500 error -- yet, when I change it to a .php script (heaven forbid I use php) it works fine. My CFM and PHP files are both blank! So why would CFM not work, and PHP work? Actually, why does CFM return a 500 and how do I fix it? I'm running Apache 1.3 CF8 PHP5 -- Thanks in advance!
Did you include the link that set chunkedmode?
Yes.
I'm using the 'full example' code provided by PhoneGap (the latest verion). I have been google-ing around the past few days and have tested a variety of settings including:
options.chunkedMode = false;
I even tried
ft.upload(imageURI, "[MY_CFM_URL]", win, fail, options, true);
I have edited my Config.xml and set
<access origin="[MYURL]"/>
<access origin="*"/>
I literally changed my 'uploadtest.cfm' to 'uploadtest.php' and by doing that I don't receive the http_status 500 FileNotFoundException error.
Yes, both files exist on my server, and I can access them in the emulator's browser just fine.
You said 500 in your first comment, then 500 File Not Found in your second. A File Not Found error is 404. Are you sure it isn't that? What do the CF logs say? The logs probably have a more verbose error.
err i meant the emulator logs showed http_status 500 along w/ the FileNotFoundException error ... anyway, I'm a dummy! Usually I can see the errors in the browser and didn't even think to look at the logs. When I did I saw an error on some line, and realized I had some underlying Application code going on for my site. Moved my script into it's own folder with an empty Application.cfm and voila, it works! Thanks for the slap in the face to remind me to actually look at the logs - that's why they're there. Thanks for taking the time to help me out. I'll get you back
Just glad ya got it.
Raymond,
Thank you for all of your phonegap posts. You've been a huge help to me. I have a (possibly stupid) question about the file upload example. Since you aren't using a CFFUNCTION to process the file when it is uploaded, how would I send back a confirmation to my server that the file upload when alright? I am kind of new to this kind of development, so I am sorry if the answer is obvious.
Thank you again.
I did send a response. Consider this part:
<cfoutput>http://192.168.1.105/#getFileFromPath(newName)#</cfoutput>
That response is returned to the PhoneGap app and used here:
x$("#resultpic").attr("src", r.response);
Doh! Ok. Got it. Thanks again.
Ray, you are such an inspiration..your work is priceless and im so glad i can talk to you through this blog
Wow, thank you. :)
Hi Ray, Do you know of any way I can resize the image before I upload. It only need to be 300 pixels wide and any height. I found this link http://stackoverflow.com/qu... and assume the naswer is buried in there somewhere.
If this is a *new* picture, you can specify a targetWidth/Height before uploading. If this is an existing picture, then you would need to use what is described in the SO link. Is it not making sense to you?
Its an existing picture and no it does not quite make sense. There appears to be two methods discussed in the link - manipulating the file in base 64 format or using canvas. Neither of which I am that familiar with. Any chance of 'whipping up' a quick example? Thanks Terry
Sure, but it may take a little while. I'm on the road this week.
I can wait - thanks a lot
Terry, sorry this has taken me so long. I still want to work an example that shows resizing an image, but I wanted to be sure you knew that if you are using a *new* image, do not forget that the PhoneGap Camera API lets you specify a target size.
Hi Ray, Thanks yes I am using that. I have been routing around myself and this link seems to be the closest to a solution - http://pastebin.com/edACk56q
As my first attempt at building a phonegap app. Where do I find the *.js files referenced in the code and what is the xui?
1) JS FIles.
phonegap.js comes from the PhoneGap SDK you download.
main.js is the second code sample I believe.
xui.js comes from http://xuijs.com/. xui is a light-weight jQuery clone.
Hi Sir,
I have another issue. i can easily upload photo and video on server. but now i want to upload audio file on server. i want just like photo upload user will select audio file from list and upload that on server.whatever i got from phonegap api and google , is first record audio and upload that, but i don't want to record, just select audio file from list and upload it.
It would be great help if you give any idea.
Well, where does the list of audio files come from? If the file system, then you should look at the docs for the File System stuff.
Yes the audio will come from file system.
But i want the same procedure just like photo and video , user will click on upload button then file system(play list ) will open ,user will select audio file from there and upload.
I am very confuse as photo and video uploading is very easy, can't this also be easy for audio.I don't have very much idea about file system apis.
You can use the Capture API (http://docs.phonegap.com/en... to capture new audio, but you want existing audio - and that has to come from a file. You can't get the same UI you get with camera as that just isn't an option.
I'd recommend spending some time reading the docs on the file system stuff. It isn't friendly per se, but you need to learn it.
Thanks for you reply.. i know about the capture api and it works great. but my need is different like upload existing audio.i'll surely read the file system stuff.
If you get some time please have a look and give me clue regarding this .
I did give you a clue. :) Read up on the file system stuff. You need to at least be prepared to read a directory and display the results to screen, which if you check the docs, should be doable.
I'm probably doing something heinous, but I can't get the "select picture" button to fire anything, not even an alert. I know all my js is loading properly and no js errors coming through Firebug when I test locally. Any idea?
Use remote debugging to see if an error is being thrown.
Need to upload file with chunked data. Do i need to change in server side code?
You would, yes.