14

I've a Visualforce page that holds a large amount of data.

I've used <apex:page readOnly="true"> to avoid the viewstate limit of 135 KB. However, I use a lot of data binding that makes the viewstate heavy.

I've recently hit a new limit "Response Size of 15 MB". I believe this is the response size I get from page onload i the process of "Apex ->HTML conversion".

Any suggestion on how to proceed will help. I'm trying to reduce the binding but still looking if there are other options available which I'm not aware of.

Visualforce Error

  • Is hard to believe that a browser will render a page bigger than 15MB. Consider pagination if you are listing data. – Martin Borthiry Jun 12 '13 at 11:27
  • The data is large and it is necessary that I show all of them. As @Andrew suggested, the size is blown up because of extra HTML tags that gets rendered when using the apex tags. – Vignesh Jun 12 '13 at 11:35
  • 1
    Just for reference for those who try it, it's not apex:page="readonly" as in the original text. The syntax is <apex:page readOnly="true". – Adrian Carr Apr 16 '14 at 18:07
23

This error relates to the size of the HTML being produced by your Visualforce page and transmitted to the client browser. It is documented here.

The maximum response size from a Visualforce page request must be below 15 MB.

Reducing the bindings in your page will not improve this, unless it is reducing the HTML content your page is outputting. Though its always good to keep bindings simple when doing repeated output.

  • HTML elements vs VF elements. Study the HTML page DOM of the page once loaded (with less data) to see if your outputting any unnecessary HTML tags. Keep in mind the VF components output a lot of HTML tags, so consider what value in a read-only / report use case these are adding. Maybe best to use direct bindings to your state with regular HTML elements, e.g. < span >{!MyData}< /span >, < table > etc instead of the VF components. Although your not hitting a view state issue, this answer also goes into a little more detail on using HTML vs VF components and the benifits, How to reduce a large internal view state / what is in the internal view state?.
  • Inline Style Usage. If your using some styling on HTML elements your repeating often, you can put these at the top of the page in a style element or in separate CSS file.
  • Dynamic Loading. You might also want to consider using JavaScript to perform some client rendering and maybe build your content up dynamically (an advanced option is to load the data as the user scrolls).
  • Pagination. Depending on your requirements for this page (report vs interactive browse), you may also want to consider using a StandardSetController or custom pagination implementation to let the user page through the data, much like the Salesforce List View UI.

This is by no means an exhaustive list, there are a few other options to be considered around the types of those above, it kind of depends on what design goals are for the page you're outputting. Hope this helps!

  • 1
    If he is listing data, pagination could be an answer – Martin Borthiry Jun 12 '13 at 11:23
  • 1
    Yes indeed, some form of dynamic loading either client or server side, I was assuming it was some kind of report and thus often users prefer to see all the data (eventually) on one page. Since we are not 100% sure of the use case though, lets include it in the answer! Thanks! :) – Andrew Fawcett Jun 12 '13 at 13:51
  • Now, your answer deserves a honor medal! :) – Martin Borthiry Jun 12 '13 at 14:28
  • Yes, Its a report that's run across a set of users vs data over a period of time! @Martin : That is the reason why I said "The data is large and it is necessary that I show all of them". BTW, terrific answer! Will mark as answer, once I complete the changes! Thanks again! – Vignesh Jun 12 '13 at 16:30
  • Your most welcome Vignesh! :-) – Andrew Fawcett Jun 12 '13 at 16:41
6

To add to Andrew's answer, in the JavaScript vein you could use JavaScript Remoting and call it from the page and use the JSON response along with a simple HTML template contained in the page markup in order to let you retrieve a massive amount of data without passing the markup in the normal page response.

The example below is a page which loads Note records via script and uses a client-side template to render it into the page. It'll load thousands of records in (typically) 2-3 seconds with zero impact to viewstate and it does not put any of the data in the page response.

Controller Class

public with sharing class AccountRemoteActionController {

    ApexPages.StandardController acctController;
    public Account theAccount { get; set; }

    public AccountRemoteActionController(ApexPages.StandardController cnt) {
        this.acctController = cnt;
        this.theAccount = (Account)this.acctController.getRecord();
    }

    @RemoteAction
    public static List<Note> getNotesByAccountId(String accountId) {
        List<Note> acctNotes = [SELECT Id
                                        , Title
                                        , Body
                                        , OwnerId
                                        , CreatedDate
                                        , CreatedById
                                        , LastModifiedDate
                                        , LastModifiedById  
                                        FROM Note 
                                        WHERE ParentId =: accountId
                                        ORDER BY CreatedDate DESC];
        return acctNotes;
    }

}

VisualForce Page

<apex:page standardController="Account" extensions="AccountRemoteActionController" >

    <apex:includeScript value="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" />   

    <script type="text/javascript">
        var _tmplCache = {};
        function parseTemplate(str, data) {
            /// <summary>
            /// Client side template parser that uses <#= #> and <# code #> expressions.
            /// and # # code blocks for template expansion.
            /// NOTE: chokes on single quotes in the document in some situations
            ///       use &amp;rsquo; for literals in text and avoid any single quote
            ///       attribute delimiters.
            /// </summary>    
            /// <param name="str" type="string">The text of the template to expand</param>    
            /// <param name="data" type="var">
            /// Any data that is to be merged. Pass an object and
            /// that object's properties are visible as variables.
            /// </param>    
            /// <returns type="string" />  
            var err = "";
            try {
                var func = _tmplCache[str];
                if (!func) {
                    var strFunc = "var p=[],print=function(){p.push.apply(p,arguments);};" +
                        "with(obj){p.push('" +
                        str.replace(/[\r\t\n]/g, " ")
                           .replace(/'(?=[^#]*#>)/g, "\t")
                           .split("'").join("\\'")
                           .split("\t").join("'")
                           .replace(/<#=(.+?)#>/g, "',$1,'")
                           .split("<#").join("');")
                           .split("#>").join("p.push('")
                           + "');}return p.join('');";

                    //alert(strFunc);
                    func = new Function("obj", strFunc);
                    _tmplCache[str] = func;
                }
                return func(data);
            } catch (e) { err = e.message; }
            return "< # ERROR: " + err + " # >";
        }
    </script>
    <script type="text/javascript">
        var PageVars = {
            AccountId: '{!JSENCODE(theAccount.Id)}'
        };
        var PageFunctions = {
            ajaxStart: function (button) {
                var $img = jQuery('<img class="ajaxImg" src="/img/loading.gif">');
                jQuery(button).after($img);
            },
            ajaxStop: function () {
                jQuery('img.ajaxImg').remove();
            },
            getNotes: function (button) {
                this.ajaxStart(button);
                AccountRemoteActionController.getNotesByAccountId(PageVars.AccountId, function (result, event) {
                    if (event.status) {
                        var $container = jQuery("#noteBlock .pbBody");
                        $container.empty();
                        // get the html from the template markup and send the results of the remote action into it
                        var html = parseTemplate($("#NoteTemplate").html(), { notes: result });
                        // append the generated html to the noteblock in the page
                        $container.append(html);
                        // style the first subheader with the class of 'first' so that it looks dapper
                        jQuery("#noteBlock .pbSubheader:first").addClass('first');                  
                    } else if (event.type === 'exception') {
                        document.getElementById("responseErrors").innerHTML = event.message;
                    } else {
                        document.getElementById("responseErrors").innerHTML = event.message;
                    }
                    PageFunctions.ajaxStop();
                }, { escape: true });
            },

            formatDate: function (theDate) {
                var d = new Date(theDate);
                var curr_date = d.getDate();
                var curr_month = d.getMonth();
                curr_month++;
                var curr_year = d.getFullYear();
                return curr_month + "/" + curr_date + "/" + curr_year;
            }
        };

    </script>

    <div id="responseErrors"></div>
    <apex:sectionHeader title="Account Notes" subtitle="{!account.Name}"/>
    <apex:detail subject="{!account.Id}" relatedList="false" title="false"/>

    <!-- Markup that resembles a pageblock --> 
    <div class="apexp">
        <div class="individualPalette">
            <div class="accountBlock">
                <div class="bPageBlock apexDefaultPageBlock secondaryPalette" id="noteBlock">
                    <div class="pbHeader">
                        <table cellspacing="0" cellpadding="0" border="0">
                            <tbody>
                                <tr>
                                    <td class="pbTitle">
                                        <h2 class="mainTitle">
                                            Notes</h2>
                                    </td>
                                    <td class="pbButton">
                                        <button onclick="PageFunctions.getNotes(this);" class="btn">Load Notes with @RemoteAction</button>                                        
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                    <div class="pbBody">
                       <!-- Notes Body result here -->
                    </div>
                    <div class="pbFooter secondaryPalette">
                        <div class="bg">
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Markup is within a script tag with a type of text/html which prevents the browser from rendering it or executing it -->
    <script id="NoteTemplate" type="text/html">
    <# for(var i=0; i < notes.length; i++)     
        {         
          var note = notes[i]; 
     #>
    <div class="collapsibleWrapper">
        <div class="pbSubheader tertiaryPalette">
           <h3> <#= note.Title #> (<#= PageFunctions.formatDate(note.CreatedDate) #>) </h3>
        </div>
        <div class="pbSubsection note">
            <table cellspacing="0" cellpadding="0" border="0" class="detailList">
                <tbody>
                    <tr>
                        <td class="labelCol">
                            Note Owner
                        </td>
                        <td colspan="3" class="data2Col">
                            <a href="/<#= note.OwnerId #>"><#= note.OwnerId #></a>
                        </td>
                    </tr>
                    <tr>
                        <td class="labelCol last">
                            Created By
                        </td>
                        <td class="dataCol col02 last">
                            <a href="/<#= note.CreatedById #>" class="CreatedBy"><#= note.CreatedById #></a>,
                            <span class="CreatedDate"><#= (new Date(note.CreatedDate)).toLocaleString() #></span>
                        </td>
                        <td class="labelCol last">
                            Modified By
                        </td>
                        <td class="dataCol last">
                            <a href="/<#= note.ModifiedById #>" class="ModifiedBy"><#= note.LastModifiedById #></a>,
                            <span class="ModifiedDate"><#= (new Date(note.LastModifiedDate)).toLocaleString() #></span>
                        </td>
                    </tr>
                    <tr>
                        <td class="labelCol">
                            Private
                        </td>
                        <td colspan="3" class="data2Col">
                            <img width="21" height="16" title="Not Checked" class="checkImg" alt="Not Checked"
                                src="/img/checkbox_unchecked.gif" />
                        </td>
                    </tr>
                    <tr>
                        <td class="labelCol">
                            Title
                        </td>
                        <td colspan="3" class="data2Col">
                            <#= note.Title #>
                        </td>
                    </tr>
                    <tr>
                        <td class="labelCol">
                            Body
                        </td>
                        <td colspan="3" class="data2Col">
                            <#= note.Body #>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>

    <# } #>
    </script>
</apex:page>
  • Mark, this is a pretty cool example. Thanks! Is there any way to display this as a repeater? – Carlos Jun 17 '15 at 20:54
  • You'd probably have to ask another question for us to get very far with it, but can you elaborate on displaying as a repeater? There's a 'repeat' behavior happening in the script block with the for(var i=0; i < notes.length; i++) over the data in the response from the remoteaction call. – Mark Pond Jun 17 '15 at 22:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.