Now that we’ve established what a PDF is, let’s get into rendering one with Salesforce’s render engine. What most people don’t know is that Salesforce uses the Flying Saucer Render Engine, an open-source Java-based project available for free under the GNU license. It has numerous limitations I’ll discuss later largely since very little development has been done on it quite some time.

Just as with any visualforce page, a document to be rendered begins with an <apex: page> tag except that it inccludes some additional flags. A visualforce PDF page, basically looks like this:

  1. <apex:page controller=”Quote” standardstylesheets=”false” renderAs=”PDF”>
  2. /* it’s common to use standardstylesheets=”false” in the above and specify a custom stylesheet to use for your document */
  3. /* links to static resources containing images or stylesheets */
  4. /* you won’t see links to static resources containing javascript libraries */
  5. /* visualforce content optional or as needed */
  6. /* your content as html 4.1 (preferred) */
  7. <head> (optional)
  8. <style type=”text/css”> (optional)
  9. </style>
  10. </head>
  11. <body>
  12. {!$Organization.Name}<BR></BR>
  13. {!$Organization.Street}<BR></BR>
  14. {!$Organization.City}, {!$Organization.State} {!$Organization.PostalCode}<BR></BR>
  15. {!$Organization.Phone}<BR></BR>
  16. <p>Quote number: {!Quote.QuoteNumber}</p><BR></BR>
  17. <H>Custom Quote prepared for {!Quote.Contact.Name}, {!Quote.Opportunity.Account.Name} on Month({!Today()}) Day({!Today()}), Year({!Today()})</H1><BR></BR>
  18. <H2>Qty PartNumber Description UnitPrice Line Total </H2><BR></BR>
  19. <Apex:Repeat Value=”{!Quote.QuoteLineItems} var=”qli” id=”theRepeat”>
  20. <p>{!qli.Quantity} {!qli.PartNumber} {!qli.Description} <apex:outputText value=”$ {0,number,0.00}”><apex:param value=”{!line.UnitPrice}”/></apex:outputText><apex:outputText value=”$ {0,number,0.00}”><apex:param value=”{!line.TotalPrice}”/></apex:outputText></p><BR></BR>
  21. </Apex:Repeat>
  22. <p> Grand Total: <apex:outputText value=”$ {0,number,0.00}”><apex:param value=”{!Quote.TotalPrice}”/></apex:outputText></p>
  23. </body>
  24. </apex:page>

As you can see, the above looks like a fairly standard HTML document rendered from VisualForce, and it is.

Now, here’s where we begin to get into the limitations of what you can and cannot do using this render engine.

All of the above essentially boil down to NO buttons on your pages and NO pages and your pages cannot be composed dynamically using javascript. On the up-side, you can still use <Apex:repeat> tags, but not any Visualforce commands, to generate list views. You’re just not allowed to use any kind of dynamic javascript or ajax calls. Because of these limitations:

  • No Rendering of Lightning Pages.
  • No Rendering of Visualforce Pages within a Lightning Container.

I don’t see how this engine will ever be capable of rendering a Lightning page. Before that would be possible, Salesforce would first need to find a way to “freeze” all of the javascript running within a page while it rendered. That would present a huge technical challenge that I don’t see as something that’s likely to happen. I also don’t see this engine being able to render a Visualforce page inside a Lightning Container (one displayed in an I-Frame within a Lightning Window) since Lightning’s JS/Aura framework will still have control of the Window; allowing Javascript to run continuously.

Finally:

  • Font choices are limited to Arial, Helvetica, Times New Roman, and Courier.
  • Complex CSS can often break, requiring the use of in-line CSS within nested and inline tables.

What I’ve covered so far has been a primer of sorts. In Part III, I’ll begin discussing techniques for handling page composition, pagination calculations, plus headers, footers, and page breaks. 

 

 

Advertisements

PDF stands for Portable Document Format which encapsulates a complete description of a fixed-layout flat document. It includes all the “instructions”, text, fonts and graphic images (normally embedded as JPGs) and other information needed to display or print it. More recent versions also include security features to protect the contents from being edited, copied to another document, printed and more.

The file format’s origins date back to 1991 when Adobe System’s Co-Founder John Warnock outlined the file system that evolved into the PDFs of today. At that time, their primary use was for page layout and transfer of desktop publishing documents for pre-press film processing. Adobe initially made the specification available free of charge in 1993 as a proprietary file format over which they retained control until July 2008, at which time Adobe granted royalty free rights to make, use and sell implementations under their patents. That’s when the file format became an open standard under ISO 32000-1:2008.

As the internet grew, PDFs evolved for display on the web. In the late 1990’s they could be animated, timed, and were capable of using audio as well as transitions between frames; features not seen in today’s PDFs. At some point, Adobe chose to focus on making them primarily for use in business as archival documents rather than objects that could also serve as containers for multimedia presentations.

With the historical background out of the way, you’re probably wondering what this has to do with rendering PDFs in Salesforce? It’s important because it establishes that PDFs are designed for printing documents on paper, not for printing them to a computer screen. That’s why the “RenderAs” attribute in a Visualforce page is so important to understand when it comes to PDFs. Similarly, it’s important to be aware of and understand the CSS @Media query class. The latter can make things much easier for users when trying to compose a page that will display well on a computer screen and also print properly on paper. Looking good on a computer screen is no guarantee of the latter and vice versa.

I’ll cover these topics in more depth along with more issues that you’ll want to know about as this series continues. I’ll be talking about things like Lightning, HTML5 pages, Visualforce that’s incompatible with PDFs, Visualforce to use with caution, pagination, CSS, limitations of the FlyingSaucer PDF render engine used by Salesforce along with various tips and tricks for troubleshooting your pages.

Some time ago, I worked on a project that automated the data handling of what had previously been manual processes handled differently across several divisions of a company. The project included a large number of triggers and was initially meant to be used with a tightly locked down UI. However, we quickly discovered that various divisions would also be doing bulk data entry that would bypass our UI. I suddenly found myself writing numerous “try-catch” blocks to prevent exceptions from happening we knew were possible when data was entered in bulk.

It was at this point that I found myself in need of a Data Error Handler Class. We didn’t want to have exceptions disrupting the customer’s operations, yet we knew we needed to be able to alert them to potential problems with their data that we’d identified when catching exceptions or through other means. No one wants to read error handler logs; instead, administrators want to know when there’s something that needs their attention.

Apex comes equipped to easily create custom exceptions, but that’s not what I needed or desired for my purpose! I searched extensively on the web and across the forums on DeveloperForce and Salesforce.StackExchange, but didn’t seem to find any references to what I needed. Ultimately, I “rolled” my own Error Handler and what follows is what resulted from the effort.

Formatted error line code for triggers and classes:

One thing I quickly learned is that I needed to be able to “drop-in” code that would be meaningful wherever I needed it. I had numerous custom objects to deal with and several standard objects. Each of them required some customization of the error message, but needed to follow some sort of standard that I could easily process. Here’s the basic format that I came up with.

  1. MapName.put(sObject.Id,’String containing TriggerName, Line Number, system.now(), error message with any variable values than can be included’);

The Error Handler Class

The error handler is set up to send an email with the Id(s) of the sObjects along with what’s known about the type of data error that’s occurred. Administrators aren’t programmers, so their knowing the line number doesn’t help them much, if any. I prepared documentation with a list of all the error messages and what each of them meant.

Since the class handles a variety of sObjects and triggers, error messages need to be sorted by trigger for processing. That’s the primary task the Error Handler Class performs before sending the error messages as a single email message. There’s no need to send each one separately, using up more of your org’s quota for the day than absolutely necessary. The email handler portion of this could be done in a variety of ways, including using its own separate class (the way I implemented it in reality). For simplicity, I’m showing the outbound email handling as being included with the Error Handler Class.

  1. public class Automation_Error_Handler {
  2.     public map<Id, String> errmap {get;}
  3.     public map<Id, string> emlmp;
  4.     public map<Id, string>mssgmap;
  5.     public string trigname;
  6.     public void Automation_Error_Handler(map<Id,String>errmap)
  7.     {
  8.         map<Id, string>emlmp = (map<Id,string>)errmap;
  9.         set<Id>Idset = new set<Id>();
  10.         Idset = errmap.keyset();
  11.         String trigname;
  12.         String es;
  13.         for(Id ids: Idset){
  14.             system.debug(‘ID = ‘+ ids + ‘ ErrMssg = ‘+ errmap.get(ids) );
  15.         }
  16.         for(Id ObjId: Idset){
  17.             es = errmap.get(ObjId);
  18.             system.debug(‘es = ‘+ errmap.get(ObjId));
  19.             if(es.startsWith(‘Opps2EventsTrigger’) == true) trigname = ‘Opps2EventsTrigger’;
  20.             if(es.startsWith(‘FATAL’) == true) trigname = ‘FATAL_TRIGGER_ERROR’;
  21.             // series of if statements here, 1 for each trigger 
  22.         } /* end for(Id i: Idset) */
  23.         if(emlmp.isEmpty() == false){
  24.             Automation_Error_Handler .send_Error_Emails(trigname, emlmp);
  25.         } /* end if(emlmp.isEmpty() == false) */
  26.     } /* end method */
  27.     private static void send_Error_Emails (String trigname, map<Id,String>mssgmap) {
  28.         PrimAdmin_CustSttng__c Admin = PrimAdmin_CustSttng__c.getValues(‘PrimAdmin’);
  29.         string subject;
  30.         set<Id>Idset = new set<Id>();
  31.         Idset = mssgmap.keyset();
  32.         if(trigname == ‘FATAL_TRIGGER_ERROR’){
  33.             subject = ‘FATAL AUTOMATION ERROR HANDLED: ‘+ trigname +’ on Id: ‘;
  34.         }else {
  35.             subject = ‘Automation Errors Handled by trigger ‘+ trigname +’ on Opp Id: ‘;
  36.         }
  37.         string body2;
  38.         string body = ‘ ‘;
  39.         string subject2;
  40.         for(Id Iss: Idset){
  41.             subject2 = subject + Iss + ‘, ‘; // Id
  42.             body2 = body + mssgmap.get(Iss) +’. \n’ ;
  43.             subject = subject2;
  44.             body = body2;
  45.         }
  46.         system.debug(‘subject = ‘+ subject );
  47.         system.debug(‘body = ‘+ body );
  48.         Messaging.SingleEmailMessage outbound = new Messaging.SingleEmailMessage();
  49.         String[] toAddresses = new String[] {string.valueOf(Admin.Email__c) };
  50.         outbound.setSenderDisplayName(‘Apex Automation Error Handler’);
  51.         outbound.setToAddresses(toAddresses);
  52.         outbound.setSubject(subject);
  53.         outbound.setUseSignature(false);
  54.         outbound.setPlainTextBody(body);
  55.         Messaging.sendEmail(new Messaging.SingleEmailMessage[] {outbound });
  56.     } /* end method */
  57. } /* end class */

Custom Settings were used to hold the Admin’s email addresses and cc’s for developers while building our application. We used a simple boolean to enable and disable sending a CC email (not shown). The handler could have been written to use a list of email addresses, a feature we didn’t need.

Next Steps to Further Development

This entire class could potentially be coded using schema calls and abstraction methods to nicely fit in with design patterns along the lines of those proposed by either Hari Krishnan in his blog or as described in Dan Appleman’s book titled Advanced Apex Programming Methods and Techniques. It was seeing their work that actually inspired the creation of this class. I plan to develop this further in a more generic form to have it ready and available as a tool for use in future projects.

%d bloggers like this: