8

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?

tshepang
  • 12,111
  • 21
  • 91
  • 136
Michael
  • 2,546
  • 2
  • 20
  • 26
  • 2
    Have you tried using ``? It's CF9 only, and I haven't tested it myself, but that may do the trick. In CF8, I'm just getting the one Images field. – Dan Short Sep 07 '11 at 18:21
  • Damn, that's exactly what I need, but I'm stuck with CF8 at the moment. Any suggestions? – Michael Sep 07 '11 at 18:48
  • Not with an HTML 5 form :/, I use Uploadify for these types of uploaders in CF8, or the `cffileupload` tag in CF9. I'm honestly not sure if the solution I provided would even work... – Dan Short Sep 07 '11 at 18:50
  • After a second look, it seems like uploadAll was created to handle multiple input elements of the same name. I'm really just trying to handle one input element with attribute "multiple". I was hoping it would just create an array of files and I could loop through those individually, kind of how you would loop through multiple text input fields of the same name. – Michael Sep 07 '11 at 19:53
  • I'll have to test it out, because when I used your sample form in CF8 it showed multiple form elements named "images", not a single form element. I'll see if I can make the `uploadall` work in CF9 and get back to you. – Dan Short Sep 07 '11 at 19:56
  • Same here - I ed the form and if I selected 3 images it lists "IMAGES" 3 times in the fieldnames. However, below that, only one "IMAGES" value is listed in the struct and it's an absolute path to one temporary file (i.e. D:\ColdFusion8\runtime\...etc...\neotmp47438.tmp) – Michael Sep 07 '11 at 20:06
  • Well using `uploadall` doesn't work... The first file gets uploaded, and then when the second tries to save I get an error that file overwriting isn't allowed... There may be a way to do this by falling back to JAVA, but that's outside my area of expertise :) – Dan Short Sep 08 '11 at 00:25

4 Answers4

6

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.

Tristan Lee
  • 1,210
  • 10
  • 17
2

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 :).

Dan Short
  • 9,598
  • 2
  • 28
  • 53
1

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.

Sebastian Zartner
  • 18,808
  • 10
  • 90
  • 132
  • Using cffile action="uploadAll" worked for me, CF11: https://trycf.com/gist/5614a9693ef6cd712a151c5d1d350c12/lucee5?theme=monokai (I know you were talking about the regular upload, but ... uploadAll is an option). – SOS Aug 08 '18 at 15:15
  • 1
    Yes, `` works in most cases, though it uploads *all* files of a request. If you have several `` elements in your form, you can't handle them differently, e.g. upload the selected files to different places. I've updated my answer to clarify that. – Sebastian Zartner Aug 08 '18 at 23:06
  • True. You're right, more granular control is needed. – SOS Aug 08 '18 at 23:43
1

Yes it works in CF-2018. Try this for multi-file uploads.

<cfif isdefined("form.submit")> 
    <cffile 
         action="uploadall" 
         destination="#expandpath(".")#" 
         nameconflict="MakeUnique" 
         accept="image/png,image/jpeg,.png,.jpg,.jpeg"                
         strict="true"
         result="fileUploaded"
         allowedextensions=".png,.jpg,.jpeg" 
    >   
    <cfloop array="#fileUploaded#" item="item">
        <br><cfdump var="#filegetmimetype(item.serverdirectory&'/'&item.serverfile)#">
        <br><a href="<cfoutput>#item.serverfile#</cfoutput>"><cfoutput>#item.serverfile#</cfoutput></a>
    </cfloop>                             
</cfif>
              
<h3>Upload multiple files.</h3> 

<cfform method="POST" action="#CGI.SCRIPT_NAME#" enctype="multipart/form-data">
    <cfinput type="file" name="Images" multiple="multiple" accept="image/jpeg, image/gif, image/png, application/zip" /><br />
    <cfinput type="submit" name=" submit" value="submit"> 
</cfform>
Bobmd
  • 125
  • 7