+ Start a Discussion
PaulATPimptastiPaulATPimptasti 

Improving the Performance of Visualforce AJAX Operations

Hi all,

 

I have an interesting question.  Has anyone found a way of improving speed of the AJAX requests contained within Visualforce pages?

 

We have built a large number of VF pages and it takes between one a three seconds for the action methods contained within links, buttons and action support tags to complete and to re-render the page.

 

This normally isn't a problem on single row input forms.  However we have started to use multi line input forms for updating and editing large quantities of data.  This means that we have to wait for the action to complete before changing the value in the subsequent fields.

 

Maybe I'm just being impatient as I'm relatively new to AJAX but it seems to me that other AJAX sites I have used respond much quicker. 

 

It would be interesting to hear your thoughts / comments. 

Best Answer chosen by Admin (Salesforce Developers) 
ThomasTTThomasTT

Hi PaulATPimptasti,

 

Wow... that's a hevay code... select list in each row... that must take long...

 

Here is my sample code.

 

http://ss-developer-edition.na6.force.com/Prototypes 

 Multi asynchronous calls may fail & Multi asynchronous calls may fail (solution)

 

 

<apex:page controller="MultiThreadTestController">
<apex:form >
<apex:pageBlock id="pageBlock">
<apex:commandButton value="refresh" action="{!refresh}"

rerender="pageBlock" status="status_refresh"/>&nbsp;&nbsp;&nbsp;
<apex:actionStatus id="status_refresh" startText="refreshing..."/>
<br/><br/>
<apex:pageBlockTable value="{!Items}" var="item">
<apex:column >
<apex:facet name="header">Button</apex:facet>
<apex:commandButton value="Update" action="{!updateStatus}" reRender="status">
<apex:param name="selectedItem" value="{!item.name}" assignTo="{!selectedItem}"/>
</apex:commandButton>
</apex:column>
<apex:column >
<apex:facet name="header">Status</apex:facet>
<apex:outputText value="{!item.status}" id="status"/>
</apex:column>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:page>

 

 As you see, if you specify a column with the id, it means "THE ROW", so user can still work on - like entering values in other rows, but no asynchronous call.

 

I exposed (don't do bad thing to it...) my demo as Site application.  In "Multi asynchronous calls may fail", if you click buttons very fast, you can change status to the current time, but once you refresh it by clicking on "Refresh" coustom button, you'll see some values will be lost. In the solusion demo, I disabled all buttons during a call, but if each row has input fields, user can still enter values in other rows.

 

 

So, you can basically quit using the YahooUI modal mask (but it's still a good solution. I'm also using it... but using Yahoo UI is a better idea than doing it by myself. Thank you for your tips!) .

 

However, as I mentioned, anyway you need to prevent from user's "another asynchronous call" like changing select list or any other asynch call for the table. It seems that your only asynch call is the select list... at the end of the day, my solution may not help you so much... 

 

ThomasTT

 

Message Edited by ThomasTT on 09-22-2009 10:49 AM
Message Edited by ThomasTT on 09-22-2009 10:54 AM

All Answers

ThomasTTThomasTT

Surely interesting issue. You used the word "row" "line" so that I assume you're talking about table (pageBlockTable or dataTable).

 

Just in case you aren't talking about table, you can re-render only small area so that the other parts are still available to users and they can work on those parts without waiting 1 asynchronous call.

 

Then, go back to Table. You can re-render only 1 line/row, actually the row which the user is working on. reRender atttribute accept component id, which relatively points component against component structure. If you have column "name" and you have a button in each line/row and the reRender atttribute is "name", then it means "name" column in THE ROW. So, user can work on other line/row during the asyncronous call.

 

HOWEVER!!

 

Conclusion is, they can't work on multiple-line/row at the same time...

When asynchronous call is made, it changes the state of controller - actually, the array/list for the table - and the state is stored in your browser, called "View State". Even though different line/row is changed, each asynchronous call tries to change the value of the array/list, and tries to update the View State in your controller... and they conflict each other because they are trying to change the same object.

 

If user operate very quickly enough to change more than 1 row/line during 1 asynchronous call, either of result will be lost. The screen shows ok, but once user refresh the page, they will realize that one of the operation didn't work (because page contains the value, but the controller already lost either of changes).

 

At the end of the day, I had to disable all buttons in each row during any asynchronous call...

 

 

If you really improve performance, you consider to do it with javascript... but it's a long and tough way. It depends on the situation and requirement, but as I see what you're trying to do, doing it with javascript is not recommended... I would say, you already doing too much on 1 page.

 

ThomasTT

 

P.S.

To improve performance of each call, reducing size of View State would contribute it, but I assume you already do many things on the page, so it would be also difficult...

PaulATPimptastiPaulATPimptasti

Hi Thomas

 

You are right, by line / row I mean a row in a pageBlockTable.  The View State is an interesting one as the controller I am using is quite large as there is a lot of shared logic between the the pages in the app, and I felt it was easier to add all of the server side code to the same class.  Might look at splitting it out if necessary.

 

Your point about being able to re-render a row is very interesting and I was not aware that was possible nor do I know where to start.  

When using the pageBlockTable I have not been able to find a way of working out the current row position, which I'm sure if I could would make the reRendering of a single row possible.

 

My scenarios is for matching payments to invoices.  When a user selects the invoice from a Select List, a method is called to calculate the outstanding amount through a reRender of the whole table.  As the entire table is being reRendered if a user changes a value in another row before the asynchronous call completes any changes are lost.  It would be great if there was a way of reRendering a single row.  Relevant code is below:

 

As a temporary work around I have used the Yahoo YUI to put a 'Loading... Please Wait' message across the whole screen.

 

Any pointers you can provide would be great. 

 

Thanks

 

Paul 

<apex:form > <apex:pageBlock title="Unassigned Payments" id="UnassignedPayments"> <apex:actionStatus id="UnassignedPaymentsPageStatus"> <apex:facet name="start"><apex:image value="{!$Resource.Processing}"/></apex:facet> <apex:facet name="stop"><apex:image value="{!$Resource.Blank}"/></apex:facet> </apex:actionStatus> <apex:pageMessages /> <apex:pageBlockButtons > <apex:commandButton id="ClearInvoicePayments" action="{!ClearInvoicePayments}" value="Clear Payments" status="PageStatus" reRender="UnassignedPayments" status="UnassignedPaymentsPageStatus" onclick="YAHOO.foration.com.showWaiting();" onComplete="YAHOO.foration.com.hideWaiting();"/> <apex:commandButton action="{!SaveMatchedPayments}" value="Save Assigned Payments" rerender="UnassignedPayments,OutstandingInvoices" status="UnassignedPaymentsPageStatus" onclick="YAHOO.foration.com.showWaiting();" onComplete="YAHOO.foration.com.hideWaiting();"/> </apex:pageBlockButtons> <apex:pageBlockTable value="{!BankStatement}" var="pay"> <apex:column headerValue="Related Invoice"> <apex:selectList value="{!pay.PaymentRecord.Related_Invoice__c}" multiselect="false" size="1" rendered="{!pay.PaymentRecord.Id == null}"> <apex:selectOptions value="{!ListOfInvoices}"/> <apex:actionSupport event="onchange" rerender="UnassignedPayments" status="PageStatus" action="{!AddPaymentToInvoice}" onsubmit="YAHOO.foration.com.showWaiting();" onComplete="YAHOO.foration.com.hideWaiting();"/> </apex:selectList> <apex:outputField value="{!pay.PaymentRecord.Related_Invoice__c}" rendered="{!NOT(pay.PaymentRecord.Id == null)}"/> </apex:column> <apex:column headerValue="Payment Date"> <apex:outputField value="{!pay.PaymentRecord.Payment_Date__c}" /> </apex:column> <apex:column headerValue="Payment Description"> <apex:outputField value="{!pay.PaymentRecord.Payment_Description__c}" /> </apex:column> <apex:column headerValue="Payment Amount"> <apex:outputField value="{!pay.PaymentRecord.Payment_Amount__c}" /> </apex:column> <apex:column headerValue="Payment Account"> <apex:outputField value="{!pay.PaymentRecord.Payment_Account__c}" /> </apex:column> <apex:column headerValue="Outstanding"> <apex:outputText rendered="{!pay.PaymentRecord.Related_Invoice__c == null}">£0.00</apex:outputText> <apex:outputPanel rendered="{!NOT(pay.PaymentRecord.Related_Invoice__c == null)}"> <c:InPageCalculation invoices="{!OutstandingInvoices}" ammount="{!pay.PaymentRecord.Payment_Amount__c}" InvId="{!pay.PaymentRecord.Related_Invoice__c}" CalcType="Payments"/> </apex:outputPanel> </apex:column> <apex:column > <apex:commandLink value="Create Invoice" action="{!refreshQuickCreateForm}" rerender="QuickCreateForm" status="UnassignedPaymentsPageStatus" onComplete="YAHOO.foration.com.showMe('quickCreatePanel');" rendered="{!AND(pay.PaymentRecord.Related_Invoice__c == null,pay.PaymentRecord.Id == null)}" onclick="YAHOO.foration.com.showWaiting();"> <apex:param name="payInt" assignTo="{!paymentIndex}" value="{!pay.paymentIndex}"/> </apex:commandLink> <apex:commandLink value="Add to Invoice" action="{!refreshQuickCreateForm}" rerender="QuickCreateForm" status="UnassignedPaymentsPageStatus" onComplete="YAHOO.foration.com.showMe('quickCreatePanel');" rendered="{!AND(NOT(pay.PaymentRecord.Related_Invoice__c == null),pay.PaymentRecord.Id == null)}" onclick="YAHOO.foration.com.showWaiting();"> <apex:param name="payInt" assignTo="{!paymentIndex}" value="{!pay.paymentIndex}"/> </apex:commandLink> </apex:column> <apex:column > <apex:commandLink value="Split Payment" action="{!splitPayments}" rerender="SplitPayments" status="UnassignedPaymentsPageStatus" onComplete="YAHOO.foration.com.showMe('splitPaymentPanel');" rendered="{!AND(pay.PaymentRecord.Related_Invoice__c == null,pay.PaymentRecord.Id == null)}" onclick="YAHOO.foration.com.showWaiting();"> <apex:param name="payInt" assignTo="{!paymentIndex}" value="{!pay.paymentIndex}"/> </apex:commandLink> </apex:column> </apex:pageBlockTable> </apex:pageBlock> </apex:form>

 

Message Edited by PaulATPimptasti on 09-22-2009 07:32 AM
ThomasTTThomasTT

Hi PaulATPimptasti,

 

Wow... that's a hevay code... select list in each row... that must take long...

 

Here is my sample code.

 

http://ss-developer-edition.na6.force.com/Prototypes 

 Multi asynchronous calls may fail & Multi asynchronous calls may fail (solution)

 

 

<apex:page controller="MultiThreadTestController">
<apex:form >
<apex:pageBlock id="pageBlock">
<apex:commandButton value="refresh" action="{!refresh}"

rerender="pageBlock" status="status_refresh"/>&nbsp;&nbsp;&nbsp;
<apex:actionStatus id="status_refresh" startText="refreshing..."/>
<br/><br/>
<apex:pageBlockTable value="{!Items}" var="item">
<apex:column >
<apex:facet name="header">Button</apex:facet>
<apex:commandButton value="Update" action="{!updateStatus}" reRender="status">
<apex:param name="selectedItem" value="{!item.name}" assignTo="{!selectedItem}"/>
</apex:commandButton>
</apex:column>
<apex:column >
<apex:facet name="header">Status</apex:facet>
<apex:outputText value="{!item.status}" id="status"/>
</apex:column>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:page>

 

 As you see, if you specify a column with the id, it means "THE ROW", so user can still work on - like entering values in other rows, but no asynchronous call.

 

I exposed (don't do bad thing to it...) my demo as Site application.  In "Multi asynchronous calls may fail", if you click buttons very fast, you can change status to the current time, but once you refresh it by clicking on "Refresh" coustom button, you'll see some values will be lost. In the solusion demo, I disabled all buttons during a call, but if each row has input fields, user can still enter values in other rows.

 

 

So, you can basically quit using the YahooUI modal mask (but it's still a good solution. I'm also using it... but using Yahoo UI is a better idea than doing it by myself. Thank you for your tips!) .

 

However, as I mentioned, anyway you need to prevent from user's "another asynchronous call" like changing select list or any other asynch call for the table. It seems that your only asynch call is the select list... at the end of the day, my solution may not help you so much... 

 

ThomasTT

 

Message Edited by ThomasTT on 09-22-2009 10:49 AM
Message Edited by ThomasTT on 09-22-2009 10:54 AM
This was selected as the best answer
PaulATPimptastiPaulATPimptasti

Hi Thomas,

 

Your demo is lightening fast!  There is certainly some room for improvement with my code.  Thanks for showing those prototypes - very helpful indeed.

 

I'm going to edit my code and see whether I can produce the same results.

 

Will report back.

 

Thanks again for your tips.

 

Paul 

PaulATPimptastiPaulATPimptasti

Hi Thomas,

 

That was great, I have edited the code to rebuild the list of Select Options after certain activities as opposed to each time the row is re-rendered.  Also I have updated the code to only re-render the specific row and this is now allowing me to move between rows without having to wait for the a-sync operation to complete.

 

The speed at which the operations complete is no faster, so maybe that has something to do with my controller or the fact that I am still re-rendering other areas of the page.  I've posted the code below, if you have a chance and can provide any pointers that would be great, but please don't feel obliged to.

 

Thanks again.

 

Paul 

 

Code can be downloaded here.

Message Edited by PaulATPimptasti on 09-22-2009 08:56 AM
Message Edited by PaulATPimptasti on 09-22-2009 09:03 AM
ThomasTTThomasTT

I couldn't get the source code (not found error), but I don't think we can tune it up dramatically. Of course, what you mentioned above will improve performance, but I can't think of anything else...

 

I'm actually an performance specialist (on Java & infrastructure) and in my experience, performance tuning is about cost vs effect. You always have a room to improve (and never end), but the cost changes all the time.

 

As long as you want to do complicated things, and as long as you need to access to server to re-render, you can't dramatically reduce the complexity or amount of data with keeping your specification. Of course, if we tune it up with taking a long time, we can improve 0.1~0.5 second, but then what? If the number of rows get dobule, then the response may get double, too.

 

If you really need to improve the performance, you may think of other way to do the same thing - rather than select list in each row, like, each row uses the same select list.

 

One other possibility is, if you feel slow in sandboxes, production may be much faster (my colleague says 4 times faster).

 

And mind tricks work, too. If you can do something or show something during a call, even though it is not much useful, user feels it much faster. A pretty waiting icon makes user pay attension and it makes them feel it faster.

 

 

By the way, in my case, custom dependent picklists (4 picklist for 4 layers) got complained about the performance (1~2 sec for each picklist/layer). My solution was, loading all data in the first place (for the 1st layer load), and make the remaining picklist dynamic by javascript. If they can't wait 2 seconds, then they need that much dramatic changes... (and nobody complained that the 1st loading took 4 seconds, it's a mind trick)

 

ThomasTT

PaulATPimptastiPaulATPimptasti

Hi Thomas,

 

Yes, Dreamweaver decided not to upload the dependant files, have uploaded them so you will see what I'm up against.

 

I've tried the mind tricks, but as I'm the one who is using the page I can't be fooled so easily.

 

I have tried to load as much as I can into memory to help improve the performance times, but I'm just starting out in web app development and not too comfortable with integrating JavaScript with my code, but I think that's the next logical step.

 

Thanks for all your help so far - hopefully I can return the favour!

 

Cheers

 

Paul 

ThomasTTThomasTT

I hope I helped you some.

 

> hopefully I can return the favour!

Just one click is enough for me :)

 

ThomasTT

 

MaidyMaidy

Hi ThomasTT,

 

I am relatively new to Salesforce and have been asked to investigate and resolve the performance issue of one of our user groups using a custom VF page. 

 

The VF page collects a series of questions (survey) into a page on which the user can enter their answer options for it to populate the equivalent default issue/action information.

 

The problem is that each time they select an answer, it is taking too long to reload the page and it seems that the whole page is being refreshed instead of just the record.

 

I found you're post and was hoping you'll be able to provide the much need help. Thanks.

 

Below is a copy of the code:

 

 

<apex:page standardController="Checklist_Item__c" extensions="CollectChecklistItemsExtension" recordSetvar="ChecklistItems" sidebar="false">
    <apex:form > 
        <apex:pageBlock title="Edit Checklist Item Data" id="idPageBlock1" mode="edit">
            <apex:pageMessages /> 
            
            <apex:pageBlockButtons >
                <apex:commandButton value="Save" action="{!save}"/>
                <apex:commandButton value="Cancel" action="{!cancel}"/>
            </apex:pageBlockButtons>
            
            <apex:pageBlockTable value="{!ChecklistItems}" var="c" id="idPageBlockTable1">
                
                <apex:column value="{!c.Id}" rendered="false" />
                
                <apex:column value="{!c.Question_Category__c}" headerValue="Category" />                
                
                <apex:column value="{!c.Question__c}" /> 
                
                <apex:column headerValue="Answer">
                    <apex:outputPanel id="idAnswerOutputPanel" >
                        <apex:inputField value="{!c.Answer__c}">
                        <apex:actionSupport event="onchange" 
                                            rerender="idPageBlock1"
                                            status="idStatus"/>
                        </apex:inputField>
                        <apex:actionStatus startText="processing..." id="idStatus"/>
                    </apex:outputPanel>  
                </apex:column>

                <apex:column headerValue="Comment">
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Comments__c}" />
                </apex:column>

                <apex:column headerValue="Issue">
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Issue__c}" rendered="{!c.Answer__c == ''}"/>
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Issue__c}" rendered="{!c.Answer__c == 'Yes'}" />
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Default_NO_Issue__c}" rendered="{!c.Answer__c == 'No'}" />
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Default_In_Part_A_Issue__c}" rendered="{!c.Answer__c == 'In-Part A'}" />
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Default_In_Part_B_Issue__c}" rendered="{!c.Answer__c == 'In-Part B'}" />
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Default_In_Part_C_Issue__c}" rendered="{!c.Answer__c == 'In-Part C'}" />
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Issue__c}" rendered="{!c.Answer__c == 'N/A'}" />                 
                </apex:column>    
                                
                <apex:column headerValue="Action">
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Action__c}" rendered="{!c.Answer__c == ''}" />               
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Action__c}" rendered="{!c.Answer__c == 'Yes'}" />
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Default_NO_Action__c}" rendered="{!c.Answer__c == 'No'}" />
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Default_In_Part_A_Action__c}" rendered="{!c.Answer__c == 'In-Part A'}" />
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Default_In_Part_B_Action__c}" rendered="{!c.Answer__c == 'In-Part B'}" />
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Default_In_Part_C_Action__c}" rendered="{!c.Answer__c == 'In-Part C'}" />
                    <apex:InputTextArea rows="10" cols="40" value="{!c.Action__c}" rendered="{!c.Answer__c == 'N/A'}" />
                </apex:column>

            </apex:pageBlockTable>
        </apex:pageBlock>
    </apex:form>
</apex:page>