I am trying to implement an HTML5 input field that lets the user select multiple files to upload. I have the following in my form:
<form method="post" enctype="multipart/form-data" action="index.cfm">
<input type="file" name="Images" id="Images" multiple="multiple" accept="image/jpeg, image/gif, image/png, application/zip" />
...
I am able to select multiple files in the browser, then click upload, but I'm not sure how to handle the form post with ColdFusion. I thought the following would work, but this only uploads the last file I selected:
<cfloop list="#attributes.Images#" index="Image">
<cffile
destination = "#ExpandPath(Trim(request.TempFolder))#"
filefield = "Images"
action = "upload"
nameconflict = "MakeUnique"
result = "UploadedTempFile"
>
<cfoutput>#UploadedTempFile.serverFile#<br /></cfoutput>
</cfloop>
Can someone explain to me how to loop through all the files submitted through my one form field so I can handle the files individually?
After searching a number of blog posts, stumbling this, and reading Adobe's documentation, it appears that the consensus is "multiple" file upload support is not supported with CF10 (unless you're doing the flash forms). The issue being, "uploadall" value for the cffile tag may upload all of the files, but you aren't returned an array of results regarding the files.
Here's a function I threw together that utilized the underlying Java methods and tested in ACF 10.
<cffunction name="getUploadedFiles" access="public" returntype="struct"
hint="Gets the uploaded files, grouped by the field name">
<cfset var local = {
files = {},
types = "text/plain,text/csv,application/msexcel,application/vnd.ms-excel,application/octet-stream",
tempFiles = form.getTempFiles(),
idx = 0} />
<cfscript>
arrayEach(form.getPartsArray(), function (field) {
var local = {fieldName = field.getName(), destinationFile = ""};
// Make sure the field available in the form is also
// available for the temporary files
if (structKeyExists(tempFiles, fieldName)) {
// Create the files of field array if it doesn't exist
if (!structKeyExists(files, fieldName)) {
files[fieldName] = [];
}
// If only 1 file was uploaded, it won't be an array - so make it one
if (!isArray(tempFiles[fieldName])) {
tempFiles[fieldName] = [tempFiles[fieldName]];
}
// Check that the form part is a file and within our list of valid types
if (field.isFile() && listFindNoCase(types, field.getContentType())) {
// Compile details about the upload
arrayAppend(files[fieldName], {
file = tempFiles[fieldName][++idx],
filePart = field,
filename = field.getFileName(),
filepath = field.getFilePath(),
contentType = field.getContentType(),
tempFile = tempFiles[fieldName][idx].getPath()
});
}
}
});
</cfscript>
<cfreturn local.files />
</cffunction>
Following along with the comments, this just loops over all the form parts, finding the files, and creating an array containing some useful file details (and filtering by specific content types per my application requirements).
Then, I created the uploadFile function which takes in fieldName and destinationPath arguments. I get the array of uploaded files based on the field I pass in, loop through the files to ensure the destination filepath does not exists (and make it unique if so), and then write the destination file using the contents of the java.io.File object that is referenced from the temporary upload.
<cffunction name="uploadFile" access="public" returntype="array"
hint="Uploads a file (or multiple files) from the form to the server">
<cfargument name="fieldName" type="string" required="true" />
<cfargument name="destinationPath" type="string" required="true" />
<cfset var local = {files = [], filepaths = [], allFiles = getUploadedFiles()} />
<cfif structKeyExists(local.allFiles, arguments.fieldName)>
<cfset local.files = local.allFiles[arguments.fieldName] />
</cfif>
<cfloop array="#local.files#" index="local.file">
<cfset local.file.destinationFile = arguments.destinationPath & local.file.fileName />
<cfif fileExists(local.file.destinationFile)>
<cfset local.file.destinationFile = listFirst(local.file.destinationFile, ".") & "_#getTickCount()#.csv" />
</cfif>
<cfset fileWrite(local.file.destinationFile, fileRead(local.file.file)) />
<cfset arrayAppend(local.filePaths, local.file.destinationFile) />
</cfloop>
<cfset setActiveFileName(local.filePaths[arrayLen(local.filePaths)]) />
<cfreturn local.filePaths />
</cffunction>
Now I have full control over all the files being uploaded, and can handle the results has needed.
UPDATE: As of ColdFusion 9 this was true. This has been corrected in ColdFusion 10.
So to wrap up our comment conversation:
This simply isn't possible with default ColdFusion behavior. cffile
doesn't support handling multiple file uploads from a single file
element. I think it could potentially be possible to fallback to JAVA to do this, but I wouldn't have a clue how to make that happen.
I would love cffile action="uploadall"
to grab all HTML5 multi-file elements. Will need to file that as an ER for CF10 :).
fileUploadAll()
/<cffile action="uploadAll">
allow you to upload all files, also multiple files of an <input type="file" multiple>
.
Though it always handles all files of a request, i.e. it doesn't distinguish between different file inputs, which you may want to handle differently, e.g. setting different allowed MIME types via the accept
argument or upload the files to different locations for each input.
Therefore I've created an enhancement request on the Adobe ColdFusion and the Lucee issue tracker.
The basic idea is to change the fileUpload()
function and <cffile action="upload">
to automatically return an array if the form field has multiple files, or if this causes compatibility issues, add a new argument multiple
defaulting to false
controlling that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With