Reputation: 2733
I'm creating a file upload component that I can <cfinclude>
onto any of my CFM pages which allows for both the standard file selection and Drag & Drop functionality.
When choosing a file and submitting the form, there is no problem since my ColdFusion code relies on the Form scope in order to retrieve the file.
In order to send files using the Drag & Drop feature, I'm using jQuery to send a request through Ajax sending the FormData
based on the current form.
$form.on('submit', function(e) {
e.preventDefault();
// Get FormData based on form and append the dragged and dropped files
var ajaxData = new FormData($form.get(0));
if (droppedFiles) {
$.each(droppedFiles, function(i, file) {
ajaxData.append($('input[type=file]').attr('name'), file);
});
}
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
cache: false,
contentType:false,
processData: false,
success: function(data) {
if (data.SUCCESS) uploadSuccessful($form);
else
{
console.error(data.ERROR);
}
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('Error: File upload unsuccessful ' + errorThrown);
},
data: ajaxData,
dataType: 'json',
dataFilter: function(data, type){
return data.substring(2, data.length);
}
});
});
The HTML:
<form class="file-upload-container" method="post" action="upload.cfm" enctype="multipart/form-data">
<label class="file-upload-input">
<i class="fa fa-upload" aria-hidden="true" style="font-size:5em;"></i><br/><br/>
<input type="file" name="attachment" id="attachment" />
</label>
<div class="file-upload-controls">
<input type="button" id="cancelUpload" value="Cancel" class="btn btn-green" />
<input type="submit" id="upload" value="Upload" class="btn btn-green" />
</div>
</form>
The action
of the <form>
is to post to my upload.cfm page.
I first verify if the form posted had a element with the name "attachment":
<cfif structKeyExists(form, "attachment")>
<!--- This always passes since I'm posting the form with the submit button --->
</cfif>
I then try to retrieve the filename so I can compare against the accepted file types, upload the file, rename the file, and insert an entry into my database. Where I am experiencing my issue is when I'm trying to get the filename from the posted FormData
object (or even the entire File's contents..).
<cffunction name="GetTheFileName" access="public" returntype="string" output="false" >
<cfargument name="fieldName" required="true" type="string" hint="Name of the Form field" />
<cfset var tmpPartsArray = Form.getPartsArray() />
<cfif IsDefined("tmpPartsArray")>
<cfloop array="#tmpPartsArray#" index="local.tmpPart">
<cfif local.tmpPart.isFile() AND local.tmpPart.getName() EQ arguments.fieldName>
<cfreturn LCase(local.tmpPart.getFileName()) />
</cfif>
</cfloop>
</cfif>
<cfreturn "" />
</cffunction>
The line Form.getPartsArray()
returns an array, however the values within the array are empty. (e.g., FilePath:'',FileName:'')
This leads me to believe that FormData
doesn't act in the same way as an actual form that is posted, regardless if ajax is posting the FormData
as multipart/form-data.
What am I missing in order to read the FormData
object on the ColdFusion side in order to retrieve the FileName.
How can the Form scope effectively utilize posted FormData
as if it were an actual <form>
being posted.
This source indicates that I can read the file using Java's FileOutputStream. (Not an ideal solution since I also allow for the typical "Choose a file" which already utilizes the Form scope)
Upvotes: 5
Views: 1548
Reputation: 2733
I have found a solution, albeit not the solution I was initially hoping for.
From my observations, ColdFusion does not treat the FormData
object in the same way as an actual form post. This is clear when the Form scope doesn't contain the form data.
Instead I had to handle data posted with the Drag & Drop functionality in a different manner.
Following the suggested method for uploading chunks of files (source), I requested the upload.cfm
page through ajax where the Content-Type matched the MimeType of the file to be uploaded, and the actual upload used the java.io.FileOutputStream
class to write the file to disk.
JavaScript + AJAX:
var droppedFiles; // Assuming droppedFiles was populated during the 'drop' event
// Get the Form and FormData (although FormData won't be used when files are Dragged & Dropped)
var $form = $('.file-upload-container');
var ajaxData = new FormData($(form).get(0));
var dragged = false;
var filesToBeUploaded = document.getElementById("attachment");
var file = filesToBeUploaded.files[0];
if (droppedFiles){file = droppedFiles;dragged=true;}
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
cache: false,
contentType: dragged ? file.type : false,
processData: false,
success: function(data) {
if (data.SUCCESS) { // Do stuff }
else
{
// Do stuff
console.error(data.ERROR);
}
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('Error: File upload unsuccessful ' + errorThrown);
},
data: dragged ? file : ajaxData,
dataType: 'json',
dataFilter: function(data, type){
return data.substring(2, data.length);
},
headers: { 'X_FILE_NAME': dragged ? file.name : "", 'X_CONTENT_TYPE': dragged ? file.type : "" }
});
ColdFusion:
<cfset bUseFormData = false />
<cfset headerData = getHTTPRequestData().headers>
<cfif structKeyExists(headerData, "X_FILE_NAME") AND ("#headerData.X_FILE_NAME#" NEQ "")>
<cfset bUseFormData = true />
</cfif>
<cfif structKeyExists(form, "attachment") OR bUseFormData>
<cfif bUseFormData>
<cfset content = getHTTPRequestData().content>
<cfset filePath = "#Application.AdminUploadPath#\" & "#headerData.X_CONTENT_TYPE#">
<cfset fos = createObject("java", "java.io.FileOutputStream").init(filePath, true)>
<cfset fos.write(content)>
<cfset fos.close()>
<cfelse>
<cffile action="UPLOAD" filefield="form.attachment" destination="#Application.AdminUploadPath#">
</cfif>
</cfif>
For simplicity's sake, I've excluded code validating file extension, MimeType, and error handling.
Upvotes: 1