[SalesForce] How to download displayed data from VF page into an excel file without querying again

I have a VF page in which I am displaying data to users. The data is fetched from an external system. I wish to have a download button on the VF page so that users will be able to download the data in csv format.

On researching, I found out that I can redirect to a VF page that defines contentType and the file will get downloaded automatically. The problem is that another call will need to be made on that VF page. Is there a way by which I can download the data without making another call to fetch the data?

Example Code

Controller Code

public class ExportContactsDemoController {

    public List<Contact> lstContact {set;get;}
   
    public ExportContactsDemoController(){
        lstContact = [Select Id, Name, Email, Phone From Contact Limit 10];   
    }
    
}

ShowContactsDemo – This VF page displays data to the users:

<apex:page controller="ExportContactsDemoController" cache="true">
    <apex:form id="f1">
        <head>
            <script>
            function ExportToExcel(){
                confirm('Are you sure you want to export an excel ?');
                window.open('/apex/ExportContactsDemo'); 
            }
            </script>
        </head>
        <body>
            <apex:pageBlock title="10 Contacts">
                <apex:commandButton value="Export to Excel" onclick="ExportToExcel();" oncomplete=""/>
                <apex:pageBlockTable value="{!lstContact}" var="objContact">
                    <apex:column value="{!objContact.Name}"/>
                    <apex:column value="{!objContact.Email}"/>
                    <apex:column value="{!objContact.Phone}"/>
                </apex:pageBlockTable>
            </apex:pageBlock>
        </body>
    </apex:form>
</apex:page>

ExportContactsDemo – This VF page uses the same controller as previous page and downloads the data

<apex:page controller="ExportContactsDemoController" contentType="application/vnd.ms-excel#ContactsData.xls">
    <apex:pageBlock >
        <apex:pageBlockTable value="{!lstContact}" var="objContact">
            <apex:column value="{!objContact.Name}"/>
            <apex:column value="{!objContact.Email}"/>
            <apex:column value="{!objContact.Phone}"/>
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>

In this example, the query present inside the controller is fired twice. Is there a way I can use the data fetched by the first query and download it without querying again?

Best Answer

Yes, you can avoid the second query, but you won't be able to open it in a new window. To do this, redirect from the first page to the second through an apex: component that can redirect via an action. When you do this, Visualforce will pass the controllers between pages and retain the View State of the page, thus not calling the controller again.

I've written a very simple example you can follow along with:

commonController.apxc

public class commonController {
    public String helloWorld { get; set; }
    public commonController() {
        // Do logic only if initializing from scratch
        if(helloWorld == null) {
            helloWorld = 'Hello World';
        }
    }
}

page1.vfp

<apex:page controller="commonController">
    {!helloWorld}
    <br />
    <apex:form>
        <apex:commandButton action="{!URLFOR($Page.page2)}" value="View as PDF" />
    </apex:form>
</apex:page>

page2.vfp

<apex:page controller="commonController" renderAs="pdf">
    {!helloWorld}
</apex:page>

I've intentionally made this a PDF so it can be as simple as possible, but you can do this sort of redirect easily for any type of content type that's supported.

You could also use a merge field for renderAs, simply changing from HTML to Excel when you change a variable, etc, although I agree that keeping the pages separate is usually a better idea.

A slightly less popular approach is to use a temporary record or file somewhere in Saleforce (e.g. a ContentVersion), which allows transfer of data that would otherwise exceed the view state of 135kb.

Related Topic