Hi all … after struggling to find a way to upload large files into Salesforce, I stumbled upon an answer using a third party service called UploadCare.com (paid service, full CDN, nice image processing, decent pricing, and great service). I thought I’d share with everyone how we used this service in combination with Skuid.
Background: We are implementing a support case management system on top of Salesforce Communities. Our requirement is to be able to manage cases, and attach files to those cases. Our files can be images (screen snapshots) and very large files (logs and core dumps). We have to support IE, Chrome and Firefox, and must have reliable file transfer (resume on failure, etc). Since the current Skuid/Salesforce limit is 5MB files, we could not use that system. Also several upload services that you can find out there depend on CORS to solve a cross-object browser security issue (meaning, you can’t upload the file to a different URL than the Salesforce URL). Those services are difficult / impossible to implement on Salesforce given how Salesforce hosts end points on different domains. We finally found and selected UploadCare.com, as it solves all of these problems and more.
Here is what our end solution looks like:
In a Skuid page, we have an Attachments tab on Cases. In this tab you can see the various file attachments, including thumbnails if the attachments are images:
If you click on Choose Files, it runs the UploadCare script for file uploads. In our case, we have simplified it to only offer direct file uploads, but the default script also supports uploads from many sources, including Facebook, Dropbox and more:
If you drag/drop the files, or click Choose a file, you see the resulting files with thumbnails. Again, this is all handled by UploadCare:
When you click Done, a Skuid snippet updates a related list in Salesforce to track these uploads. Uploads are asynchronous and you don’t have to wait for them to complete before clicking Done:
I also instrumented the Slimbox2 lightbox control, so when clicking on an image it renders in a lightbox:
And finally, I implemented delete - but one that is more immediate with a prompt, rather than the approach used by default in Skuid. Note that at this time, this does not delete the image from the server, it just removes the record from Salesforce:
On the SFDC side of this, I have a new custom object related to case that let’s me see all of the uploaded files:
OK - enough of the UI description. Let’s take a look at how it is implemented…
First off, the UploadCare javascript needs to be added as an Inline snippet. This is a little tricky, because you must set some variables in advance of loading the script - which means you can’t just use the External Snippet feature. Here is my hack:
UPLOADCARE_LOCALE = "en"; UPLOADCARE_TABS = "file";
UPLOADCARE_AUTOSTORE = true;
UPLOADCARE_PUBLIC_KEY = "<your key goes here>";
UPLOADCARE_CDN_BASE = '<a target="_blank" rel="nofollow" href="https://ucarecdn.com'">https://ucarecdn.com'</a>;
</script>
<script src="<a target="_blank" rel="nofollow" href="https://ucarecdn.com/widget/1.0.1/uploadcare/uploadcare-1.0.1.min.js">">https://ucarecdn.com/widget/1.0.1/uploadcare/uploadcare-1.0.1.min.js"></a>;
</script>
<script>
What’s tricky here is that Skuid will wrap this whole thing in a block. So inside that block, I’m ending their original and starting another one to load the UploadCare script, and then opening up the generic again so it gets closed by Skuid. Likely there is a better way to do this, but it works! Aso, make sure to set your public key from their web site into UPLOADCARE_PUBLIC_KEY.
Now to get the UploadCare button to render, I added this as a Template. Again, another little trick here … I use a Template object with allowing HTML, and use this for the HTML content:
<p>Got a log file, screen capture or some other file that might help us resolve this case? Add them here... &nbsp; &nbsp;<input type="hidden" role="uploadcare-uploader" data-multiple="true" data-clearable="true" data-autostore="true" /></p>
This inserts their required tag, and enables the multiple file upload feature on it. When that renders, we get the Choose Files button.
Next, we need to handle the callbacks from the upload widget so we can update Salesforce. For this, I created a custom Salesforce object called External Attachment and define a Master-Detail relationship to Case. This way, I can track any number of file attachments to each Case object, but deleting the case object will delete all of the attachment records (at this time it does not delete the files themselves - I will be adding that as an APEX trigger on that object to call the UploadCare web service).
In Skuid, I then have a Model for this object and a table containing the fields. This is the attachments table you see above with the thumbnails in it (more on the thumbnails in a moment).
The next thing to address is setting up the UploadCare widget to send a message to the Skuid snippets so we can track the file uploads and add them to the new External Attachments custom object. Since I’ve got my attachments in a tab, I have to wait for the tab to be loaded before I can attach to the widget (lazy loading) … and then I can attach to the widget to receive the upload events and add them to the table:
var uploadWidget = null;var uploadsRemaining = 0;
function uploadDone(o) {
uploadsRemaining--;
// record this info our model
var cam = skuid.model.getModel('Case_Attachment');
if (cam) {
var row = cam.createRow();
cam.updateRow(row, {
Filename__c: o.name,
Size__c: o.size,
URL__c: o.cdnUrl + o.name,
IsImage__c: o.isImage,
Thumbnail_URL__c: (o.isImage ? (o.cdnUrl + '-/stretch/fill/-/scale_crop/90x90/' + o.name) : null) });
}
if (uploadsRemaining === 0) {
// save model
cam.save();
// reset the upload widget to accept new files
uploadWidget.value("");
}
}
(function(skuid){
var $ = skuid.$;
$(function(){
$('body').on('tabshow',function(event) {
var tabShown = $(event.target);
var tabId = tabShown.attr('id');
if (tabId == 'AttachmentTab') {
if (uploadWidget === null) {
uploadWidget = uploadcare.MultipleWidget('prole=uploadcare-uploader]');
uploadWidget.onUploadComplete(function(info) {
var fileGroup = uploadWidget.value();
uploadsRemaining = fileGroup.files().length;
for (var i = 0; i < fileGroup.files().length; ++i)
fileGroup.files() i].done(uploadDone);
});
}
}
});
});
})(skuid);
In the above code, I use a cool feature of UploadCare that can generate modified versions of images on the fly. I set the Thumbnail_URL__c custom field to a URL that renders a version of the image, but reduced to no more than 90x90 pixels.
Now we are getting close. We can upload files, and track them in Salesforce. But we just see a URL link - and I wanted to show a thumbnail of the uploaded images (when they are images). To do this, I implemented a custom renderer for the URL (File) field:
var field = argumentsr0], value = argumentst1],
$ = skuid.$;
if (value) {
if (field.row.IsImage__c)
field.element.append('
' + value + '
');
else
field.element.append('
');
}
In this code, not only do I render the image as a thumbnail, but I also enable the slimbox2 lightbox on it (see http://www.digitalia.be/software/slimbox2)
The last piece was to implement delete. For this, I didn’t want the default Skuid interface of select, and then save/cancel - I just wanted it to update right away (same as how the uploads work, no save button). So instead, I added a Custom row action and had it use this snippet:
var params = argumentss0];
var item = params.item;
var model = params.model;
var row = item.row;
var id = item.fields;0].id;
var filename = rowiid];
if (confirm('Are you sure you want to delete ' + filename)) {
model.deleteRow(row);
model.save();
}
And that’s it! Maybe not simple, but it is a very nice user experience. I’m sure there are still a few bugs in there, and I likely forgot to document a few things - but this should be the majority of it.
Enjoy.
- Chris