+ Start a Discussion
mkdjnsmkdjns 

Render semi-complex Visualforce page as PDF, save as attachment

I'm having an issue with saving a Visualforce page as a PDF attachment.

 

My page is (simplified):

<apex:page id="thePage" standardController="Case" extensions="customStuff" renderAs="{!renderMode}" showHeader="{!renderMode != 'pdf'}" standardStylesheets="{!renderMode != 'pdf'}">

   <apex:form id="theForm" rendered="{!renderMode != 'pdf'}">
      <apex:pageBlock id="theBlock">
         <apex:pageBlockSection id="theSection" columns="3">
            <apex:pageBlockSectionItem id="theSectionItems">
               <apex:outputLabel for="chooseTemplate" value="Choose the EOB template to use:" />
                  <apex:outputPanel >
                     <apex:selectList id="chooseTemplate" value="{!template}" size="1">
                        <apex:selectOptions value="{!templates}" />
                        <apex:actionSupport event="onchange" rerender="theBlock, theLetterBody" status="loadStatus" onsubmit="javascript&colon;document.body.style.cursor = 'wait';" oncomplete="javascript&colon;document.body.style.cursor = 'default';" />
                     </apex:selectList>
                     <apex:actionStatus id="loadStatus">
                     <apex:facet name="start"><img src="/img/loading.gif" /></apex:facet>
                  </apex:actionStatus>
               </apex:outputPanel>
            </apex:pageBlockSectionItem>
            <apex:commandButton action="{!savePDF}" value="Attach PDF" rendered="{!len(template) > 1}"/>
         </apex:pageBlockSection>
      </apex:pageBlock>
   </apex:form>

      <div id="content">
      </div>
</apex:page>

 

 

And the controller (also simplified):

 

public class customStuff {

  private string caseID;
  public Case activeCase {get; set;}
  public string renderMode {get; set;}
  public string Template {get; set;}
	
  public customStuff(ApexPages.StandardController controller) {
		
    caseID = ApexPages.currentPage().getParameters().get('id');
    if (caseID != null){
      setActiveCase();
    }
    	
    	if(ApexPages.currentpage().getParameters().get('t') != null) {
    		template = ApexPages.currentpage().getParameters().get('t');
    	}
    	
    if(ApexPages.currentPage().getParameters().get('p') != null) {
      renderMode = 'pdf';
    } else {
      renderMode = null;
    }
  }

  public pageReference setActiveCase() {
    activeCase = new Case();
    this.activeCase = [select id from Case where id = :caseID];
    return null;
  }

  public pageReference savePDF() { 
    pageReference thePage = Page.customStuff;
    thePage.getParameters().put('p','pdf');
    thePage.getParameters().put('t',Template);
    thePage.getParameters().put('id',CaseID);
    
    blob body = thePage.getContent();
    
    attachment pdf = new attachment();
    pdf.filename='my filename';
    pdf.body = body;
    pdf.ParentId = activeCase.id;
    pdf.isPrivate = false;
    
  }

  public list<selectOption> getTemplates() {
    list<selectOption> theTemplates = new list<selectOption>();
    theTemplates.add(new selectOption('', '--None--'));
    theTemplates.add(new selectOption('1', 'Item 1'));
    theTemplates.add(new selectOption('2', 'Item 2'));
    theTemplates.add(new selectOption('3', 'Item 3'));
    theTemplates.add(new selectOption('4', 'Item 4'));
    return theTemplates;
  }

}

 

 

My issue is that calling the page in the savePDF method seems to ignore the added parmeters, but only in certain instances. For example, the standard header and styles are removed, but the pageBlock is still rendered. And the page doesn't render as a pdf, even though it has received a parameter instructing it to.

 

This boggles the mind, but I'm hoping there's something simple I'm missing.

 

Thanks in advance!!

 

-Mike

Best Answer chosen by mkdjns
TehNrdTehNrd

Sorry I'm late to the party but I have found a solution!!!

 

To be brutally honest I'm exactly sure what the issue is. There seems to be some funky behavior with instantiating a new instance of the same page you are currently on. Constructor doesn't run on new page and there is some other weirdness I haven't spent time to isolate. The solution....

 

replace:     <apex:form id="theForm" rendered="{!renderMode != 'pdf'}">

 

with:     <apex:form id="theForm" rendered="{!$CurrentPage.parameters.p != 'pdf'}">

 

Here is the code I modified:

PAGE:

 

<apex:page id="thePage" standardController="Case" extensions="customStuff" renderAs="{!renderMode}" showHeader="{!renderMode != 'pdf'}" standardStylesheets="{!renderMode != 'pdf'}">

<apex:form id="theForm" rendered="{!$CurrentPage.parameters.p != 'pdf'}">
<apex:pageBlock id="theBlock">
<apex:outputLabel for="chooseTemplate" value="Choose the EOB template to use:" />
<apex:outputPanel >
<apex:selectList id="chooseTemplate" value="{!template}" size="1">
<apex:selectOptions value="{!templates}" />
<apex:actionSupport event="onchange" rerender="theBlock,theLetterBody,preview" status="loadStatus"/>
</apex:selectList>
</apex:outputPanel>

<apex:commandButton action="{!savePDF}" value="Attach PDF" rendered="{!len(template) > 0}"/>
</apex:pageBlock>
</apex:form>

<apex:outputPanel id="preview">
<div id="content">
{!Case.Subject}<br/><br/>

{!template}<br/><br/>

Parameters:<br/>
p: {!$CurrentPage.parameters.p}<br/>
t: {!$CurrentPage.parameters.t}<br/>
id: {!$CurrentPage.parameters.id}<br/>
</div>
</apex:outputPanel>
</apex:page>

 

 

EXTENSION:

 

public class customStuff {

private string caseID;
public Case activeCase {get; set;}
public string renderMode {get; set;}
public string Template {get; set;}

public customStuff(ApexPages.StandardController controller) {

system.debug('CONTROLLLLLERRRRR!!!!!');

caseID = ApexPages.currentPage().getParameters().get('id');
if(caseID != null){
setActiveCase();
}

if(ApexPages.currentpage().getParameters().get('t') != null) {
template = ApexPages.currentpage().getParameters().get('t');
}

if(ApexPages.currentPage().getParameters().get('p') != null) {
renderMode = 'pdf';
} else {
renderMode = null;
}
}

public pageReference setActiveCase() {
activeCase = new Case();
this.activeCase = [select id from Case where id = :caseID];
return null;
}

public PageReference savePDF() {
PageReference thePage = Page.customStuff;
thePage.getParameters().put('p','pdf');
thePage.getParameters().put('t',Template);
thePage.getParameters().put('id',CaseID);
thePage.setRedirect(true);

blob body = thePage.getContentAsPDF();

attachment pdf = new attachment();
pdf.name = 'myfilename.pdf';
pdf.body = body;
pdf.ParentId = activeCase.id;
pdf.isPrivate = false;
insert pdf;

PageReference casePage = new PageReference('/'+caseID);
casePage.setRedirect(true);
return casePage;
}

public list<selectOption> getTemplates() {
list<selectOption> theTemplates = new list<selectOption>();
theTemplates.add(new selectOption('', '--None--'));
theTemplates.add(new selectOption('1', 'Item 1'));
theTemplates.add(new selectOption('2', 'Item 2'));
theTemplates.add(new selectOption('3', 'Item 3'));
theTemplates.add(new selectOption('4', 'Item 4'));
return theTemplates;
}

}

 

 

 

All Answers

bob_buzzardbob_buzzard

Have you tried using the getContentAsPDF pagereference method?  I've used that in the same way that you are without problems.

mkdjns.123456mkdjns.123456

I have tried that. The issue still exists as the elements of the page that are supposed to not render if renderMode == 'pdf' (essentially, the entirety of the <apex:form> element) still render when the page is called from the savePDF method.

 

If I take the content of the savePDF method and run it in the System Log, I get a perfectly normal PDF with all of the hidden elements hidden. I assuem it's because that code isn't running in the sam context as the page itself.

 

I've tried putting the savePDF method into its own class with no luck, so I think Salesforce is simply getting confused.

 

-Mike

joshbirkjoshbirk

The PDF translation is only done when the page is first created, so you may need to do a full redirect, IE:

 

  public pageReference savePDF() { 
    pageReference thePage = Page.customStuff;
    thePage.getParameters().put('p','pdf');
    thePage.getParameters().put('t',Template);
    thePage.getParameters().put('id',CaseID);
    thePage.setRedirect(true);

   return thePage;
  }

 

Or, if you want an attachment - you can getContentAsPDF and then redirect based on id (different example, but similar):

blob pdf = pdfPage.getContentAsPDF();
        Attachment a = new Attachment();
        a.Body = pdf;
        a.Name = 'Agreement for '+contact.Name;
        a.ParentId = contact.Id;
        insert a;
        return new PageReference('/'+a.id);

Hope that helps!

mkdjns.123456mkdjns.123456

That doesn't seem to do the trick either. It's almost like the url parameters are being ignored when the page is called in the save method. I can't figure out why some of the added parameters are being ignored when the pdf is generated.

joshbirkjoshbirk

What does the URL look like when the page is redirected?

mkdjns.123456mkdjns.123456

The new pethod looks like:

public pageReference savePDF() { 
	pageReference pdf = Page.customStuff;
	pdf.getParameters().put('p','pdf');
	pdf.getParameters().put('t',Template);
	pdf.getParameters().put('id',CaseID);
	pdf.setRedirect(true);
		
	blob body = pdf.getContent();
	string filename = 'my File Name';
		
	attachment theFile = new attachment();
		
	theFile.isPrivate = false;
	theFile.body = body;
	theFile.ParentId = activeCase.id;
	theFile.Name = filename;

	insert theFile;

	return pdf;
}

When the page refreshes, the url is '/customStuff?id=<mycaseid>&p=pdf&t=<myselectedtemplate>, and the browser contains my nicely formatted PDF, with the pageBlock not rendered. However, the resulting attachment is NOT a pdf. Instead it is html.

 

If I debug renderMode in the savePDF method, it is null. That seems to be the key. Does the constructor not fire again with what I'm doing?

 

joshbirkjoshbirk

The attachment needs:

 

blob body = pdt.getContentAsPDF()

 

instead of just .getContent()

mkdjns.123456mkdjns.123456

I don't think so. I'm conditionally rendering the page as pdf, based on parameters passed in the URL. If I do getContentAsPDF(), thePageBlock will still be there since the renderMode in the controller is NEVER set to pdf.

 

 

<apex:page id="thePage" standardController="Case" extensions="customStuff" renderAs="{!renderMode}" showHeader="{!renderMode != 'pdf'}" standardStylesheets="{!renderMode != 'pdf'}">

 

 

joshbirkjoshbirk

Are you sure?  The renderMode the conditional is looking at is the string the controller sets, right?  getContent and getContentAsPDF should pull the same result - they just force the result to be HTML versus PDF.

mkdjnsmkdjns

Pretty sure. The renderMode conditional seems to be completely ignored whenever I enter the savePDF method. Even if I set renderMode to 'pdf' in the savePDF method, it seems to be ignored by the page and pageBlock elements.

 

I've tried not doing the conditional renderAs, but renderMode is still ignored by the pageBlock.I've tried setting rendermode = pdf in the savePDF method and it still is ignored.

 

I'm at a loss!

mkdjnsmkdjns

Josh-

 

I've found a hacky, kludgy solution to the problem. What you said earlier about the pdf translation happening at the time the page is created FINALLY got through.

 

I ended up having to create an actionFunction that would call the savePDF method. The actionFunction is called by the onComplete of a button that sets renderMode = 'pdf' and then rerenders the pageBlock.

 

All of this generally works, but is a bit slow. I'd like to find an alternate solution if one exists, but for now, I'm moving on.

 

Thanks for taking a look!

 

Mike

TehNrdTehNrd

Sorry I'm late to the party but I have found a solution!!!

 

To be brutally honest I'm exactly sure what the issue is. There seems to be some funky behavior with instantiating a new instance of the same page you are currently on. Constructor doesn't run on new page and there is some other weirdness I haven't spent time to isolate. The solution....

 

replace:     <apex:form id="theForm" rendered="{!renderMode != 'pdf'}">

 

with:     <apex:form id="theForm" rendered="{!$CurrentPage.parameters.p != 'pdf'}">

 

Here is the code I modified:

PAGE:

 

<apex:page id="thePage" standardController="Case" extensions="customStuff" renderAs="{!renderMode}" showHeader="{!renderMode != 'pdf'}" standardStylesheets="{!renderMode != 'pdf'}">

<apex:form id="theForm" rendered="{!$CurrentPage.parameters.p != 'pdf'}">
<apex:pageBlock id="theBlock">
<apex:outputLabel for="chooseTemplate" value="Choose the EOB template to use:" />
<apex:outputPanel >
<apex:selectList id="chooseTemplate" value="{!template}" size="1">
<apex:selectOptions value="{!templates}" />
<apex:actionSupport event="onchange" rerender="theBlock,theLetterBody,preview" status="loadStatus"/>
</apex:selectList>
</apex:outputPanel>

<apex:commandButton action="{!savePDF}" value="Attach PDF" rendered="{!len(template) > 0}"/>
</apex:pageBlock>
</apex:form>

<apex:outputPanel id="preview">
<div id="content">
{!Case.Subject}<br/><br/>

{!template}<br/><br/>

Parameters:<br/>
p: {!$CurrentPage.parameters.p}<br/>
t: {!$CurrentPage.parameters.t}<br/>
id: {!$CurrentPage.parameters.id}<br/>
</div>
</apex:outputPanel>
</apex:page>

 

 

EXTENSION:

 

public class customStuff {

private string caseID;
public Case activeCase {get; set;}
public string renderMode {get; set;}
public string Template {get; set;}

public customStuff(ApexPages.StandardController controller) {

system.debug('CONTROLLLLLERRRRR!!!!!');

caseID = ApexPages.currentPage().getParameters().get('id');
if(caseID != null){
setActiveCase();
}

if(ApexPages.currentpage().getParameters().get('t') != null) {
template = ApexPages.currentpage().getParameters().get('t');
}

if(ApexPages.currentPage().getParameters().get('p') != null) {
renderMode = 'pdf';
} else {
renderMode = null;
}
}

public pageReference setActiveCase() {
activeCase = new Case();
this.activeCase = [select id from Case where id = :caseID];
return null;
}

public PageReference savePDF() {
PageReference thePage = Page.customStuff;
thePage.getParameters().put('p','pdf');
thePage.getParameters().put('t',Template);
thePage.getParameters().put('id',CaseID);
thePage.setRedirect(true);

blob body = thePage.getContentAsPDF();

attachment pdf = new attachment();
pdf.name = 'myfilename.pdf';
pdf.body = body;
pdf.ParentId = activeCase.id;
pdf.isPrivate = false;
insert pdf;

PageReference casePage = new PageReference('/'+caseID);
casePage.setRedirect(true);
return casePage;
}

public list<selectOption> getTemplates() {
list<selectOption> theTemplates = new list<selectOption>();
theTemplates.add(new selectOption('', '--None--'));
theTemplates.add(new selectOption('1', 'Item 1'));
theTemplates.add(new selectOption('2', 'Item 2'));
theTemplates.add(new selectOption('3', 'Item 3'));
theTemplates.add(new selectOption('4', 'Item 4'));
return theTemplates;
}

}

 

 

 

This was selected as the best answer
mkdjnsmkdjns

I think that makes sense. Looks like you're forcing the page to handle the parameters directly as opposed to making the controller handle it. I'll give your changes a shot on the next one of these I have to do.

 

Thanks for taking a look.