Compressing files in Salesforce

January 30th, 2013 § 40 comments

Imagine that you want to zip a bunch of Files (Attachments, Documents, etc.) and send them via email. Right now there isn’t any app in the Appexchange that provides this functionality and, actually, there isn’t any class in Salesforce that allows developers to compress files. So, what’s the workaround? I will give you a clue… forget about compress server-side ;)

Right now the only way is performing the compression client-side. The way in which this works is by using the Javascript library called JSZip.

Tested in the following web browsers:

  • Chrome v24.
  • Firefox v18.
  • Safari v6.

Features

  • Compress Attachments, Documents, etc. in one single ZIP file.
  • Save the ZIP file in Salesforce and/or send it by email.

Improvements

The way in which the Body of each file is retrieved could be improved. I’m using Javascript remoting but it has some important limitations:

  • The response of the remote call has a maximum size of 15 MB.
  • The response of the remote call must return within 30 seconds, after which the call will time out.

The correct way is downloading the content of each file via URL as you can see in this link, but right now I’m facing with a cross-domain issue.

Achilles’ heel

Because the ZIP file is uploaded to Salesforce, the maximum size is 10MB.

About this example

In order to show how this functionality works I’ve developed the following use case:

  • Create a visualforce page that displays a list of Attachments.
  • Select one or more Attachments and generate a ZIP file.
  • Upload the ZIP file as a Document in Salesforce.

The Apex controller

Here is the Apex controller’s code:

/**
 * @author      Josep Vall-llobera <valnavjo_at_gmail.com>
 * @version     1.1.0
 * @since       03/02/2013
 */
public class SfdcZipSampleController {

	public String zipFileName {get; set;}
	public String zipContent {get; set;}

	public PageReference uploadZip() {
		if (String.isEmpty(zipFileName) ||
			String.isBlank(zipFileName)) {
			zipFileName = 'my_zip.zip';
		}
		else {
			zipFileName.replace('.', '');
			zipFileName += '.zip';
		}
		
		Document doc = new Document();
		doc.Name = zipFileName;
		doc.ContentType = 'application/zip';
		doc.FolderId = UserInfo.getUserId();
		doc.Body = EncodingUtil.base64Decode(zipContent);
		
		insert doc;
		
		this.zipFileName = null;
		this.zipContent = null;

		PageReference pageRef = new PageReference('/' + doc.Id);
		pageRef.setRedirect(true);
		
		return pageRef;
	}

	public List<Attachment> getAttachments() {
		return [select Id, ParentId, Name, ContentType, BodyLength
				from Attachment
				limit 100];
	}
	
	@RemoteAction
	public static AttachmentWrapper getAttachment(String attId) {
		
		Attachment att = [select Id, Name, ContentType, Body
						  from Attachment
						  where Id = :attId];
		
		AttachmentWrapper attWrapper = new AttachmentWrapper();
		attWrapper.attEncodedBody = EncodingUtil.base64Encode(att.body);
		attWrapper.attName = att.Name;
						  
		return attWrapper;
	}
	
	public class AttachmentWrapper {
		public String attEncodedBody {get; set;}
		public String attName {get; set;}
	}
}

The Visualforce page

Here is the Visualforce page:

<apex:page controller="SfdcZipSampleController" showHeader="true" sidebar="false">

<head>
	<script type="text/javascript" src="/soap/ajax/26.0/connection.js"> </script>

	<apex:includeScript value="{!URLFOR($Resource.jQuery, '/jquery-1.8.2.min.js')}"/>

	<apex:includeScript value="{!URLFOR($Resource.js_zip, '/jszip.js')}"/>
	<apex:includeScript value="{!URLFOR($Resource.js_zip, '/jszip-load.js')}"/>
	<apex:includeScript value="{!URLFOR($Resource.js_zip, '/jszip-deflate.js')}"/>
	<apex:includeScript value="{!URLFOR($Resource.js_zip, '/jszip-inflate.js')}"/>

    <script type="text/javascript">
        var j$ = jQuery.noConflict();

        j$(document).ready(function() {
        	//Hide upload button
        	var uploadZipButton = j$('input[id$=uploadZipButton]');
        	uploadZipButton.css('display', 'none');
        	
        	//Instantiate JSZip
        	var zipFile = new JSZip();

			//Intercept click event
        	j$('a[id=generateZipLink]').click(function() {

				var checkedSize = j$("input[name='att_ids_group[]']:checked").size();
				var count = 0;

				//Iterate over each selected file				
				j$("input[name='att_ids_group[]']:checked").each(function() {
					//Refresh zip process status
					j$('span[id$=zipStatus]').text('Getting file...');
					
					//Get file using javascript remoting
					Visualforce.remoting.Manager.invokeAction(
						'{!$RemoteAction.SfdcZipSampleController.getAttachment}',
						j$(this).val(),
						function(result, event){
							if (event.status) {
								//Refresh zip process status
								j$('span[id$=zipStatus]').text('Compressing file "' + result.attName + '"...');

								count++;
								
								//Add the file
								compressFile(zipFile, result.attName, result.attEncodedBody);
								
								//Once all the selected files have been compressed
								if (count == checkedSize) {
									//Refresh zip process status
									j$('span[id$=zipStatus]').text('Generating zip file...');
									
									//Send form
									sendZip(zipFile);
								}
							} else if (event.type === 'exception') {
								alert('Exception: ' + event.message);
							} else {
								alert('Message: ' + event.message);
							}
						}, 
						{escape: true}
					);//End getAttachment

				});//end each selected attId
        	});//end click
			
			//Compress one single file
			function compressFile(zipFile, name, data) {
				zipFile.file(name, data, {base64:true});
			}

			//Generate and upload zip file
			function sendZip(zipFile) {
				var data = zipFile.generate();
						
				var zipContent = j$('input[id$=zipContent]');
				zipContent.val(data);
				
				//Refresh zip process status
				j$('span[id$=zipStatus]').text('Uploading zip file...');
				
				var uploadZipButton = j$('input[id$=uploadZipButton]');
				uploadZipButton.trigger('click');
			}
        });
    </script>
</head>

<apex:form id="uploadZipForm" enctype="multipart/form-data">
	
	<apex:inputHidden id="zipContent" value="{!zipContent}" />
	
	<apex:commandButton id="uploadZipButton" value="Upload" action="{!uploadZip}" reRender="thePageBlock" />
	
	<apex:pageBlock id="thePageBlock" title="Attachments">
		<apex:pageBlockTable value="{!attachments}" var="att">
			
			<apex:column >
				<input type="checkbox" name="att_ids_group[]" value="{!att.Id}" />
			</apex:column>
			<apex:column value="{!att.Name}" />
			<apex:column value="{!att.ContentType}" />
			<apex:column value="{!att.BodyLength} kb" />
			<apex:column value="{!att.ParentId}" />

		</apex:pageBlockTable>
	</apex:pageBlock>

	<apex:outputLabel for="zipFileName" value="File name: " />
	<apex:inputText id="zipFileName" value="{!zipFileName}" />.zip
	
	<p>
		<a id="generateZipLink" href="#">Generate zip</a>
	</p>
	<p>
		<span>Status: </span>
		<span id="zipStatus"></span>
	</p>
	
</apex:form>

</apex:page>

Screenshots

[nggallery id=6]
[nggallery id=7]

Source code

You can download the source code from here.

Be Apex my friend!

Useful resources

JSZip library
Javascript remoting
Working with Base64 Binary Encoded Strings
The question that raised this post

Tagged ,

§ 40 Responses to Compressing files in Salesforce"

Leave a Reply

Your email address will not be published. Required fields are marked *

Code resources available on:
My presentations:
Check out my apps on:
SFDC certified professional:

What's this?

You are currently reading Compressing files in Salesforce at Valnavjo's blog.

meta