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

  • Bujji

    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

    • Kirtesh Jain

      I found that Safari and IE8/IE9 doesn’t support File API/ File Reader API . which browser did you use ?

      • I used chrome and IE 11

        • Krishan Gopal

          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”.

  • juan

    Thanks the code is awesome, but I;m having some issues the responseText comes empty. Any ideas or why?

  • Juan

    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

    • Thanks juan, i will update this code once i get time.

  • Chidanand

    @JitendraZaa:disqus Bro this code Doesn’t allow me to drop multiple files. Any workaround?

    • Hey Chidanand, it currently doesnt support multiple files, I will try to tweak code and update this article. meantime if you are able to do so, please share 🙂

      • Priyanka Singh

        Hello Zaa sir,I am new to Rest API,I tried this code bt getting error..plz check

        • Sameer Jaffery

          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

      • force.comlearner

        Jitendra Zaa have you implemented for multiple files drag n grop. can you please share it here…

        thanq

    • Priyanka Singh

      Hi i tried this code…but getting below error..please help

  • chethan

    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://

    • There is space in your URL before “v1” , It should work if URL is correct. – https://ap2.salesforce.com/services/apexrest/DragAndDrop/ v1

      • chethan

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

        • kamendra singh

          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

  • Sameer

    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!

    • When you are receiving this error ?

      • Sameer

        Problem solved…..thanks to reply me……

  • Rida Jamal

    XHR2s upload progress is not supported i have this error while trying to upload from IE 10 ? any idea please ?

  • kamendra singh

    Hi Jitendra,

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

    Thanks,
    Kamendra Singh

    • What error you are getting ?

      • kamendra singh

        Hi Jitendra,

        I am getting below error.please help me for same-

        • kamendra singh

          Hi Jitendra,

          Did you get chance to look that error.

          Thanks,
          Kamendra Singh

          • Bee Kay

            Please see post above. It should help.

  • Anvesh

    Hello Jitendra,
    We are trying this code but we are getting 400 bad request. Can you please help me?

    • Bee Kay

      Please see my post above. It should help.

  • Bee Kay

    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();
    }

    }

    • satish Apple

      hi bee kay.. Am still getting that error. would you mind please help me

    • pavan

      Failed to load resource: the server responded with a status of 404 (Not Found)

  • Dashing hunk

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

    Error message “INVALID_SESSION_IDSession expired or invalid”

  • Ido Greenbaum

    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.

  • Cristiano Sinadino

    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.

    • Cristiano Sinadino

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

  • Navaneeth Arya

    is there any possibility to upload folders?

  • Shahid

    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?