+ Start a Discussion
TLFTLF 

Visualforce error if Ajax commandButton clicked too soon

Hi,

 

I've got a commercial Visualforce application that is deployed to clients via managed package. During testing in my developer edition and sandbox environments everything appears to work fine, yet we get numerous developer script exception notification emails from clients using the application. I tried to replicate the errors being reported, without much success until one day I managed to reproduce. The application has a number of pages that include apex:commandButton controls that use the "rerender" attribute to specify Ajax requests and partial page updates. The problems seem to occur when clients with slow internet connections click on one of the Ajax commandButtons before the page load has completed. The exceptions being raised are almost always coming from the <init> method of the Visualforce controller (the constructor). Typically, an ID of an SObject is passed to the page, and I'm retrieving an SObject via SOQL in the constructor. The errors seem to indicate that the constructor is being invoked but the page parameters are incomplete. So, this raises a few questions:

 

  • Has anyone else observed behavior like this?
  • Have you come up with a solution?
  • Why does clicking on a button that's supposed to do an Ajax submit result in invoking the constructor of the page controller? Shouldn't the controller already be instantiated at that point?

It seems like the most logical solution is to somehow disable the buttons until the page is completely loaded. I tried a few things to try to achieve this, but I've been unsuccessful.

  • I first tried adding javascript event handlers for the document.ready and window.load events. In document.ready, I disable input:submit buttons by adding the btnDisabled style and setting disabled=true. Then in window.load, re-enabled. This code worked, except that when the connection is slow, the page appears well before the document.ready event fires. During this time, the buttons remain clickable, and an error is generated (sometimes).
  • I tried adding a boolean field in my controller called "actionsEnabled". I added disabled="{!NOT(actionsEnabled)}" to the buttons. In the controller, I added an action method that set actionsEnabled=true and added this as the <apex:page> "action" method. This did not work either.

Not sure what else to try here. Any thoughts?

Best Answer chosen by Admin (Salesforce Developers) 
sfdcfoxsfdcfox

I've never personally encountered this, and I'd probably have to download a driver to simulate a dialup connection just so I'd have a chance to test it. However, I've got some theories.

 

Short educational bit.

 

When a user requests a Visualforce page, it goes through a particular cycle, roughly as this pseudo-code:

 

 

Start processing thread for this request
Check parameters
Check permissions
Validate and load view state
Call constructor if view state is empty
Call setters
Call action method(s)
Call getters
Serialize non-transient memory
Output page results
Terminate processing thread for this request

Early during the processing of the page's life cycle, the view state is checked, and if one exists, the constructor isn't called (as you observed). However, if no view state is present, then the constructor would be called.

 

 

Next educational bit.

 

When the page is output, the View State is the very last item output into the form, way down at the bottom of the code (only the Date Picker code and footer is after the form).

 

So, to answer your first question, it's conceivable that on an ultra-slow connection that the buttons may very well be rendered before they're operational (the JS library is already cached, so JS will work before the page loads anyway). Since the view state is at the far bottom of the page, it's quite possible on complicated VF layouts that there is a ton of code that is loaded and rendered before the view state is accessible.

 

Thus, calling an action at any point before the page is fully loaded can well result in the constructor being called. A large view state might be even worse, because if the browser is waiting on half of the view state to load, JS can still see the view state form parameter in its incomplete state. The behavior of a partial view state could be eliminated by the developers of salesforce.com if they emitted the name attribute after the value attribute, although this would still result in constructors being called when they shouldn't. They would also need to emit the view state first before any other form elements to prevent this condition.

 

I would have to guess that they made view state last so as to avoid the scenario of users clicking on buttons and links that would be missing parameters; they simply made the entire page invalid until it finishes loading.

 

Neither of your two solutions would precisely work because:

 

1) document.ready occurs before images and so on loads, but doesn't fire until the closing HTML tag (or end of stream, etc). There's still definitely a window in which a partially loaded document can be rendered, which would leave the buttons as accessible until the document finishes loading; the view state can still be accessed in that incomplete scenario I mentioned before.

 

2) The "action" method on the page is called during the "action" step of the pseudo-code above. It's not two entirely separate transactions, it's one transaction with the constructor plus one additional method being called. This means that the page won't have loaded fully before output (in other words, you're effectively doing what you were doing originally, but in a more elaborate manner).

 

So, here's some thoughts:

 

1) Use your second method, but do not add the action to the page, but instead use an actionFunction, and use window.onload to call this actionFunction, using a reRender that targets the form element.

 

2) Use a containing div with a "display: none" style, and remove that style from the div once document.onready or window.onload fires. This avoids a round trip, so should save 2-5 seconds for those dialup users.

 

I'm sure there's other designs you could go for, but I hope this helps out.

All Answers

sfdcfoxsfdcfox

I've never personally encountered this, and I'd probably have to download a driver to simulate a dialup connection just so I'd have a chance to test it. However, I've got some theories.

 

Short educational bit.

 

When a user requests a Visualforce page, it goes through a particular cycle, roughly as this pseudo-code:

 

 

Start processing thread for this request
Check parameters
Check permissions
Validate and load view state
Call constructor if view state is empty
Call setters
Call action method(s)
Call getters
Serialize non-transient memory
Output page results
Terminate processing thread for this request

Early during the processing of the page's life cycle, the view state is checked, and if one exists, the constructor isn't called (as you observed). However, if no view state is present, then the constructor would be called.

 

 

Next educational bit.

 

When the page is output, the View State is the very last item output into the form, way down at the bottom of the code (only the Date Picker code and footer is after the form).

 

So, to answer your first question, it's conceivable that on an ultra-slow connection that the buttons may very well be rendered before they're operational (the JS library is already cached, so JS will work before the page loads anyway). Since the view state is at the far bottom of the page, it's quite possible on complicated VF layouts that there is a ton of code that is loaded and rendered before the view state is accessible.

 

Thus, calling an action at any point before the page is fully loaded can well result in the constructor being called. A large view state might be even worse, because if the browser is waiting on half of the view state to load, JS can still see the view state form parameter in its incomplete state. The behavior of a partial view state could be eliminated by the developers of salesforce.com if they emitted the name attribute after the value attribute, although this would still result in constructors being called when they shouldn't. They would also need to emit the view state first before any other form elements to prevent this condition.

 

I would have to guess that they made view state last so as to avoid the scenario of users clicking on buttons and links that would be missing parameters; they simply made the entire page invalid until it finishes loading.

 

Neither of your two solutions would precisely work because:

 

1) document.ready occurs before images and so on loads, but doesn't fire until the closing HTML tag (or end of stream, etc). There's still definitely a window in which a partially loaded document can be rendered, which would leave the buttons as accessible until the document finishes loading; the view state can still be accessed in that incomplete scenario I mentioned before.

 

2) The "action" method on the page is called during the "action" step of the pseudo-code above. It's not two entirely separate transactions, it's one transaction with the constructor plus one additional method being called. This means that the page won't have loaded fully before output (in other words, you're effectively doing what you were doing originally, but in a more elaborate manner).

 

So, here's some thoughts:

 

1) Use your second method, but do not add the action to the page, but instead use an actionFunction, and use window.onload to call this actionFunction, using a reRender that targets the form element.

 

2) Use a containing div with a "display: none" style, and remove that style from the div once document.onready or window.onload fires. This avoids a round trip, so should save 2-5 seconds for those dialup users.

 

I'm sure there's other designs you could go for, but I hope this helps out.

This was selected as the best answer
TLFTLF

Awesome response sdfcfox. I appreciate you taking the time to write it up. I'll give your suggestions a try and follow up. FYI, I successfully used the Firefox Throttle plug-in to simulate slow connections.

sfdcfoxsfdcfox

Thanks for the info on the Firefox extension. I'll check it out. In the interim, let me know how it goes for you.

TLFTLF

Ok, I think I've got this working. Basically I did what you suggested sdfcfox. Here's what I did:

 

On my page, I added the following. This relies on the jQuery plugin to undo the "display:none;" style on apex:pageBlockButtons on the window load event. It also calls the setLoadComplete action function, which invokes a controller action that sets the "loadComplete" variable to true.

 

 

<apex:includeScript value="{!$Resource.jquery}" />
<script>
	jQuery.noConflict();
	jQuery(window).bind('load', function() {
		jQuery('.pbButton').css('display', 'inline');
		setLoadComplete();
	});
<script>
.
.
.
<apex:form id="theForm" >

	<apex:actionFunction name="setLoadComplete" action="{!loadComplete}" rerender="theForm" />

	<apex:pageBlock id="editBlock" mode="edit" rendered="{!mode == 'edit'}">
		<apex:pageBlockButtons id="editButtons" location="top" style="{!IF(NOT(loadComplete), 'display:none;', '')}" >
		.
		. <!-- buttons here -->
		.
		</apex:pageBlockButtons>

		.
		. <!-- page elements here -->
		.
	</apex:pageBlock>
</apex:form>

 

 

Here the controller code I added:

 

public with sharing class MyController {
	
	public Boolean loadComplete {
		get {
			if (loadComplete == null) {
				loadComplete = false;
			}
			return loadComplete;
		}
		set;
	}

	.
	.
	.

	public PageReference loadComplete() {
		loadComplete = true;
		return null;
	}
}

 

Thanks for your suggestion. It's a bit ugly when the page load takes a long time, because during that time, the buttons are not visible at all, then they appear once the load completes.