<apex:page showHeader="false" standardStylesheets="false" standardController="Account" extensions="SaveAsPdfExtension" contentType="{! renderedContentType }" renderAs="{! renderingService }"> <!-- This page must be called with an Account ID in the URL, e.g.: https://<salesforceInstance>/apex/AccountContactsPdf?id=001D000000JRBet --> <apex:form rendered="{! renderingService != 'PDF' }" style="text-align: right; margin: 10px;"> <apex:commandLink action="{! saveToPdf }" value="Save to PDF"> <apex:param assignTo="{! renderedFileName }" value="Contact-List.pdf"/> </apex:commandLink> <hr/> </apex:form> <h1>Contacts for {! Account.Name}</h1> <apex:dataTable value="{! Account.Contacts }" var="contact"> <apex:column headerValue="Name" value="{! contact.Name }"/> <apex:column headerValue="Title" value="{! contact.Title }"/> <apex:column headerValue="Phone" value="{! contact.Phone }"/> <apex:column headerValue="Email" value="{! contact.Email }"/> </apex:dataTable> <hr/> <!-- A little bit of info about the page's rendering; see how it changes when saved as a PDF. --> contentType: <apex:outputText value=" {! renderedContentType }"/><br/> renderingService: <apex:outputText value=" {! renderingService }"/><br/> </apex:page>
This example has two important elements. First, the renderAs and contentType attributes of the <apex:page> component are set dynamically using expressions. The values of these expressions control into which format the page is rendered.
The other element is the <apex:form>, which provides a user interface for saving the page to PDF. The form has one element, an <apex:commandLink> that calls the saveToPdf action method. An <apex:param> component provides a name for the PDF file, which is used in the controller code to set the file name.
The form is only displayed when the page is rendered as HTML; it’s not visible in the PDF version. This display trick is accomplished by setting the rendered attribute on the <apex:form> component to false when the page is rendered as a PDF file.
public class SaveAsPdfExtension { // Required extension constructor (empty, no-op) public SaveAsPDFExtension(ApexPages.StandardController controller) {} // Determines what kind of rendering to use for the page request public String renderingService { get; private set; } // Allow the page to set the PDF file name public String renderedFileName { get; set { renderedFileName = this.sanitizeFileName(value); } } // Rendered content MIME type, used to affect HTTP response public String renderedContentType { get { String renderedContentType = 'text/html'; // the default if( ! this.renderingAsHtml() ) { // Provides a MIME type for a PDF document renderedContentType = 'application/pdf'; // Add a file name for the PDF file if( this.renderedFileName != null) { // This is supposed to set the file name, but it doesn't work renderedContentType += '#' + this.renderedFileName; // This is a work-around to set the file name ApexPages.currentPage().getHeaders().put( 'content-disposition', 'attachment; filename=' + this.renderedFileName); } } return renderedContentType; } } // Are we rendering to HTML or PDF? public Boolean renderingAsHtml() { return ( (renderingService == null) || ( ! renderingService.startsWith('PDF')) ); } // Action method to save (or "print") to PDF public PageReference saveToPdf() { renderingService = 'PDF'; return null; } // Private helper -- basic, conservative santization private String sanitizeFileName(String unsafeName) { String allowedCharacters = '0-9a-zA-Z-_.'; String sanitizedName = unsafeName.replaceAll('[^' + allowedCharacters + ']', ''); // You might also want to check filename length, // that the filename ends in '.pdf', etc. return(sanitizedName); } }
The main part of the extension is simple. The renderingService property controls whether the page is rendered in HTML or PDF. Its value defaults to null when the page is loaded, and changes to “PDF” if the saveToPdf action method is called. The renderAs attribute of the <apex:page> component references renderingService. When it’s anything other than “PDF” the page renders normally as HTML. When it’s “PDF” the page—you guessed it—renders as a PDF file.
The renderedContentType property provides a MIME type value that is used by the contentType attribute of the Visualforce <apex:page> component. Setting this value affects the server response. It adds an HTTP header that tells the client browser the format of the response—in this case, either HTML or PDF.
The renderedContentType property also sets the file name for the downloaded PDF file. It gets the file name from the renderedFileName property, which is set using the <apex:param> component in the page. Although it’s documented that appending “#” and a file name to the contentType sets the file name that’s sent to the client browser, this convention doesn’t work. Therefore, a header is set to provide the file name.
If you don’t need to set the file name for the PDF download, you can ignore the renderedContentType and renderedFileName properties. This simpler approach to adding a save to PDF function is demonstrated in Fonts Available When Using Visualforce PDF Rendering.