CFMobile Example – Taking picture and uploading to ColdFusion server

In this post I am going to show you how to create a mobile application using ColdFusion Splendor that can take a picture and upload the picture to CF server. This application uses Camera and File APIs.

The application is very simple – it has two buttons, one to take picture and the other one to set URL where pictures are to be uploaded. You can set URL of the server before taking a picture or after, just before it is to be uploaded. Once the URL is set, it is stored in the localStorage and will be remembered.  There is a messages div where the application displays messages about different operations it is performing.

2014_03_04_image1 2014_03_04_image2 2014_03_04_image3

Here is HTML UI code in the index.cfm –

DOCTYPE html>

<h1>CFMobile Photo Demo App </h1>
This demo app shows how to take picture and upload to CF server using CFMobile features.<br>

<button  type="button" id="takePictBtn">Take Picture & Upload</button><br>
<button type="button" id="setServerURLBtn">Set Server URL</button>
<h2>Messages</h2>
<div id="msgDiv"></div>

Then there is a JavaScript block where I register event handlers for buttons. I also have a utility function to display messages in  the msgDiv –

<script >
	document.getElementById("setServerURLBtn").onclick = function(){
		var serverUrl = "";
		if (typeof localStorage.serverUrl !== "undefined")
			serverUrl = localStorage.serverUrl;
		serverUrl = prompt("Enter Server URL",serverUrl);
		if (serverUrl != null)
			localStorage.serverUrl = serverUrl;
	};

	document.getElementById("takePictBtn").onclick = function(){
		takePictureAndUpload();
	};

	function displayMessage (msg, append)
	{
		var divElm = document.getElementById("msgDiv");
		if (append != undefined && !append)
			divElm.innerHTML = "";
		divElm.innerHTML += msg;
	}
</script>

Event handler for the setServerURLBtn first checks if server url is already stored in the localStorage. If yes, it initializes input field with that url. After user enters the URL, this function sets it to localstorage.serverUrl variable.

Event handler for takePictBtn calls takePictureAndUpload function which we will see in the cfclient block later. Note that I am not assigning takePictureAndUpload directly to the event handler because when that code is executed by the browser, cfclient block is not initialized yet and so takePictureAndUpload function would not be available.

Remaining code in this file is cfclient code. First we enable device APIs (required because we are going to use Camera APIs) in cfclientsettings tag. Then we start cfclient block.

<cfclientsettings enabledeviceapi="true" >
<cfclient>
	<cfscript>
          //TODO: code to take picture and upload to server
         </cfscript>
</cfclient>

We will now add functions in the cfscript block of cfclient. The first function is takePictureAndUpload –

function takePictureAndUpload()
{
	var imageUrl = cfclient.camera.getPicture();

	if (!isDefined("imageUrl") || imageUrl.length == 0)
		return;

	displayMessage("Temporary image - " + imageUrl + "<br><hr>", false);

	//This file is in a temporary file system. Move it to persistent file system
	var newFilePath = copyFileFromTempToPersistentFileSystem(imageUrl);

	//Now upload file to the server
	uploadFileToServer(newFilePath);

	//Delete temporary file
	cfclient.file.remove(imageUrl);
	displayMessage("Deleted temporaty file " + imageUrl + "<br><hr>");
}

cfclient.camera.getPicture() launches device’s camera application. Depending on options passed to this function, it either returns path of the saved picture or picture data in base64 format. In this application I am not passing any options and the API returns path of the picture file.
PhoneGap supports two kinds of file system types – persistent and temporary. Files generated by device APIs are generally stored in the temporary file system. As the name suggest, all files stored in this file system are temporary in nature and many not be available later. So I need to move the picture file from temporary file system to  persistent file system. I will do that in copyFileFromTempToPersistentFileSystem that we will see shortly.

Once I get path of the file (returned by copyFileFromTempToPersistentFileSystem) in persistent file system, I call another function, uploadFileToServer and then delete temporary file.

Here is the code for  copyFileFromTempToPersistentFileSystem function –

function copyFileFromTempToPersistentFileSystem (tempFilePath)
{
	//save existing file system
	var oldFileSystem = cfclient.file.getFileSystem();

	//Get file object from the path
	var tmpFile = cfclient.file.get(tempFilePath);

	//set persistent file system
	var persistentfileSystem = cfclient.file.setFileSystem("persistent");

	var newFilePath = persistentfileSystem.root.fullPath + "/" + tmpFile.name;

	//If file with the same name exists in the persistent file system, then try save
	//it with different name
	var count = 1;
	while (count < 10)
	{
		try
		{
			cfclient.file.get(newFilePath);

			//file already exists. Try different file name
			count++;
			newFilePath = persistentfileSystem.root.fullPath + "/" + replace(tmpFile.name,".","_") + "_" + count + ".jpg";
		}
		catch (any e) {
			//Assume file does not exists. Go ahead and copy from temp location to persistent location.	
			console.log("Exception : " + e.message);
			break;
		}
	}

	//Copy file
	cfclient.file.copy(tempFilePath,newFilePath);
	displayMessage("Image file copied to " + newFilePath + "<br><hr>");

	//Restore old file system
	cfclient.file.setFileSystem(oldFileSystem.name);

	//return new file path
	return newFilePath;
}

And uploadFileToServer function –

function uploadFileToServer(imageFilePath)
{
	if (!structKeyExists(localStorage,"serverUrl"))
	{
		var serverUrl = prompt("Enter Server URL",serverUrl);
		if (serverUrl != null)
			localStorage.serverUrl = serverUrl;
		else
		{
			alert("No file uploaded");
			return;
		}
	}

	displayMessage("uploading file to " + localStorage.serverUrl + "<br><hr>");

	var uploadOptions = {
		fileKey:"uploadedPicture",
		fileName:"uploadedPictured.jpg"
	};

	cfclient.file.upload(imageFilePath, localStorage.serverUrl,
			onFileUploaded, uploadError, uploadOptions
	);

	function onFileUploaded()
	{
		displayMessage("File uploaded successfully");
	}

	function uploadError(err)
	{
		//See error codes at http://docs.phonegap.com/en/3.3.0/cordova_file_file.md.html#FileTransferError
		if (err.code == 3)
			alert("Error uploading file - Connection error");
		else
			alert("Error uploading file. Code - " + err.code);
	}

}

This function first checks if user has already entered URL where files is to be uploaded. If it is not set, then it prompts user to enter the URL. Then it sets form field options in uploadOptions struct.
Image data would be sent in a form field assigned to fileKey field. In this application it is “uploadedPicture”. As per PhoneGap docs fileName field tells  server the name to be used when saving the uploaded file. But I found that this field is not passed to the server, or server does not save file with the name assigned to this field. I have not investigated if the bug is in PhoneGap or cfclient implementation.

cfclient.file.upload function actually sends file data to the server. Third and fourth argument to this function are success and error calbacks respectively. In this example those are onFileUploaded  (success callback) and uploadError (error callback) functions.

That completes the client side code. Now let’s see how the server-side code (to receive uploaded file and save it on the server) could be written (it is in fileUploadHandler.cfm in server folder of this project) –

<!--- Save uploaded file from a mobile application --->
<cfif structKeyExists(form,"uploadedPicture")>
	<cftry>
		<!--- save file in the current folder --->
		<cfset fileInfo = getFileInfo(form.uploadedPicture)>

		<cfset path = getDirectoryFromPath(getTemplatePath()) & "/" & fileInfo.name & ".jpg">
		<cffile action="copy" destination="#path#" source="#form.uploadedPicture#" >
	<cfcatch type="any" name="e">
		<cflog text="#e.message#" >
	</cfcatch>
	</cftry>
</cfif>

Note that the uploaded file date is stored in the form field “uploadedPicture”, as specified in the client side code in uploadFileToServer function. The above code saves picture file in the same folder as the CFML file processing it.

I have provided links to download Android application and Thunder project below. If you download the project, you will see that client side code is in the root folder of the project and server-side code is in a subfolder called ‘server’. There is Application.cfm in the root of the project and another one in the ‘server’ folder. It is important that client and server CFML code have their own Application.cfm.
I have added Application.cfm in the project root so that I could run this application in the Shell app. But since server folder is a child of project folder, it would use Application.cfm in the project root, if it does not have its own Application.cfm. So I included an empty Application.cfm in the server folder too. It would be better to have client and server code in completely independent folders, but in this case I added server folder in the root of client folder to simplify packaging of the project.

You will find all uploaded pictures from this application in CFMobilePhotoApp/server folder on the server.

When you package the application in Thunder, make sure you select only index.cfm in project properties->ColdFusion Mobile Project->Resource Selection tab.

Download project and Android APK for this application.

-Ram Kulkarni

17 Replies to “CFMobile Example – Taking picture and uploading to ColdFusion server”

    1. Idea was to keep a copy so that if connection failed (while uploading) or phone is offline then user can retry later. But I did not implement this to keep the code simple and easy to understand.

  1. Great article. Your sample works great when installed. But when i package your sample and install it on a Samsung S4, the “take picture” button will not launch the camera.
    When it installs it does give access for camera and local storage so that shouldn’t be it. Any suggestions?

    1. I verified it again on the released builds of CFB 3 and CF 11. The camera app is launched when ‘Take Picture & Upload’ button is clicked.
      I know you said access rights for camera and file are given, but can you check again? Go to mobile project properties, PhoneGap tab and make sure under Configuration->Feature you have Camera and File values.

      1. i do have to say that when i try to generate the phonegap build, it always makes me retype my phonegap password many times (i do type it in correctly each time). Also, when the iOS key details are left blank, it always takes a few times of building it until it finally builds the Android .apk and skips the iOS version. Its very troublesome but when it does go through its nice.

        1. I also recently saw this issue (when connecting to PG Build server). We will investigate it. Regarding the issue with iOS certificate, I will try to reproduce that. I always packaged apps with iOS certificate so I may not have seen this issue.

  2. Great article but have you done any further investigation into why the fileName field is not passed to the server when using cfclient.file.upload as per your comment below? This is really proving to be a problem for me. I need to be able to control the name of the file.

    “As per PhoneGap docs fileName field tells server the name to be used when saving the uploaded file. But I found that this field is not passed to the server, or server does not save file with the name assigned to this field. I have not investigated if the bug is in PhoneGap or cfclient implementation.”

    Also, I thought I’d pass along something that took me more than a day to figure out. If you’re calling a remote CFC don’t follow the recommendation in the ColdFusion 11 Lockdown Guide to enable the Server Settings > Settings > Prefix serialized JSON with // in the Coldfusion Administrator. You must leave this option unchecked or else your CFC return results will be “undefined”.

    1. Update: As a workaround to the fileName issue, I ended up passing the file name as a parameter on the serverUrl.

    2. Hi Ken,
      regarding the file name issue you are facing with cfclient file upload, currently the file name is same as the original file name while uploading, fileName field from phonegap is not supported. Not sure if there is already a bug for this, so I would encourage you to raise one.

      -Sandeep
      Adobe ColdFusion team

Leave a Reply to byron jones jersey Cancel reply