
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 & 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 & 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.
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
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
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”.
Thanks the code is awesome, but I;m having some issues the responseText comes empty. Any ideas or why?
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.
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);}
@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 🙂
Hello Zaa sir,I am new to Rest API,I tried this code bt getting error..plz check
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
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
Hi Dhariya,
I’m using test sandbox. Where I ll get the package of DragAndDrop – I am newbie to REST API so, bit confused.
Jitendra Zaa have you implemented for multiple files drag n grop. can you please share it here…
thanq
Removing below code worked for me.. I am not sure why should that be difficult after such a great implementation..
//if (files.length > 1) {
// alert(‘Multi Upload is not supported, please try to upload single file’);
// return;
//}
Hi i tried this code…but getting below error..please help
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
Hey, I made that change but still it’s not working.. I removed space in component. Is there any other place I should change.
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
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 ?
Problem solved…..thanks to reply me……
XHR2s upload progress is not supported i have this error while trying to upload from IE 10 ? any idea please ?
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 ?
Hi Jitendra,
I am getting below error.please help me for same-
Hi Jitendra,
Did you get chance to look that error.
Thanks,
Kamendra Singh
Please see post above. It should help.
Hello Jitendra,
We are trying this code but we are getting 400 bad request. Can you please help me?
Please see my post above. It should help.
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();
}
}
hi bee kay.. Am still getting that error. would you mind please help me
Failed to load resource: the server responded with a status of 404 (Not Found)
You are a wonderful person. I kneel down and kiss your feet. Thank you!
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);}
Fails in Sites when a VF page is setup so there is no login required.
Error message “INVALID_SESSION_IDSession expired or invalid”
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.
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.
I found the solution to the error.
var sfdcHostName =window.location.host.split(‘.’)[1]; was not parsing the URL properly.
Could you please paste your code for parsing the URL as we are facing same error
Thanks!
is there any possibility to upload folders?
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?
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?
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
I found the answer for this.The functionality started running for portal users after APIs were enabled for them.
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..
Hi jitendra can we do this in lightening ?
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.
Hi Pavithra ,I am also facing the same problem .Please let us know if you did this fix
Advanced Thanks
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?
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..?
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..?
Hi Jitendra the code is not supporting in Internet Explorer Can you please post the solution
Thanks
Can you Please suggest me how to refresh automatically attachment section after uploading the files?
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.
Hello, this looks promising but I am not able to get it to render on an account detail page. Is anyone still using this?
Hi Jitendra,
Awesome post. Works perfectly for a logged in user. But for a guest user on a force.com site, this returns a 503 error. Any idea how to solve this?