Download the public beta of ColdFusion Splendor from here

This post is reproduced from my post at my personal blog ramkulkarni.com.

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. 

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

0 Comments to “CFMobile Example – Taking picture and uploading to ColdFusion server”

Leave a Comment

Leave this field empty: