Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ColdFusion handling of HTML5 <input type="file" multiple="multiple" />

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?

like image 559
Michael Avatar asked Sep 07 '11 18:09

Michael


3 Answers

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.

like image 110
Tristan Lee Avatar answered Nov 05 '22 06:11

Tristan Lee


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

like image 45
Dan Short Avatar answered Nov 05 '22 07:11

Dan Short


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.

like image 40
Sebastian Zartner Avatar answered Nov 05 '22 06:11

Sebastian Zartner