+ Start a Discussion
Jon Mountjoy_Jon Mountjoy_ 

PDF generation from VF fails if button has rendered attribute

I have a Visualforce page that renders as PDF (depending on an incoming parameter) when a button is pushed.
However, if that button has a "rendered" attribute, then the PDF fails to render for some reason.  (I don't want the button rendered if I'm producing a PDF, as I don't want to see it in the PDF). Instead, the page simply refreshes.  This seems a little odd - any idea what's up?

Here' s the page:
<apex:page renderAs="{!chooserender}" controller="MyController" > 
  <apex:pageBlock title="Some Page Block">
   <apex:pageBlockSection title="Section 1"> Text </apex:pageBlockSection>
   <apex:pageBlockSection title="Section 2"> Text </apex:pageBlockSection>
  </apex:pageBlock>
  
  <apex:form >
  <apex:commandLink rendered="{!$CurrentPage.parameters.p == null}"  value="PDF" action="{!deliverAsPDF}" target="_blank"></apex:commandLink>
  </apex:form>

</apex:page>

 Here's the controller:
public class MyController {

  public String getChooserender() {
     if (ApexPages.currentPage().getParameters().get('p') != null)
        return 'pdf';
        else
      return null;
  }

  public PageReference deliverAsPDF() {
      PageReference pdf =  Page.foo;
        pdf.getParameters().put('p','p');
        return pdf;
  }

}

I've blogged about this (sans problem) here.
 
I know I can solve my problem in a different way (style sheets come to mind), but I'd really like to figure out why, when a button has a rendered='false', it no longer generates a PDF. (I mean, I've already pushed it, and an action is occuring, so whether the button itself is present or not in the result shouldn't matter?)

Thanks!




Best Answer chosen by Admin (Salesforce Developers) 
dchasmandchasman
There is a subtle interaction going on here - when action methods return a page reference with out specifying a redirect that means redisplay the the current controller state using the returned page which is handled in a very specific way that is not playing nicely with a change in renderAs combined with targeting to a new window. This is something we'll look into a bit more but a redirect or even not using an action method based approach would actually be a better option - minors mods (blue is the required change but I also switched to using apex property syntax too) to your original controller will handle this:

Code:
public class MyController {
public PageReference deliverAsPDF() {
PageReference pdf = Page.foo;
pdf.getParameters().put('p','p');
pdf.setRedirect(true);
return pdf;
}

public String chooseRender {
get { return (ApexPages.currentPage().getParameters().get('p') != null) ? 'pdf' : null; }
}
}

 With that said I would tend to simplify this even further and remove the action method all together because no decision or other logic that requires code is being made at the time of clicking - this can then be distilled to something that does not need a controller or extension, <apex:form> etc:

Code:
<apex:page renderAs="{!if($CurrentPage.parameters.p == null, null, 'pdf')}"> 
    <apex:pageBlock title="Some Page Block">
        <apex:pageBlockSection title="Section 1"> Text </apex:pageBlockSection>
        <apex:pageBlockSection title="Section 2"> Text </apex:pageBlockSection>
    </apex:pageBlock>
    
    <apex:outputLink rendered="{!$CurrentPage.parameters.p == null}" value="/apex/foo?p=true" target="_blank">PDF</apex:outputLink>
</apex:page>

 

All Answers

dchasmandchasman
There is a subtle interaction going on here - when action methods return a page reference with out specifying a redirect that means redisplay the the current controller state using the returned page which is handled in a very specific way that is not playing nicely with a change in renderAs combined with targeting to a new window. This is something we'll look into a bit more but a redirect or even not using an action method based approach would actually be a better option - minors mods (blue is the required change but I also switched to using apex property syntax too) to your original controller will handle this:

Code:
public class MyController {
public PageReference deliverAsPDF() {
PageReference pdf = Page.foo;
pdf.getParameters().put('p','p');
pdf.setRedirect(true);
return pdf;
}

public String chooseRender {
get { return (ApexPages.currentPage().getParameters().get('p') != null) ? 'pdf' : null; }
}
}

 With that said I would tend to simplify this even further and remove the action method all together because no decision or other logic that requires code is being made at the time of clicking - this can then be distilled to something that does not need a controller or extension, <apex:form> etc:

Code:
<apex:page renderAs="{!if($CurrentPage.parameters.p == null, null, 'pdf')}"> 
    <apex:pageBlock title="Some Page Block">
        <apex:pageBlockSection title="Section 1"> Text </apex:pageBlockSection>
        <apex:pageBlockSection title="Section 2"> Text </apex:pageBlockSection>
    </apex:pageBlock>
    
    <apex:outputLink rendered="{!$CurrentPage.parameters.p == null}" value="/apex/foo?p=true" target="_blank">PDF</apex:outputLink>
</apex:page>

 

This was selected as the best answer
Jon Mountjoy_Jon Mountjoy_
Thanks Doug! I think I understand your first sentence (took a few reads :-) ) - I love that new getter syntax, and of course your solution without the controller is even prettier!
Fiona LuiFiona Lui
I got the error "Time limit exceeded,Your request exceeded the time limit for processing. " in generating PDF file. Is there any way to set the time limit to a larger value?
NPNP

What about , if we had to export it to excel file instead of using the pdf. 

 

I have a report in VF page, now I want the same page to be export to Excel.  I do not want to write the same code again and just  a new attribute to the page tag: "ContentType": 

 

Thanks