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.
Leave a Reply