Salesforce – Drag and Drop File Uploader Component with Progress Bar – HTML5 and Pure Javascript Based

HTML5 Drag And Drop File
HTML5 based Drag And Drop File

You may find many ways to upload attachments in Salesforce using visualforce however most of them uses some Javascript libraries (means either you need to depend on static resources or add CDN in remote site settings) or they do not have progress bar or they are not drag and drop. I thought to create one simple Visualforce component which can show progress bar and have drag and drop capabilities.

Initially i gave though to use visualforce remoting however challenge was how to show progress bar and same challenge was with custom controller or extension also. So, i decided to create one simple Apex REST API to upload attachment.

@RestResource(urlMapping='/DragAndDrop/v1/*')
global with sharing class DragAndDropRESTAPI
{
    @HttpPost
    global static String attachDoc(){
        RestRequest req = RestContext.request;
        RestResponse res = Restcontext.response;

        String fName = req.params.get('FileName');
        String parId = req.params.get('parId');
        Blob postContent = req.requestBody; 

        Attachment a = new Attachment (ParentId = parId,
                                       Body = postContent,
                                       Name = fName);

        insert a;
        return a.Id;
   }
}

After this class, I created below Visualforce component which needs only two parameter – Parent record id where attachment needs to be loaded and height of component.

<apex:component >
         <apex:attribute name="parentId" type="String" description="Parent record where attachment would be attached"/>
         <apex:attribute name="DragDropHeight" type="String" description="Height in Pixel for Drag and Drop Section"/>
    <style>
        #holder {
            border: 2px dashed #ccc;
            width: 98%;
            height:95%;
            background-color:#ccc;
            -webkit-border-radius: 8px 8px 8px 8px;
            border-radius: 8px 8px 8px 8px;
            text-align: center;
            }
        #holder span, #uploadCompleted span {
            position: relative;
            top: 30%;
            transform: translateY(-50%);
            text-shadow: 2px 2px 2px #525252;
            font-size:3em;
            color:#A3A3A3;
            }
        #holder.hover { border: 2px dashed #636363; }

        #uploadStatus span{
            text-shadow: 2px 2px 2px #525252;
            font-size:1em;
             }

        #holder p { margin: 10px; font-size: 14px; }
        progress { width: 100%;  height:2em;  }
        progress:after { content: '%'; }
        .fail { background: #c00; padding: 2px; color: #fff; }
        .hidden { display: none;}
        .dragDropComponentSize{ height:{!DragDropHeight} ; overflow-y:auto; }
        body{width:98%;font-family: Helvetica,"Helvetica Neue",Arial,sans-serif;}
    </style>

<article class="dragDropComponentSize" id="dndContainer">
    <div id="holder" >
        <span id="holder_txt1"> Drag and Drop file here </span>
         <span  id="holder_txt2" class="hidden"> Upload </span>
    </div>
    <p id="upload" class="hidden"><label>Drag &amp; drop not supported by your browser, but you can still upload via this input field:<br /><input type="file" /></label></p>
    <p id="filereader">File API &amp; FileReader API not supported</p>
    <p id="formdata">XHR2's FormData is not supported</p>
    <p id="progress">XHR2's upload progress isn't supported</p>
    <p id="uploadStatus" class="hidden"><span>Upload progress:</span> <progress id="uploadprogress" min="0" max="100" value="0">0</progress></p>
</article>
<script>
    var holder = document.getElementById('holder');
    var holder_txt1 = document.getElementById('holder_txt1');
    var holder_txt2 = document.getElementById('holder_txt2');
    var uploadStatus = document.getElementById('uploadStatus'); 

    var sfdcHostName =window.location.host.split('.')[1]; 

    tests = {
      filereader: typeof FileReader != 'undefined',
      dnd: 'draggable' in document.createElement('span'),
      formdata: !!window.FormData,
      progress: "upload" in new XMLHttpRequest
    },
    support = {
      filereader: document.getElementById('filereader'),
      formdata: document.getElementById('formdata'),
      progress: document.getElementById('progress')
    },
    progress = document.getElementById('uploadprogress'),
    fileupload = document.getElementById('upload');

"filereader formdata progress".split(' ').forEach(function (api) {
  if (tests[api] === false) {
    support[api].className = 'fail';
  } else {
    support[api].className = 'hidden';
  }
});

function textBeforeDrag(flag){
    if(flag)
    {
        holder_txt1.className = '';
        holder_txt2.className = 'hidden';
    }else{
        holder_txt1.className = 'hidden';
        holder_txt2.className = '';
    }
}

function resetAll()
{
    holder.className = holder_txt1.className = '';
    holder_txt2.className = uploadStatus.className = 'hidden';
}

function readfiles(files) { 

    var formData = tests.formdata ? new FormData() : null;

      //Not sure why multiple files dropping, so for time being disable multi file functionality
      if(files.length > 1)
      {
          alert('Multi Upload is not supported, please try to upload single file');
          return;
      }

    for (var i = 0; i < files.length; i++) {
        uploadStatus.className = '';
        holder.className = 'hidden';
        // now post a new XHR request
        if (tests.formdata) {
          var xhr = new XMLHttpRequest();

          var sfdcurl = 'https://'+sfdcHostName+'.salesforce.com/services/apexrest/DragAndDrop/ v1?FileName='+encodeURIComponent(files[i].name)+'&cType= '+encodeURIComponent(files[i].type)+ '&parId={!parentId}' ;

          xhr.open('POST','/services/proxy' );

          //xhr.setRequestHeader("Content-type",'multipart/form-data');
          //xhr.setRequestHeader("Content-type",'');
          xhr.setRequestHeader("Authorization","Bearer {!$Api.Session_ID}");
          xhr.setRequestHeader('SalesforceProxy-Endpoint', sfdcurl);
          xhr.setRequestHeader('X-User-Agent', 'DragAndDropAPI v1.0');

          xhr.onload = function() {
            progress.value = progress.innerHTML = 100;
            //resetAll();
          };

          if (tests.progress) {
            xhr.upload.onprogress = function (event) {
              if (event.lengthComputable) {
                var complete = (event.loaded / event.total * 100 | 0);
                progress.value = progress.innerHTML = complete;
              }
            }
          }

          xhr.onreadystatechange=function()
            {
                if (xhr.readyState==4 && xhr.status != 200)
                {
                    if(xhr.responseText)
                        alert(xhr.responseText);
                    else
                        alert('Some error occurred while uploading file');

                     console.log(xhr);
                }
            }

          xhr.send(files[i]);
        }
    } 

    /*
    if("{!isRefresh}".toUpperCase()  == "TRUE")
    {
        location.reload();
    }
    */
}

if (tests.dnd) {
  holder.ondragover = function () {
        this.className = 'hover';
        textBeforeDrag(false);
        return false;
      };
  holder.ondragend = function () {
        this.className = '';
        textBeforeDrag(true);
        return false;
      };
  holder.ondrop = function (e) {
        textBeforeDrag(true);
        this.className = '';

        e.preventDefault();
        readfiles(e.dataTransfer.files);
  }
} else {
        fileupload.className = 'hidden';
        fileupload.querySelector('input').onchange = function () {
        readfiles(this.files);
  };
}

</script>
</apex:component>

And we are now good to use above component anywhere in our salesforce application. For example, I used this component in one Visualforce page and added it as inline Visualforce in my Account Page layout.

<apex:page standardController="Account" docType="html-5.0" >
    <c:DragDrop parentId="{!$CurrentPage.parameters.id}" DragDropHeight="100px" />
</apex:page>

Demo is shown in below youtube video.

Note : Please make sure you are setting up doctype as html5 in your visualforce page.
This code is also available on my github account.

Related posts

56 thoughts on “Salesforce – Drag and Drop File Uploader Component with Progress Bar – HTML5 and Pure Javascript Based”

  1. Hi,

    I tried the above, but i am seeing the below errors under the Drag and Drop Image in the inline vf page.

    File API & FileReader API not supported

    XHR2’s FormData is not supported

    XHR2’s upload progress isn’t supported

    Thanks,
    Bujji

        1. Hi Jitendra,
          Unknown property ‘isRefresh’ error when try to save visualforce component

          if(“{!isRefresh}”.toUpperCase() == “TRUE”)
          {
          location.reload();
          }

          when I remove this code snippet in visulforce compenent it will not shown uploaded file at record without refreshing web page.

          And one more issue I face is progress war not shown % upload in progress war shown only % sign. value should be shown in numeric how much data is uploaded.

          At end this code is not working in Internet Explorer, showing error msg “XHR2s upload progress is not supported”.

  2. Hi I tried the above and works great, thansk you saved me a lot of work I had no idea of how to do it, but not for IE11, so I found a workaround for this Instead of sending the file I chek if it is IE or not. And if it is I use a FormData to send the file

    if ((navigator.userAgent.indexOf(“MSIE”) != -1 ) || (!!document.documentMode == true ))
    var formData = new FormData();
    formData.append(“myfile”, fileVar);
    xhr.send(formData);
    }
    else
    {
    xhr.send(fileVar);
    }

    Hope it helps someone

    1. Thank you. That was pretty great. Took me a minute to get the whole page to refresh and not just the embedded visualforce page. Here’s what I got to work:

      xhr.addEventListener(“load”, transferComplete);

      window.top.location=’/{!parentId}’;
      function transferComplete(evt) {window.top.location.reload(true);}

        1. Hi Jitendra,
          Thanks for the great share been looking for this kind of functionality for long time.

          But I am also facing the same issue as above. Please can you point out where I am going wrong.

          Thanks
          Sameer

        2. The problem of getting this error might be you are trying to use in developer org which has managed package so you just need to add /package-name/DragAndDrop/v1*

          It will work!
          Thanks

          1. Hi Dhariya,

            I’m using test sandbox. Where I ll get the package of DragAndDrop – I am newbie to REST API so, bit confused.

  3. hey
    i tried the code, following error appears after the file has uploaded completely. not able to upload any file, please help

    Error 400 Unable to forward request due to: Invalid uri ‘https://ap2.salesforce.com/services/apexrest/DragAndDrop/ v1?FileName=forcecom_workbook_VF_15.pdf&cType= application%2Fpdf&parId=00128000004P4z6’: escaped absolute path not valid

    HTTP ERROR 400
    Problem accessing /services/proxy. Reason:
    Unable to forward request due to: Invalid uri ‘https://ap2.salesforce.com/services/apexrest/DragAndDrop/ v1?FileName=forcecom_workbook_VF_15.pdf&cType= application%2Fpdf&parId=00128000004P4z6’: escaped absolute path not validPowered by Jetty://

      1. Hey, I made that change but still it’s not working.. I removed space in component. Is there any other place I should change.

        1. Hi Chetan,

          I am aslo getting same error,when i am trying to drag and drop file.did you you resolve that problem.please let me know.

          Thanks,
          Kamendra Singh

  4. I am facing this problem..

    plz solve this….

    Thanks

    Sameer

    URL No Longer Exists
    You have attempted to reach a URL that no longer exists on salesforce.com.

    You may have reached this page after clicking on a direct link into the application. This direct link might be:

    • A bookmark to a particular page, such as a report or view

    • A link to a particular page in the Custom Links section of your Home Tab, or a Custom Link

    • A link to a particular page in your email templates

    If you reached this page through a bookmark, you are probably trying to
    access something that has moved. Please update your bookmark.

    If you reached this page through any of the other direct links
    listed above, please notify your administrator to update the link.

    If you reached this page through a link on our site, please report the broken link directly to our Support Team
    and we will fix it promptly. Please indicate the page you were on when
    you clicked the link as well as any other related information. We
    apologize for the inconvenience.

    Thank you again for your patience and assistance. And thanks for using salesforce.com!

  5. Hi Jitendra,

    I tried many that code but it’s not working,getting error.Please help me for same

    Thanks,
    Kamendra Singh

  6. Wanted to give back something 🙂

    1. For all those getting “Error 400 Unable to forward request due to: Invalid uri” error, please remove TWO spaces from the below statement in the code, one after DragAndDrop/ and second in ‘&cType= ‘, like so: var sfdcurl = ‘https://’+sfdcHostName+’.salesforce.com/services/apexrest/DragAndDrop/v1?FileName=’+encodeURIComponent(files[i].name)+’&cType=’+encodeURIComponent(files[i].type)+ ‘&parId={!parentId}’ ;

    2. I noticed that this code doesn’t refresh the page after an upload, so the user can’t see what they attached right away. Here is the snippet that you could add to the component that auto-refreshes the page after a successful transfer:


    xhr.setRequestHeader(“Authorization”,”Bearer {!$Api.Session_ID}”);
    xhr.setRequestHeader(‘SalesforceProxy-Endpoint’, sfdcurl);
    xhr.setRequestHeader(‘X-User-Agent’, ‘DragAndDropAPI v1.0’);

    xhr.addEventListener(“load”, transferComplete);
    xhr.addEventListener(“error”, transferFailed);
    xhr.addEventListener(“abort”, transferCanceled);

    function transferComplete(evt) {
    console.log(“————————–> The transfer is complete.”);
    window.location.reload();
    }

    function transferFailed(evt) {
    console.log(“————————–> An error occurred while transferring the file.”);
    }

    function transferCanceled(evt) {
    console.log(“————————–> The transfer has been canceled by the user.”);
    }

    3. Test class. See below:

    @isTest
    private class DragAndDropRESTAPI_Test{

    static testMethod void drag_drop_attachment_tester(){

    Account acc = new Account(Name = ‘TEST ACCOUNT’);
    insert acc;

    RestRequest req = new RestRequest();
    RestResponse res = new RestResponse();

    req.requestURI = ‘/DragAndDrop/v1/*’;
    req.addParameter(‘FileName’, ‘TestFileName’);
    req.addParameter(‘parId’, acc.Id);
    req.requestBody = Blob.valueOf(‘TEST CONTENT’);

    req.httpMethod = ‘GET’;
    RestContext.request = req;
    RestContext.response = res;

    Id newly_created_attachment_id = DragAndDropRESTAPI.attachDoc();
    }

    }

    1. Wow. Thank you Jitendra and Bee Kay. That was pretty great. Took me a minute to get the whole page to refresh and not just the embedded visualforce page. Here’s what I got to work:

      xhr.addEventListener(“load”, transferComplete);

      window.top.location=’/{!parentId}’;
      function transferComplete(evt) {window.top.location.reload(true);}

  7. Fails in Sites when a VF page is setup so there is no login required.

    Error message “INVALID_SESSION_IDSession expired or invalid”

  8. Thank you for posting this useful Drag n’ Drop solution.

    I successfully implemented and tweaked this in my Sandbox environment, and having difficulties pushing this to Production.
    Perhaps you can share the Tests you created for this please?

    Thank you.

  9. Thanks Jitranda for the amazing tutorial! I am wondering if anybody could comment on this error: Stream Closed. I get this error every time I drop an attachment in the box. Thank you in advance for taking the time to read it.

    1. I found the solution to the error.
      var sfdcHostName =window.location.host.split(‘.’)[1]; was not parsing the URL properly.

  10. I am facing CORS issue when I am trying to use this code from a community portal because of the REST API call. is there any workaround?

  11. I’m having an issue implementing this on a Site with a Guest User Profile, it works great as a logged in user.
    I confirmed the Guest User has premissions on the parent object. Any suggestions?

  12. Hello Jitendra,

    The code is working for normal salesforce users. I want to use this for partner users but it does not work.

    I guess because of iFrame the event.datatransfer.files is probably not getting the value but thats my guess. I tried with ‘event.originalEvent.datatransfer…..’ but it didn’t work.

    Any pointers you would like to suggest?

    Thanks,
    Hrushikesh

  13. Hi jitendra after removing the space also i a
    will getting the error like “couldn’t find a.match for URL/DragAndDrop/V1”.

    Can u please help me in this..

  14. Hi , we are trying to use this code , but we get this error in IE11

    XHR2’s upload progress isn’t supported

    Let us know what is the fix for this.

  15. Im using Google Chrome and Im Sys Admin and I get these errors

    File API & FileReader API not supported
    XHR2’s FormData is not supported
    XHR2’s upload progress isn’t supported

    Im assuming this doesnt work in all browsers?

  16. Hi Jitendra,
    1.Which is supporting for only single file upload. Is there any option for multi-files upload ?
    2 Is there any option,i.e.,adding the description during file uploading like Bank Statement,Document etc..?

  17. Hi Jitendra,
    1.Which is supporting for only single file upload. Is there any option for multi-files upload ?
    2 Is there any option,i.e.,adding the description during file uploading like Bank Statement,Document etc..?

  18. Hello, If anyone facing issue for ‘Session expired or invalid’ then replace ‘var sfdcurl = ‘https://’+sfdcHostName+’.salesforce.com/services/apexrest/DragAndDrop/ v1?FileName=’+encodeURIComponent(files[i].name)+’&cType= ‘+encodeURIComponent(files[i].type)+ ‘&parId={!parentId}’ ;’ with ‘var sfdcurl = ‘https://’+sfdcHostName+’.salesforce.com/services/apexrest/DragAndDrop/v1?FileName=’+encodeURIComponent(files[i].name)+’&cType=’+encodeURIComponent(files[i].type)+ ‘&parId={!parentId}’ ;’. URI must not contain space.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.