3

Is it possible to exclude a visualforce page that will be used for rendering a PDF from a managed package?

The reason is, we have a standard PDF layout, but we want to give our clients the possibility in changing the layout of the pdf, in order to create their own branded version of the pdf.

I was thinking about, not including the pdf in a package, not including the button on the page layout that generates the pdf either (which generates the pdf, and adds it as an attachment to the SObject), and just create an unmanaged "support" package that includes the default pdf, and the button (the button calls a webservice method via javascript).

But then comes the problems i'm thinking about.

  • In the apex webservice method, I reference the page via Page.PDFPageName, but since the page isn't included in the package, i'm not sure if the deployment would fail.
  • Some use cases require the pdf to be generated from code (in some cases, when the sobject gets made, the pdf gets autogenerated)

How would one start of implementing this, (if its possible at all)

2
  • 2
    FinancialForce guys like @AndrewFawcett or PhilHawthorn will know best, poke them ;) I had to modify one of their managed invoice pdf templates. I could not modify the original but I was encouraged to clone the VF page, change some namespaces of custom components used and I was good to go. They exposed a custom setting where I could say "use my page, not the default one" and their managed package code was reading it from the setting. Don't have any details from the other side of the fence though ;)
    – eyescream
    Feb 6, 2013 at 10:31
  • you probably already know this... new PageReference('/apex/ns__PDFPageName') is functionally identical to Page.PDFPageName, but removes the compile-time dependency - one could use that to decouple the page from the apex webservice package Feb 6, 2013 at 21:52

2 Answers 2

7

The way we did this in FinancialForce (as mentioned by eyescream above) was to include the VF page in the managed package, but ensure that the controller and all methods the page called by the page (same with any components you use) were marked as global. So the button defaults to using that VF page, all good.

When the customer wants to override it they clone the page (copy + paste the contents), change the 'c:' references to the package namespace, eg. 'myns:' and save. The page should work as before. Then by having a custom setting defined to override the print VF page to be used we have code behind our print button that checks if there's a value, if there is it constructs the PageReference to redirect to from that (passing in the id of the record to print, so no using Page.PDFPageName), otherwise it uses the managed package version (Page.PDFPageName in your case). So something like this in the controller:

global PageReference getPrintPage()
{
    Id invoiceId = stdController.getId();
    PageReference printPage = Page.PDFPageName;      

    String customPageUrl = MySettings__c.getInstance().CustomPrintPage__c;
    if( !String.isBlank( customPageUrl ) )
    {
        printPage = new PageReference( customPageUrl );
    }

    printPage.getParameters().put( 'id', invoiceId );

    return printPage.setRedirect( true );
}

Your code to attach the PDF to the record (or in our case mark a document as 'Printed') lives in the controller so will still be called by the overridden page since it's using the same controller behind the scenes.

4
  • 2
    Wouldn't the visualforce page ID change after deployment?
    – pjcarly
    Feb 6, 2013 at 13:37
  • 1
    Ah, I meant the record Id so have added a code snippet to explain. The custom setting would hold a URL to the custom VF page. Feb 6, 2013 at 14:08
  • In short: you don't have to use Page.somePage but can instead just use new PageReference('/apex/somePage') to evaluate it at runtime. Feb 8, 2013 at 0:47
  • Yeah, I tend to prefer to use Page.somePage to force the dependency and stop me from accidentally renaming or removing the 'default' page from the package. Old habits and all that! Feb 8, 2013 at 12:10
3

Yep, it's possible! If you (the packagor) want to handle the injection, leaving the layout to the client, you can do this with an apex:composition by parameterizing the template:

  1. ns__ManagedComposition.page (note the default template fallback)

    <apex:page renderas="pdf">
      <apex:composition template="{!BLANKVALUE($CurrentPage.parameters.template, $Page.ManagedTemplate)}">
        <apex:define name="data">herp derp</apex:define>
      </apex:composition>
    </apex:page>
    
  2. ns__ManagedTemplate.page (up to you)

  3. CustomTemplate.page

    <apex:page>
      <apex:sectionHeader title="Custom header" />
        <apex:insert name="data" />
      <apex:sectionHeader title="Custom footer" />
    </apex:page>
    

And load the page thusly: /apex/ns__ManagedComposition?template=CustomTemplate

Further, if you want that inside out where they (the client) handle the injection, use Visualforce Dynamic Bindings:

  1. PdfComposition.page

    <apex:page>
      <apex:composition template="{!$Page['ns__PdfTemplate']}">
        <apex:define name="content">Herp derp</apex:define>
      </apex:composition>
    </apex:page>
    
  2. ns__PdfTemplate.page

    <apex:page renderas="pdf">
      <apex:sectionHeader title="[Inject their logo]" />
      <apex:insert name="logo" />
    
      <apex:sectionHeader title="[Inject their content]" />
      <apex:insert name="content" />
    </apex:page>
    

This way you can deploy unmanaged example templates without creating a cross-package dependency.

cross-package composition

4
  • 2
    Looks interesting Herp, will check this out Feb 6, 2013 at 13:21
  • 1
    This does look interesting, and is probably the ideal solution when jou just want to give the client the possibility to change logos, footers, headers, ... but can you give the client a complete blank slate this way? in the meaning of if you define a page we will use yours, else, use this default page we constructed?
    – pjcarly
    Feb 7, 2013 at 10:35
  • 1
    @JohnConners globalization of the controller and components is the blankest slate. Maybe combine his powerful approach with the proposed custom setting and a dumb wrapper which performs the switch: eg a ManagedWrapper.page: containing <apex:page><apex:include pageName="{!BLANKVALUE($Setup.TemplateOverrides__c.CustomTemplate__c, $Page.ManagedTemplate)}" /></apex:page>. Feb 7, 2013 at 11:13
  • That way ManagedTemplate.page and CustomTemplate.page both use ManagedGlobalController.cls but the wrapper+inclusion ensures the page handle remains consistent to everyone who uses it; for attachment to links, custom buttons, API invocation of getContent() etc Feb 7, 2013 at 11:15

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .