Drag and Drop Multi File Upload with ColdFusion

My cousin Winston Yee (@winston_yee) hit me up the other day for a file uploader that was able to pause mid-upload, assuming the user was interrupted, and then resume where it left off.  I remembered reading something recenctly in @Appliness on my iPad about a JavaScript drag/drop file upload, so I pointed him at that.  As it turns out, that wasn't what he was looking for, but I thought I'd write a quick blog post about it anyway, because I think it's cool.  Sorry to let you down again, Winston ;)

Basically, the AJAX functions ColdFusion gives us out of the box are cool (e.g. cfajaxproxy), but there's so much more you can do with regular old JavaScript on the front end, and then have the CF stuff do the work behind the scenes.  If you've worked on Base Camp before, you know that you can simply drag a file to a designated rectangle on the screen and it uploads immediately.  No funky-looking <input type="file"> tag, no more CSS tricks to hide it, no more submit button.  I like that and here's how to do it:

We need two files.  The first one is uploadForm.cfm and it looks like what you see below.  I'll let the comments in the code explain how it works.



 


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">


 


<html xmlns="http://www.w3.org/1999/xhtml">


 


<!---


Filename:      uploadForm.cfm


Created by:    Kris Korsmo


Organization:  kriskorsmo.com


Purpose:       Dispays a drag/drop file upload formLast Updated:  3/9/2013


--->


 


<head>


   <title>Drag it. Drop it.</title>


 


   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />


 


   <!--- 


         Link to the latest version of jQuery.  Obviously you can point this


         to your local version of jQuery if you prefer.  I like to keep this


         script in the document head, and the rest of the scripts at the 


         bottom of the page.


   --->


 


   <script src="http://code.jquery.com/jquery-latest.min.js"></script>


 


   <style>


      #dropArea {


         background-color: #efefef;


         border: 1px dotted #aeaeae;


         color: #aeaeae; 


         height: 200px; 


         line-height: 200px; 


         text-align: center;


         width: 300px; 


      }


   </style>


 


</head>


 


<body>


 


   <!---


         No form is requred.  Just create a blank div


         and style it so that it looks like a drop zone.


         I called my div dropArea.


   --->


 


   <div id="dropArea">


      Drop your files here


   </div>


 


   <!---


      The following alert messages will be hidden initially


      and depending on the state of the file upload we'll


      hide them and show them.


 


      As a side note, the names I gave the classes work with


      Twitter Bootstrap.  Although I didn't include Bootstrap


      in this demo, it looks really nice with this sort of UI.


   --->


 


   <div class="alert alert-info">


      Hang on!  Your file is uploading.


   </div>


 


   <div class="alert alert-error">


      Hey! Only JPG files are allowed.


   </div>


 


   <div class="alert alert-success">


      Sweet!  Your file was uploaded.


   </div>


 


   <script type="text/javascript">


 


      // hide all the divs with the class 'alert'


      $('.alert').hide();


 


      // create a JavaScript variable called target for our dropArea div


      var target = document.getElementById('dropArea');


 


      // add an event listener for the dragover event on our div


      target.addEventListener("dragover", function(event){


         event.preventDefault();


      }, false);


 


      // add an event listener for the drop event on our div


      // all the events below will happen when the drop event occurs


      target.addEventListener("drop", function(event){


         event.preventDefault();


 


         // set the variable 'files' to the dataTransfer object


         files = event.dataTransfer.files,


 


         // use the dataTranfer object to tell us how many files were


         // dropped onto our div


         numFiles = files.length;


 


         // loop over all the files


         var i=0;


         for (;i < numFiles; i++) {


 


            // we can simulate an old-fashioned form by using the formData object


            var uploadForm = new FormData();


 


            // you can use the dataTransfer object to gather information about the


            // files that were dropped.  That makes it easy to allow only certain


            // types of files to be uploaded. fType is our variable for the file type


            var fType = (files[i].type);


 


            // I'm only going to allow JPEG images to be uploaded. You can choose


            // whatever mime types you want to allow.  A good list of mime types can


            // be found at http://bit.ly/15EROqJ


            if (fType == 'image/jpeg'){


 


               // we'll append information to the form using the formData object


               uploadForm.append("theFile", files[i]);


               uploadForm.append("fileName", files[i].name);


 


               // create an instance of the XMLHttpRequest object (the AJAX magic)


               var xhr = new XMLHttpRequest();


 


               // create an event listener for our XMLHttpRequest


               // when the load event is complete it will fire the


               // transferComplete function


               xhr.addEventListener("load",transferComplete,true);


 


               // add a function called transferComplete that will be fired


               // when the file upload is complete


               function transferComplete(event){


                  // hide the div that says the file is uploading


                  $('.alert-info').hide();


                  // show the div that says the file was successfully uploaded


                  $('.alert-success').fadeIn().delay(2000).fadeOut('slow');


               }


 


               // show the div that says our ile is being uploaded


               $('.alert-info').fadeIn();


 


               // specify the name of the file which will handle the upload


               // (method, file, asynch)


               xhr.open("post", "handleUpload.cfm", true);


 


               // send the form


               xhr.send(uploadForm);


            }


 


            // if the file mime type is not 'image/jpg' we'll show an error


            else {


               $('.alert-error').fadeIn().delay(2000).fadeOut('slow');


            }


         }


      }, false);


 


</script>


 


</body>


 


</html>


 


 

The second file is handleUpload.cfm.  It simply handles the upload on the server side and looks like this:



 


<cffile  action="upload" 


         destination="#expandPath('uploadedFiles/')#" 


         accept="image/jpeg" 


         nameconflict="overwrite"


         filefield="theFile" />


 


That's all there is to it.  Of course there is more you can do with it as well.  You can monitor the progress of the file uploads and return a progress bar, you can FTP, CFMAIL or manipulate the uploaded files and return results, or an endless number of possibilities.

There are a lot of ways to skin a cat - if you have a better way let me know and I'll hold the tail!  

And a working demo is located here

  1. Dale Fraser

    #1 by Dale Fraser - March 9, 2013 at 8:39 PM

    Looks good,

    Needs a % progress for the current uploading files(s)

    And then to list the files received somewhere.

    Good sample however.
  2. Michael Zock

    #2 by Michael Zock - March 10, 2013 at 5:16 AM

    Thanks!

    I usually just use http://www.plupload.com/ for tasks like that, but it's nice to see the bare essentials accompanied by some useful comments.
  3. Jeff

    #3 by Jeff - July 26, 2013 at 2:58 PM

    Thanks for this. It seems very simple. I tried it locally and tried your demo but I get an error "ReferenceError: transferComplete is not defined". You have the function in your code so I'm not sure why the error occurs.
  4. Andre Luz

    #4 by Andre Luz - December 1, 2013 at 2:32 PM

    Not working on Firefox but Great job...
  5. Patrick

    #5 by Patrick - December 18, 2013 at 12:32 AM

    So I am playing with this, works great in Chrome but not in IE 9 or Firefox. Any one else having that issue?
  6. drew17

    #6 by drew17 - February 11, 2014 at 6:19 AM

    This is great!

    I cannot get it to work in Firefox. Has anyone found a fix?

    Thanks!
  7. Dani

    #7 by Dani - May 1, 2014 at 11:51 AM

    Hello people, I made it work in FF by moving the function "transferComplete" above the call to it.
    So Basically you move this call right after the function:

    xhr.addEventListener("load",transferComplete,true);

    I hope it helps.
    Thank you Kris for this tutorial!
  8. Dani

    #8 by Dani - May 2, 2014 at 9:42 AM

    Hello people, if I want to send a variable to change the upload directory, should I do it with the xhr.send?
    Kris, would you please guide me a little bit on this?
    I want to be able to upload to a directory retrieved from the database, based on the customerID, so let's say it would be uploadDir/#custID#.

    Would it be with the xhr.send?

    Thank you!
  9. kris

    #9 by kris - May 2, 2014 at 10:00 AM

    Dani,

    I might not understand your needs 100%, but let's say for example you have a client portal, and when the client is signed in their customer ID is stored in the session. I'd simply change handleUpload.cfm to this:

    <cfset targetDir = expandPath('uploadedFiles/#SESSION.ClientID#') />
    <cffile action="upload"
    destination="#targetDir#"
    accept="image/jpeg"
    nameconflict="overwrite"
    filefield="theFile" />

    I know you said you need to get some information from a query, but I'd handle that in this file as well.
  10. Dani

    #10 by Dani - May 2, 2014 at 10:08 AM

    Thank you so much Kris.
    That could do the work, I think.

    The situation is this: users of the system get a call from a particular client (ClientID) and they need to be able to upload multiple files for that client. So I guess the change would be using this:

    <cfset targetDir = expandPath('uploadedFiles/#getClients.ClientID#') />

    being GetClients my query. My concern is if this will work since it's not a session variable but a variable coming from a query based on the customer who placed the call.

    Thanks again for your time.
  11. Dani

    #11 by Dani - May 2, 2014 at 10:32 AM

    Hi Kris, I have replied previously but I think the answer got lost somewhere in the air.
    ClientID will be coming from a query because is not related to the session. The session is for the user, who get calls from clients. So the idea is that when he opens a client's file, the ID gets saved and used later on as the directory to upload files to.

    I've read just minutes ago that I can convert query results as session variables by naming the query session.getClientID, but not sure if this would work in order to use your example.

    Thank you for your time.
  12. Dani

    #12 by Dani - August 26, 2014 at 8:07 AM

    Hello Kris, is it possible to wrap this in a <a href> tag in order to open a file browser when clicking within the drop area? I have tried to find examples, but all of them will use a hidden input type="file" and can't select multiple files.

    Thank you!
  13. MikeL

    #13 by MikeL - February 23, 2015 at 9:19 AM

    Thanks for posting this, Kris, and for the adjustment for Firefox, Dani. I may still include the common convention of a file field and upload button. But this is a nice option to build on, too.
  14. larde pierre

    #14 by larde pierre - August 31, 2016 at 2:21 PM

    Your example is great, I used it successfully,

    I am asking if there is a simple way to open the window file manager automatically
    or a javascript code to open it , from which we can drop the files ?

    Also, I tried it with many files, when I selected 134 files (example),
    about 25 files have been missed from the 134.
    Any idea ?

    Thanks very much for this anyway.
  15. CeCe

    #15 by CeCe - December 21, 2016 at 7:35 AM

    Hi Kris, thank you so much for posting this, it's really helpful. However I have a question, do you know how I can make connection between <input type="file"> tag(upload multi files) and drag zone ? I want a user not only drag and drop multi files but also can choose multi files to upload. Thank you!!
(will not be published)
Leave this field empty: